JavaWeb三大组件之Filter学习详解
Filter基本上可以说存在所有的JavaWeb项目中,比如最基本的一个请求参数的编码CharacterEncodingFilter,大家一般都会配置下,那么filter是干嘛的呢?
本篇将主要集中在fitler的以下几个知识点:
- 干嘛的
- 怎么用
- 多个Filter执行的先后顺序
- 注意事项
I. 基本知识
Filter称之为过滤器,是用来做一些拦截的任务, 在Servlet接受请求之前,做一些事情,如果不满足限定,可以拒绝进入Servlet

从上面的图,可以看出一个Filter的工作流程:
一个http请求过来之后
- 首先进入filter,执行相关业务逻辑
- 若判定通行,则进入Servlet逻辑,Servlet执行完毕之后,又返回Filter,最后在返回给请求方
- 判定失败,直接返回,不需要将请求发给Servlet
通过上面的流程,可以推算使用场景:
- 在filter层,来获取用户的身份
- 可以考虑在filter层做一些常规的校验(如参数校验,referer校验等)
- 可以在filter层做稳定性相关的工作(如全链路打点,可以在filter层分配一个traceId;也可以在这一层做限流等)
1. 基本使用姿势
要使用一个Filter,一半需要两步,实现Filter接口的自定义类,web.xml中对filter的定义
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | public interface Filter {public void init(FilterConfig filterConfig) throws ServletException;
 
 
 public void doFilter(ServletRequest request, ServletResponse response,
 FilterChain chain)
 throws IOException, ServletException;
 
 
 public void destroy();
 }
 
 | 
主要就三个方法,从命名来看,
- 也比较清晰,在创建Filter对象的时候,调用 init方法
- 销毁Filter对象的时候,调用 destroy方法
- 当请求过来之后,调用 doFilter,也就是主要的业务逻辑所在了
详细case后面再说
接下来就是xml的配置了,和Servlet类似,每自定义一个,都需要在xml中加上一个配置(挺繁琐的操作的)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | <filter>
 <filter-name>encodingFilter</filter-name>
 <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
 <async-supported>true</async-supported>
 <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>encodingFilter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 
 | 
配置也比较简单了,一个  一个  前者定义具体的Filter,后者表示这个Filter拦截的URL (看起来和Servlet的配置规则没什么两样)
II. 实例
我们的实例,就拿大名鼎鼎的CharacterEncodingFilter来说明,顺带膜拜下Spring的大神的优秀源码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 | public class CharacterEncodingFilter extends OncePerRequestFilter {
 private String encoding;
 
 private boolean forceEncoding = false;
 
 public CharacterEncodingFilter() {
 }
 
 public CharacterEncodingFilter(String encoding) {
 this(encoding, false);
 }
 
 public CharacterEncodingFilter(String encoding, boolean forceEncoding) {
 Assert.hasLength(encoding, "Encoding must not be empty");
 this.encoding = encoding;
 this.forceEncoding = forceEncoding;
 }
 
 public void setEncoding(String encoding) {
 this.encoding = encoding;
 }
 
 public void setForceEncoding(boolean forceEncoding) {
 this.forceEncoding = forceEncoding;
 }
 
 @Override
 protected void doFilterInternal(
 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 throws ServletException, IOException {
 
 if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null)) {
 request.setCharacterEncoding(this.encoding);
 if (this.forceEncoding) {
 response.setCharacterEncoding(this.encoding);
 }
 }
 filterChain.doFilter(request, response);
 System.out.printl("servelt 执行完成,又返回filter");
 }
 }
 
 | 
上面的实现比较简单,主要将视线集中在 doFilterInternal 方法内部,如果要设置编码参数,则直接修改 HttpServletRequest, HttpServletResponse 两个参数,操作完成之后,执行下面这一行
| 1
 | filterChain.doFilter(request, response);
 | 
注意
- 上面这一行执行,表示Filter层已经通过了,请求可以转发给下一个Filter或者直接传给Servlet
- 而下一个Filter, Servlet执行完成之后,还会继续往下走,就是上面的那一行输出,也会被调用(那一行是我加的,源码中没有)
所以,如果你不希望继续往下走,那么就简单了,不执行上面的那一行即可
疑问
问题一:看了上面的源码,一个很明显的问题就是,参数怎么设置的?
仔细看上面的源码,发现自定义Filter是继承 org.springframework.web.filter.OncePerRequestFilter 而不是直接实现的 Filter 接口,而且方法内也没有显示的实现 init()方法,所有很容易猜到是父类中实现了参数的初始化过程
具体的实现逻辑是在 org.springframework.web.filter.GenericFilterBean#init 中,同样是Spring实现的,主要代码捞出来
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | public final void init(FilterConfig filterConfig) throws ServletException {Assert.notNull(filterConfig, "FilterConfig must not be null");
 if (logger.isDebugEnabled()) {
 logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
 }
 
 this.filterConfig = filterConfig;
 
 
 try {
 PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
 ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
 initBeanWrapper(bw);
 bw.setPropertyValues(pvs, true);
 }
 catch (BeansException ex) {
 String msg = "Failed to set bean properties on filter '" +
 filterConfig.getFilterName() + "': " + ex.getMessage();
 logger.error(msg, ex);
 throw new NestedServletException(msg, ex);
 }
 
 
 initFilterBean();
 
 if (logger.isDebugEnabled()) {
 logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
 }
 }
 
 | 
