
- 外文名
- Filter
- 发 布
- 2000年10月
- 体 现
- 代码开放性原则
- 作 用
- 改变request和修改response
- 出 自
- sun公司
- 中文名
- 过滤器
- 所 属
- servlet 2.3
-
java过滤器Filter
2019-07-31 19:08:31Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是过滤字符编码、做一些业务逻辑判断如是否有权限访问页面等。其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它...一、简介
Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的服务器端程序,主要的用途是过滤字符编码、做一些业务逻辑判断如是否有权限访问页面等。其工作原理是,只要你在web.xml文件配置好要拦截的客户端请求,它都会帮你拦截到请求,此时你就可以对请求或响应 (Request、Response)统一设置编码,简化操作;同时还可进行逻辑判断,如用户是否已经登陆、有没有权限访问该页面等等工作。它是随你的 web应用启动而启动的,只初始化一次,以后就可以拦截相关请求,只有当你的web应用停止或重新部署的时候才销毁,以下通过代码示例来了解它 的使用。
二、实例
package test.filter; import ...; /** * 介绍过滤器的使用,以设置编码为例 */ public class MyFilter implements Filter { private FilterConfig config = null; private boolean isFilter = false; public void destroy() { System.out.println("MyFilter准备销毁..."); } public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain chain) throws IOException, ServletException { // 强制类型转换 HttpServletRequest request = (HttpServletRequest)arg0; HttpServletResponse response = (HttpServletResponse)arg1; // 获取web.xm设置的编码集,设置到Request、Response 中 request.setCharacterEncoding(config.getInitParameter("charset")); response.setContentType(config.getInitParameter("contentType")); response.setCharacterEncoding(config.getInitParameter("charset")); // 将请求转发到目的地继续执行 chain.doFilter(request, response); } public void init(FilterConfig arg0) throws ServletException { this.config = arg0; if(isFilter){ System.out.println("MyFilter初始化..."); } } private void setIsFilter(boolean isFilter){ this.isFilter = isFilter; } }
然后在web. xml中配置该过滤器:
<filter> <filter-name>MyFilter</filter-name> <filter-class>test.filter.MyFilter</filter-class> <init-param> <param-name>isFilter</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <!-- 没有配置dispatcher就是默认request方式的 --> <dispatcher>FORWARD</dispatcher> <dispatcher>ERROR</dispatcher> <dispatcher>INCLUDE</dispatcher> </filt
三、详细介绍
在doFilter方法中通常都做些什么呢,下面列举一下:
1、通过控制对chain.doFilter的方法的调用,来决定是否需要访问目标资源。
比如,可以在用户权限验证等等。判断用户是否有访问某些资源的权限,有权限放行,没权限不执行chain.doFilter方法。
2、在调用chain.doFilter方法之前,做些处理来达到某些目的。
比如,解决中文乱码的问题等等。可以在doFilter方法前,执行设置请求编码与响应的编码。甚至可以对request接口进行封装装饰来处理get请求方式的中文乱码问题(重写相应的request.getParameter方法)。
3、在调用chain.doFilter方法之后,做些处理来达到某些目的。
比如对整个web网站进行压缩。在调用chain.doFilter方法之前用类A对response对象进行封装装饰,重写getOutputStream和重写getWriter方法。在类A内部中,将输出内容缓存进ByteArrayOutputStream流中,然后在chain.doFilter方法执行后,获取类A中ByteArrayOutputStream流缓存数据,用GZIPOutputStream流进行压缩下。Filter不仅可以通过url-pattern来指定拦截哪些url匹配的资源。而且还可以通过servlet-name来指定拦截哪个指定的servlet(专门为某个servlet服务了,servlet-name对应Servlet的相关配置)。
filter-mapping标签中dispatcher指定过滤器所拦截的资源被Servlet 容器调用的方式,可以是REQUEST,INCLUDE,FORWARD和ERROR之一,默认REQUEST。用户可以设置多个<dispatcher> 子元素用来指定 Filter 对资源的多种调用方式进行拦截。
REQUEST:
当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问或ERROR情况时,那么该过滤器就不会被调用。
INCLUDE:
如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。
FORWARD:
如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。
ERROR:
如若在A.jsp页面page指令中指定了error属性=examError.jsp,那么A.jsp中若出现了异常,会跳转到examError.jsp中处理。而在跳转到examError.jsp时,若过滤器配置了ERROR的dispather那么则会拦截,否则不会拦截。
四、高级配置(允许代理注入spring bean)
web.xml中配置过滤器DelegatingFilterProxy:
<filter> <filter-name>permission</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>permission</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping>
在spring bean配置中加入:
<bean id="permission" class="你的bean"></bean>
bean的id必须和filter-name一样。如果想不一样,可以这样配置:
<filter> <filter-name>permission</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>targetBeanName</param-name> <param-value>test</param-value> </init-param> </filter> <filter-mapping> <filter-name>permission</filter-name> <url-pattern>*.htm</url-pattern> </filter-mapping>
在spring bean配置中加入:
<bean id="test" class="你的bean"></bean>
以上你的spring bean必须实现Filter接口。
那这样子做是为了什么呢?
答:这样做就可以将DelegatingFilterProxy所代理的filter作为spring的bean,受到spring的管理,也就是通过Spring容器来管理filter的生命周期,还有就是如果filter中需要一些Spring容器的实例,可以通过spring直接注入,另外读取一些配置文件这些便利的操作都可以通过Spring来配置实现。
其中如果设置"targetFilterLifecycle"为True,则Filter.init()和Filter.destroy()有效;若为false,则这两个方法失效。
如果大家有用到shiro(一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理等)的话,通常就会用到这个DelegatingFilterProxy了!
-
-
Java Web之过滤器(Filter)
2018-07-31 16:58:40过滤器(Filter) 过滤器实际上就是对web资源进行拦截,做一些处理后再交给servlet。 通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理 大概流程图如下 应用场景 自动登录 统一设置...过滤器(Filter)
过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理
通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理大概流程图如下
应用场景
自动登录
统一设置编码格式
访问权限控制
敏感字符过滤等
创建Filter
在Servlet中我们一般都会对request和response中的字符集编码进行配置,如果Servlet过多字符集编码发生变化时修改起码会很麻烦,这些通用的字符集编码配置等工作我们可以放到Filter中来实现。
下面我们来创建一个处理字符集编码的Filter。右键包名—>new ---->Filter
输入过滤器名称,跟创建Servlet一样,这里我们直接使用 @WebFilter 注解,不再去web,xml中进行配置了。
创建完成后默认代码,可以看到,CharsetFilter实现了Filter接口,实现了3个方法。3个方法的作用已经在注释中写清楚了。
package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(filterName = "CharsetFilter") public class CharsetFilter implements Filter { public void destroy() { /*销毁时调用*/ } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { /*过滤方法 主要是对request和response进行一些处理,然后交给下一个过滤器或Servlet处理*/ chain.doFilter(req, resp);//交给下一个过滤器或servlet处理 } public void init(FilterConfig config) throws ServletException { /*初始化方法 接收一个FilterConfig类型的参数 该参数是对Filter的一些配置*/ } }
配置Filter
可配置的属性有这些
常用配置项
urlPatterns
配置要拦截的资源- 以指定资源匹配。例如
"/index.jsp"
- 以目录匹配。例如
"/servlet/*"
- 以后缀名匹配,例如
"*.jsp"
- 通配符,拦截所有web资源。
"/*"
**initParams **
配置初始化参数,跟Servlet配置一样例如
initParams = { @WebInitParam(name = "key",value = "value") }
dispatcherTypes **
配置拦截的类型,可配置多个。默认为DispatcherType.REQUEST**
例如dispatcherTypes = {DispatcherType.ASYNC,DispatcherType.ERROR}
其中DispatcherType是个枚举类型,有下面几个值
FORWARD,//转发的 INCLUDE,//包含在页面的 REQUEST,//请求的 ASYNC,//异步的 ERROR;//出错的
下面我们来对CharsetFilter 代码进行一下修改
package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import java.io.IOException; @WebFilter(filterName = "CharsetFilter", urlPatterns = "/*",/*通配符(*)表示对所有的web资源进行拦截*/ initParams = { @WebInitParam(name = "charset", value = "utf-8")/*这里可以放一些初始化的参数*/ }) public class CharsetFilter implements Filter { private String filterName; private String charset; public void destroy() { /*销毁时调用*/ System.out.println(filterName + "销毁"); } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { /*过滤方法 主要是对request和response进行一些处理,然后交给下一个过滤器或Servlet处理*/ System.out.println(filterName + "doFilter()"); req.setCharacterEncoding(charset); resp.setCharacterEncoding(charset); chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { /*初始化方法 接收一个FilterConfig类型的参数 该参数是对Filter的一些配置*/ filterName = config.getFilterName(); charset = config.getInitParameter("charset"); System.out.println("过滤器名称:" + filterName); System.out.println("字符集编码:" + charset); } }
这样一个简单的字符集编码处理的过滤器就完成了
我们看看执行打印的结果
需要注意的是
过滤器是在服务器启动时就会创建的,只会创建一个实例,常驻内存,也就是说服务器一启动就会执行Filter的init(FilterConfig config)方法.
当Filter被移除或服务器正常关闭时,会执行destroy方法
多个Filter的执行顺序
在我们的请求到达Servle之间是可以经过多个Filter的,一般来说,建议Filter之间不要有关联,各自处理各自的逻辑即可。这样,我们也无需关心执行顺序问题。
如果一定要确保执行顺序,就要对配置进行修改了,执行顺序如下- 在web.xml中,filter执行顺序跟
<filter-mapping>
的顺序有关,先声明的先执行 - 使用注解配置的话,filter的执行顺序跟名称的字母顺序有关,例如AFilter会比BFilter先执行
- 如果既有在web.xml中声明的Filter,也有通过注解配置的Filter,那么会优先执行web.xml中配置的Filter
我们写个小例子看一下
新建3个Filter,加上之前的CharsetFilter一共四个
其中CharsetFilter和ABFilter是通过注解声明的
CharsetFilter注解配置
@WebFilter(filterName = "CharsetFilter", urlPatterns = "/*",/*通配符(*)表示对所有的web资源进行拦截*/ initParams = { @WebInitParam(name = "charset", value = "utf-8")/*这里可以放一些初始化的参数*/ })
ABFilter
@WebFilter(filterName = "ABFilter",urlPatterns = "/*")
AFilter和BFilter是在web.xml配置的。
执行顺序跟<filter>
的顺序无关
<filter-mapping>
的顺序才决定执行顺序<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <filter> <filter-name>AFilter</filter-name> <filter-class>filter.AFilter</filter-class> </filter> <filter> <filter-name>BFilter</filter-name> <filter-class>filter.BFilter</filter-class> </filter> <!--这里BFilter在AFilter之前--> <filter-mapping> <filter-name>BFilter</filter-name> <url-pattern>/filter.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AFilter</filter-name> <url-pattern>/filter.jsp</url-pattern> </filter-mapping> </web-app>
每个Filter添加了打印语句,如下
以ABFilter为例package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(filterName = "ABFilter",urlPatterns = "/*") public class ABFilter implements Filter { private String filterName; public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println(filterName + " doFilter()"); chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { filterName= config.getFilterName(); System.out.println("过滤器名称:" + filterName +" init"); } }
下面我们来访问filter.jsp看看打印结果
可以看到,执行结果符合预期。
BFilter和AFilter是在web.xml中声明的,且BFilter的<filter-mapping>
在前,故BFilter在AFilter之前执行。
ABFilter和CharsetFilter是通过注解声明的,故他俩在BFilter和AFilter之后执行,但是ABFilter的名称以A开头,故在CharsetFilter之前执行
访问权限控制小例子##
下面我们写一个访问控制权限控制的小例子。
我们在浏览一些网站经常有这个情况,没有登录时是不允许我们访其主页的,只有登录过后才能访问。
下面我们就用Filter简单实现一下。需求分析
- 登录时将登录的账号密码保存到cookie中,下次访问时携带账号和密码,过滤器中进行校验
- 用户没有登录直接访问主页时,要跳转到登录页面
- 登录过滤器不对登录页面进行过滤
我们先来看一下项目结构
这里主要看一下LoginFilter的代码
我们在LoginFilter中对非登录页面的其他jsp都会进行过滤,判断cookie中是否携带了account和pwd。
如果有这两个数据表示之前登录过,那么对数据进行校验,正确的话就进行下一个操作。
否则的话,跳转到登录界面package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebFilter(filterName = "LoginFilter", urlPatterns = "*.jsp", dispatcherTypes = {}) public class LoginFilter implements Filter { public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("LoginFilter doFilter"); HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String url = request.getRequestURI(); System.out.println("请求的url:" + url); /*登录页面不需要过滤*/ int idx = url.lastIndexOf("/"); String endWith = url.substring(idx + 1); if (!endWith.equals("login.jsp")) { /*不是登录页面 进行拦截处理*/ System.out.println("不是登录页面,进行拦截处理"); if (!isLogin(request)) { System.out.println("没有登录过或者账号密码错误,跳转到登录界面"); response.sendRedirect("login.jsp"); } else { System.out.println("已经登录,进行下一步"); chain.doFilter(req, resp); } } else { System.out.println("是登录页面,不进行拦截处理"); chain.doFilter(req, resp); } } private boolean isLogin(HttpServletRequest request) { Cookie[] cookies = request.getCookies(); String account = ""; String pwd = ""; if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { if (cookie.getName().equals("account")) { account = cookie.getValue(); } else if (cookie.getName().equals("pwd")) { pwd = cookie.getValue(); } } } if (account.equals("") || pwd.equals("")) { return false; } else if (account.equals("yzq") && pwd.equals("123")) { return true; } return false; } public void init(FilterConfig config) throws ServletException { System.out.println("LoginFilter init"); } }
执行效果
可以看到,我们在没有登录的情况下直接去访问index.jsp页面时会自动跳转到登录页面,在登录成功后,再次直接访问index页面则可以访问。
下面是demo
如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!
- 以指定资源匹配。例如
-
拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别
2018-06-03 13:31:00一、引言 本来想记录一下关于用户登陆和登陆之后的权限管理、菜单管理的问题,想到解决这个问题用到Interceptor,但想到了Interceptor,就想到了Filter,于是...首先说一下Filter的使用地方,我们在配置web.x...一、引言
本来想记录一下关于用户登陆和登陆之后的权限管理、菜单管理的问题,想到解决这个问题用到Interceptor,但想到了Interceptor,就想到了Filter,于是就想说一下它们的执行顺序和区别。关于Interceptor解决权限和菜单管理的问题,在放在下一篇写吧,就酱紫。
二、区别
1、过滤器(Filter)
首先说一下Filter的使用地方,我们在配置web.xml时,总会配置下面一段设置字符编码,不然会导致乱码问题:
<filter> <filter-name>encoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encoding</filter-name> <servlet-name>/*</servlet-name> </filter-mapping>
配置这个地方的目的,是让所有的请求都需要进行字符编码的设置,下面来介绍一下Filter。
(1)过滤器(Filter):它依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
2、拦截器(Interceptor)
拦截器的配置一般在SpringMVC的配置文件中,使用Interceptors标签,具体配置如下:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.scorpios.atcrowdfunding.web.LoginInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.scorpios.atcrowdfunding.web.AuthInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
(2)拦截器(Interceptor):它依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
三、代码
下面在一个项目中我们使用既有多个过滤器,又有多个拦截器,并观察它们的执行顺序:
(1)第一个过滤器:public class TestFilter1 implements Filter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //在DispatcherServlet之前执行 System.out.println("############TestFilter1 doFilterInternal executed############"); filterChain.doFilter(request, response); //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后 System.out.println("############TestFilter1 doFilter after############"); } }
(2)第二个过滤器:
public class TestFilter2 implements Filter { @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //在DispatcherServlet之前执行 System.out.println("############TestFilter2 doFilterInternal executed############"); filterChain.doFilter(request, response); //在视图页面返回给客户端之前执行,但是执行顺序在Interceptor之后 System.out.println("############TestFilter2 doFilter after############"); } }
(3)在web.xml中注册这两个过滤器:
<!-- 自定义过滤器:testFilter1 --> <filter> <filter-name>testFilter1</filter-name> <filter-class>com.scorpios.filter.TestFilter1</filter-class> </filter> <filter-mapping> <filter-name>testFilter1</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 自定义过滤器:testFilter2 --> <filter> <filter-name>testFilter2</filter-name> <filter-class>com.scorpios.filter.TestFilter2</filter-class> </filter> <filter-mapping> <filter-name>testFilter2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
再定义两个拦截器:
(4)第一个拦截器:public class BaseInterceptor implements HandlerInterceptor{ /** * 在DispatcherServlet之前执行 * */ public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println("************BaseInterceptor preHandle executed**********"); return true; } /** * 在controller执行之后的DispatcherServlet之后执行 * */ public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { System.out.println("************BaseInterceptor postHandle executed**********"); } /** * 在页面渲染完成返回给客户端之前执行 * */ public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("************BaseInterceptor afterCompletion executed**********"); } }
(5)第二个拦截器:
public class TestInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception { System.out.println("************TestInterceptor preHandle executed**********"); return true; } public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { System.out.println("************TestInterceptor postHandle executed**********"); } public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { System.out.println("************TestInterceptor afterCompletion executed**********"); } }
(6)、在SpringMVC的配置文件中,加上拦截器的配置:
<!-- 拦截器 --> <mvc:interceptors> <!-- 对所有请求都拦截,公共拦截器可以有多个 --> <bean name="baseInterceptor" class="com.scorpios.interceptor.BaseInterceptor" /> <mvc:interceptor> <!-- 对/test.html进行拦截 --> <mvc:mapping path="/test.html"/> <!-- 特定请求的拦截器只能有一个 --> <bean class="com.scorpios.interceptor.TestInterceptor" /> </mvc:interceptor> </mvc:interceptors>
(7)、定义一个Controller控制器:
package com.scorpios.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class TestController { @RequestMapping("/test") public ModelAndView handleRequest(){ System.out.println("---------TestController executed--------"); return new ModelAndView("test"); } }
(8)、测试结果:
启动测试项目,地址如下:http://www.localhost:8080/demo,可以看到控制台中输出如下:
这就说明了过滤器的运行是依赖于servlet容器,跟springmvc等框架并没有关系。并且,多个过滤器的执行顺序跟xml文件中定义的先后关系有关。接着清空控制台,并访问:http://www.localhost:8080/demo/test,再次看控制台的输出:
从这个控制台打印输出,就可以很清晰地看到有多个拦截器和过滤器存在时的整个执行顺序了。当然,对于多个拦截器它们之间的执行顺序跟在SpringMVC的配置文件中定义的先后顺序有关。四、总结
对于上述过滤器和拦截器的测试,可以得到如下结论:
(1)、Filter需要在web.xml中配置,依赖于Servlet;
(2)、Interceptor需要在SpringMVC中配置,依赖于框架;
(3)、Filter的执行顺序在Interceptor之前,具体的流程见下图;
(4)、两者的本质区别:拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。 -
spring cloud gateway之filter篇
2018-12-17 21:45:21转载请标明出处: ...本文出自方志朋的博客 在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理,在路由处理之前,需要经过“pre...由filter工作流程点,可以知道filter有着非常重要的作用...转载请标明出处:
http://blog.csdn.net/forezp/article/details/85057268
本文出自方志朋的博客个人博客纯净版https://www.fangzhipeng.com/springcloud/2018/12/21/sc-f-gatway3.html
在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理,在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应之后,可以由“post”类型的过滤器处理。
filter的作用和生命周期
由filter工作流程点,可以知道filter有着非常重要的作用,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。首先需要弄清一点为什么需要网关这一层,这就不得不说下filter的作用了。
作用
当我们有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
对于这样重复的工作,有没有办法做的更好,答案是肯定的。在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。
生命周期
Spring Cloud Gateway同zuul类似,有“pre”和“post”两种方式的filter。客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter。
gateway filter
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。
GatewayFilter工厂同上一篇介绍的Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。
Spring Cloud Gateway 内置的过滤器工厂一览表如下:
现在挑几个常见的过滤器工厂来讲解,每一个过滤器工厂在官方文档都给出了详细的使用案例,如果不清楚的还可以在org.springframework.cloud.gateway.filter.factory看每一个过滤器工厂的源码。
AddRequestHeader GatewayFilter Factory
创建工程,引入相关的依赖,包括spring boot 版本2.0.5,spring Cloud版本Finchley,gateway依赖如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
在工程的配置文件中,加入以下的配置:
server: port: 8081 spring: profiles: active: add_request_header_route --- spring: cloud: gateway: routes: - id: add_request_header_route uri: http://httpbin.org:80/get filters: - AddRequestHeader=X-Request-Foo, Bar predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver] profiles: add_request_header_route
在上述的配置中,工程的启动端口为8081,配置文件为add_request_header_route,在add_request_header_route配置中,配置了roter的id为add_request_header_route,路由地址为http://httpbin.org:80/get,该router有AfterPredictFactory,有一个filter为AddRequestHeaderGatewayFilterFactory(约定写成AddRequestHeader),AddRequestHeader过滤器工厂会在请求头加上一对请求头,名称为X-Request-Foo,值为Bar。为了验证AddRequestHeaderGatewayFilterFactory是怎么样工作的,查看它的源码,AddRequestHeaderGatewayFilterFactory的源码如下:
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() .header(config.getName(), config.getValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); }; } }
由上面的代码可知,根据旧的ServerHttpRequest创建新的 ServerHttpRequest ,在新的ServerHttpRequest加了一个请求头,然后创建新的 ServerWebExchange ,提交过滤器链继续过滤。
启动工程,通过curl命令来模拟请求:
curl localhost:8081
最终显示了从 http://httpbin.org:80/get得到了请求,响应如下:
{ "args": {}, "headers": { "Accept": "*/*", "Connection": "close", "Forwarded": "proto=http;host=\"localhost:8081\";for=\"0:0:0:0:0:0:0:1:56248\"", "Host": "httpbin.org", "User-Agent": "curl/7.58.0", "X-Forwarded-Host": "localhost:8081", "X-Request-Foo": "Bar" }, "origin": "0:0:0:0:0:0:0:1, 210.22.21.66", "url": "http://localhost:8081/get" }
可以上面的响应可知,确实在请求头中加入了X-Request-Foo这样的一个请求头,在配置文件中配置的AddRequestHeader过滤器工厂生效。
跟AddRequestHeader过滤器工厂类似的还有AddResponseHeader过滤器工厂,在此就不再重复。
RewritePath GatewayFilter Factory
在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能,这个功能是Zuul没有的。在配置文件中加上以下的配置:
spring: profiles: active: rewritepath_route --- spring: cloud: gateway: routes: - id: rewritepath_route uri: https://blog.csdn.net predicates: - Path=/foo/** filters: - RewritePath=/foo/(?<segment>.*), /$\{segment} profiles: rewritepath_route
上面的配置中,所有的/foo/**开始的路径都会命中配置的router,并执行过滤器的逻辑,在本案例中配置了RewritePath过滤器工厂,此工厂将/foo/(?.*)重写为{segment},然后转发到https://blog.csdn.net。比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.csdn.net/forezp的页面,比如在网页上请求localhost:8081/foo/forezp/1,页面显示404,就是因为不存在https://blog.csdn.net/forezp/1这个页面。
自定义过滤器
Spring Cloud Gateway内置了19种强大的过滤器工厂,能够满足很多场景的需求,那么能不能自定义自己的过滤器呢,当然是可以的。在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口。写一个RequestTimeFilter,代码如下:
public class RequestTimeFilter implements GatewayFilter, Ordered { private static final Log log = LogFactory.getLog(GatewayFilter.class); private static final String REQUEST_TIME_BEGIN = "requestTimeBegin"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN); if (startTime != null) { log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms"); } }) ); } @Override public int getOrder() { return 0; } }
在上面的代码中,Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。还有有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。然后将该过滤器注册到router中,代码如下:
@Bean public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) { // @formatter:off return builder.routes() .route(r -> r.path("/customer/**") .filters(f -> f.filter(new RequestTimeFilter()) .addResponseHeader("X-Response-Default-Foo", "Default-Bar")) .uri("http://httpbin.org:80/get") .order(0) .id("customer_filter_router") ) .build(); // @formatter:on }
重启程序,通过curl命令模拟请求:
curl localhost:8081/customer/123
在程序的控制台输出一下的请求信息的日志:
2018-11-16 15:02:20.177 INFO 20488 --- [ctor-http-nio-3] o.s.cloud.gateway.filter.GatewayFilter : /customer/123: 152ms
自定义过滤器工厂
在上面的自定义过滤器中,有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂,在打印时间的时候,可以设置参数来决定是否打印请参数。查看GatewayFilterFactory的源码,可以发现GatewayFilterfactory的层级如下:
过滤器工厂的顶级接口是GatewayFilterFactory,我们可以直接继承它的两个抽象类来简化开发AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader)。
过滤器工厂的顶级接口是GatewayFilterFactory,有2个两个较接近具体实现的抽象类,分别为AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这2个类前者接收一个参数,比如它的实现类RedirectToGatewayFilterFactory;后者接收2个参数,比如它的实现类AddRequestHeaderGatewayFilterFactory类。现在需要将请求的日志打印出来,需要使用一个参数,这时可以参照RedirectToGatewayFilterFactory的写法。
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> { private static final Log log = LogFactory.getLog(GatewayFilter.class); private static final String REQUEST_TIME_BEGIN = "requestTimeBegin"; private static final String KEY = "withParams"; @Override public List<String> shortcutFieldOrder() { return Arrays.asList(KEY); } public RequestTimeGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN); if (startTime != null) { StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath()) .append(": ") .append(System.currentTimeMillis() - startTime) .append("ms"); if (config.isWithParams()) { sb.append(" params:").append(exchange.getRequest().getQueryParams()); } log.info(sb.toString()); } }) ); }; } public static class Config { private boolean withParams; public boolean isWithParams() { return withParams; } public void setWithParams(boolean withParams) { this.withParams = withParams; } } }
在上面的代码中 apply(Config config)方法内创建了一个GatewayFilter的匿名类,具体的实现逻辑跟之前一样,只不过加了是否打印请求参数的逻辑,而这个逻辑的开关是config.isWithParams()。静态内部类类Config就是为了接收那个boolean类型的参数服务的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法。
。需要注意的是,在类的构造器中一定要调用下父类的构造器把Config类型传过去,否则会报ClassCastException
最后,需要在工程的启动文件Application类中,向Srping Ioc容器注册RequestTimeGatewayFilterFactory类的Bean。
@Bean public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() { return new RequestTimeGatewayFilterFactory(); }
然后可以在配置文件中配置如下:
spring: profiles: active: elapse_route --- spring: cloud: gateway: routes: - id: elapse_route uri: http://httpbin.org:80/get filters: - RequestTime=false predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver] profiles: elapse_route
启动工程,在浏览器上访问localhost:8081?name=forezp,可以在控制台上看到,日志输出了请求消耗的时间和请求参数。
global filter
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:
-
GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上
-
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
Spring Cloud Gateway框架内置的GlobalFilter如下:
上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。在下面的案例中将讲述如何编写自己GlobalFilter,该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:
public class TokenFilter implements GlobalFilter, Ordered { Logger logger=LoggerFactory.getLogger( TokenFilter.class ); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (token == null || token.isEmpty()) { logger.info( "token is empty..." ); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return -100; } }
在上面的TokenFilter需要实现GlobalFilter和Ordered接口,这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest,然后根据ServerHttpRequest中是否含有参数token,如果没有则完成请求,终止转发,否则执行正常的逻辑。
然后需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中,代码如下:
@Bean public TokenFilter tokenFilter(){ return new TokenFilter(); }
启动工程,使用curl命令请求:
curl localhost:8081/customer/123
可以看到请没有被转发,请求被终止,并在控制台打印了如下日志:
2018-11-16 15:30:13.543 INFO 19372 --- [ctor-http-nio-2] gateway.TokenFilter : token is empty...
上面的日志显示了请求进入了没有传“token”的逻辑。
总结
本篇文章讲述了Spring Cloud Gateway中的过滤器,包括GatewayFilter和GlobalFilter。从官方文档的内置过滤器讲起,然后讲解自定义GatewayFilter、GatewayFilterFactory以及自定义的GlobalFilter。有很多内置的过滤器并没有讲述到,比如限流过滤器,这个我觉得是比较重要和大家关注的过滤器,将在之后的文章讲述。
更多阅读
参考资料
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.0.M1/single/spring-cloud-gateway.html
https://www.jianshu.com/p/eb3a67291050
https://blog.csdn.net/qq_36236890/article/details/80822051
https://windmt.com/2018/05/08/spring-cloud-14-spring-cloud-gateway-filter
源码下载
https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate
扫一扫,支持下作者吧(转载本站文章请注明作者和出处 方志朋的博客)
-
-
JAVA8 Stream接口,map操作,filter操作,flatMap操作
2018-06-10 17:22:181,filter操作,我们先看方法的定义 Stream<T> filter(Predicate<? super T> predicate); 这个方法,传入一个Predicate的函数接口,关于Predicate函数接口定义,可以查看《JAV... -
FFmpeg filter
2018-06-10 17:08:44一、FFmpeg filter简介FFmpeg中的libavfilter提供了一整套的基于filter的机制。filter本身是一个插件的形式,可以快速的组装需要的效果。比如下面的filter,可以实现视频的水平镜像效果。ffplay.exe sample.rmvb -vf... -
导向滤波(Guided Filter)的解析与实现
2017-07-10 17:54:31导引滤波(Guided Filter)是由Dr. Kaiming He等人提出的一种滤波算法(现在MATLAB的图像处理工具箱中已经内置了实现该算法的函数)。导引滤波不仅可以实现edge-preserving的图像平滑效果,亦可广泛应用于图像去雾、... -
Zuul Filter
2018-10-30 17:51:43文章目录简介Filter 类型Zuul 原生 Filter 简介 Zuul 的核心逻辑是由一系列紧密配合工作的 Filter 来实现的,它们能够在进行 HTTP 请求或者响应的时候执行相关操作。可以说,没有 Filter 责任链,就没有如今的 ... -
PEAK FILTER & NOTCH filter
2016-05-26 16:13:54PEAK FILTER -
Java进阶(三十三)java基础-filter
2016-06-30 21:52:54java基础-filter 我们先看看没有filter的时候,整个web客户端-服务端的一个流程。 接下来我们再看看引入了filter之后的Uml图。尝试分析这两者之间的差别。 filter从哪里来? 是在servlet2.3规范中加入的。 filter的... -
filter_input filter_input_array php筛选器 php过滤器
2020-10-12 13:04:57filter_input 获取一个输入变量,并对它进行过滤 filter_input_array 获取多个输入变量,并通过相同的或不同的过滤器对它们进行过滤 语法 filter_input ( int $type , string $variable_name [, int $filter = ... -
SpringBoot下,利用@WebFilter配置使用与注意Filter
2018-09-11 18:08:04Filter简介 Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如JSP,Servlet,静态图片文件或静态HTML文件进行拦截,从而实现一些特殊功能。... -
MATLAB 中 filter 函数的使
2014-08-20 20:47:42一维数字滤波滤波器filter 使用 filter 1-D digital filter expand all in page Syntax y = filter(b,a,X) [y,zf] = filter(b,a,X) [y,zf] = filter(b,a,X,zi) y = filter(b,a... -
Python高级函数Counter、defaultdict、map、reduce、filter使用
2020-08-18 23:38:13在这里为大家介绍一下Python非常实用的Counter、defaultdict、map、reduce、filter、groupby的函数使用,提高大家在平时实用Python的效率计数器函数 Counter带默认值的字典 defaultdictmap函数reduce函数filter函数 ... -
使用 Java8的 stream对list数据去重,使用filter()过滤列表,list转map
2018-09-12 14:00:50list去重,根据对象某个属性、某几个属性去重 去除List中重复的String List unique = list.stream().distinct().collect(Collectors.toList()); 去除List中重复的对象 // Person 对象 ... ... -
MATLAB中filter的理解与使用
2019-01-17 11:41:50MATLAB新手学习记录——filter函数使用filter概念与基本语法以最简单的 y = filter(b,a,X) 为例**可实现差分方程** 上学期刚学过《信号与系统》,因此这个寒假打算在确定考研(有一丢丢可能保研)目标院校之余,学习... -
Kalman Filter 通俗讲解
2018-06-03 16:44:05Kalman Filter,很多人刚听到这个名词时,总是会下意识认为这就是个滤波器。我这里想要重点声明的是,Kalman Filter不是滤波,它是一种信息融合的过程。 那么Kalman Filter到底是什么?它在那些方面有着应用,它的... -
shiro默认filter
2017-08-24 16:47:31shiro默认filter -
Spring Boot filter
2020-02-17 10:07:08在Spring Boot中自定义filter 本文我们将会讲解如何在Spring Boot中自定义filter并指定执行顺序。 定义Filter很简单,我们只需要实现Filter接口即可,同时我们可指定@Order来确定其执行顺序,我们定义两个filter如下... -
bloom filter与Cuckoo Filter
2015-09-19 19:11:51bloom filter与Cuckoo Filter 1. bloom filter 算法是: 创建一个m位BitSet,先将所有位初始化为0,然后选择k个不同的哈希函数。第i个哈希函数对字符串str哈希的结果记为h(i,str),且h(i,str)的范围是0到m-... -
filter简介
2015-11-13 16:38:14Filter可认为是Servlet的一种"加强版" 使用Filter完整流程是:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理,最后Filter再对服务器响应进行后处理 Filter有如下用处: 在HttpServletRequest到达... -
SQLAlchemy中filter_by()和filter()的用法不同
2019-09-18 22:27:36filter_by() 和 filter() 的最主要的区别: 模块 语法 ><(大于和小于)查询 and_和or_查询 filter_by() 直接用属性名,比较用= 不支持 不支持 filter() 用类名.属性名,比较用== ...
-
2012年水资源公报.doc
-
mysql-5.7.20-win32-debug-test.zip
-
大数据Hive on MR/TEZ与hadoop的整合应用
-
Collections.xmind
-
记忆碎片之python装饰器及多个装饰器联合使用
-
方法重载(动态参数列表)
-
ArcGIS Pro2.6和ArcGIS Enterprise学习
-
继承和派生(1)--继承和派生的基本概念
-
thinkphp5.1博客后台实战视频
-
psasp自带全算例7[1].0
-
QC_T29106-2014汽车电线束技术条件最新_PDF解密.pdf
-
MQTT实现SHT20温度传感器定时上传温度值
-
表彰大会实施方案.docx
-
基于Java的RTSP服务源码
-
Java学习路线,好的学习路线和好的方法,能让我们少走些弯路
-
FFmpeg之yuv裁剪(二十一)
-
生成树协议
-
eCognition8.9系列产品介绍.pdf
-
百度apollo无人车传感器 IMU 深入剖析
-
【数据分析-随到随学】Hadoop数据分析