看上面一大串的代码,到底干了嘛? 简单来讲,就是获取xml中配置的参数,然后填充到Filter对象中(对Srping而言,CharacterEncodingFilter就是一个bean),这个具体的逻辑和本篇关系不大,就直接跳过了
问题二:在Filter层中可以获取参数么
从doFilter的方法签名中看,既然有Request参数,那么应该是可以获取到请求参数的,那么实际验证一下
先实现一个最最最简单的Filter
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class TestFilter implements Filter {@Override
 public void init(FilterConfig filterConfig) throws ServletException {
 }
 
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
 System.out.println("in filter");
 System.out.println("args: " + JSON.toJSONString(request.getParameterMap()));
 chain.doFilter(request, response);
 System.out.println("out filter");
 }
 
 @Override
 public void destroy() {
 }
 }
 
 | 
开始测试
| 1
 | curl -d 'name=Hello&password=world' http://127.0.0.1:8088/123
 | 
输出如下
| 12
 3
 
 | in filterargs: {"name":["Hello"],"password":["world"]}
 out filter
 
 | 
注意
在Filter中获取参数时,最好不要直接使用获取请求流的方式,如果获取请求流,那么Servlet就获取不到请求参数了
问题三:多个filter的顺序怎么定
前面学习Servlet的时候,也有这个问题,一个URL被多个Servlet命中了,那么先后顺序是怎样的呢?
- 精确匹配 > 最长匹配 > 其他模糊匹配 > 没有匹配的则是404
那么Filter呢,他们的区别还是比较明显的,很多Filter都是拦截所有的请求,即很多Filter的命中规则都是一样的,那么怎么办?
- 先执行带有url-pattern标签的filter,再执行带有servlet-name标签的filter
- 如果同为url-pattern或servlet-name,则会按照在web.xml中的声明顺序执行
测试case如下,我们定义三个Filter: 
- TestFilter: 匹配所有路径
- ATestFilter: 匹配所有路径
- ServletFilter: 匹配 mvc-servlet
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 System.out.println("in ATestFilter");
 chain.doFilter(request, response);
 }
 
 
 
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 System.out.println("in TestFilter");
 chain.doFilter(request, response);
 }
 
 
 @Override
 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 System.out.println("in ServletFilter");
 chain.doFilter(request, response);
 }
 
 | 
对应的xml配置如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | <filter><filter-name>servletFilter</filter-name>
 <filter-class>com.test.ServletFilter</filter-class>
 <async-supported>true</async-supported>
 </filter>
 <filter-mapping>
 <filter-name>servletFilter</filter-name>
 <servlet-name>mvc-dispatcher</servlet-name>
 </filter-mapping>
 
 <filter>
 <filter-name>testFilter</filter-name>
 <filter-class>com.test.TestFilter</filter-class>
 <async-supported>true</async-supported>
 </filter>
 <filter-mapping>
 <filter-name>testFilter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 
 
 <filter>
 <filter-name>atestFilter</filter-name>
 <filter-class>com.test.ATestFilter</filter-class>
 <async-supported>true</async-supported>
 </filter>
 <filter-mapping>
 <filter-name>atestFilter</filter-name>
 <url-pattern>/*</url-pattern>
 </filter-mapping>
 
 | 
输出结果
| 12
 3
 
 | in TestFilterin ATestFilter
 in ServletFilter
 
 | 
III. 小结
Filter 通常用于JavaWeb的过滤使用,通过doFilter方法中执行 chain.doFilter(request, response);,进入下一个Filter或者Servlet执行逻辑,当执行完成之后,依然会回到Filter这一层,继续走下去
针对上面的逻辑,Filter的常见应用场景有:
- 用户信息获取,身份校验
- 安全校验(referer校验失败,直接拒绝)
- 稳定性相关(限流,监控埋点,全链路日志埋点)
Filter的执行顺序:
- url-mapping 的优先执行,其次是 servlet-mapping
- 同一个匹配方式(如都是url-mapping)中,根据在xml中定义的先后顺序来确定
Filter的注意事项:
- 正常业务,请记得一定执行 chain.doFilter(request, response), 最后把它放在finnal块中,防止你在Filter中的代码抛异常导致进入不到后续的逻辑
- 在Filter中不要直接获取请求数据流(请求流被读取完之后,Servlet就get不到了!)
IV. 其他
声明
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正
扫描关注,java分享
