自定义Filter后,我的业务代码怎么被执行了多次?

简介: 若要求构建的过滤器针对全局路径有效,且无任何特殊需求(主要针对 Servlet 3.0 的一些异步特性),则完全可直接使用 Filter 接口(或继承 Spring 对 Filter 接口的包装类 OncePerRequestFilter),并使用**@Component** 将其包装为 Spring 中的普通 Bean,也可达到预期需求。

若要求构建的过滤器针对全局路径有效,且无任何特殊需求(主要针对 Servlet 3.0 的一些异步特性),则完全可直接使用 Filter 接口(或继承 Spring 对 Filter 接口的包装类 OncePerRequestFilter),并使用**@Component** 将其包装为 Spring 中的普通 Bean,也可达到预期需求。


使用哪种方式,可能都遇到问题:业务代码重复执行多次。以 @Component + Filter 接口实现呈现案例。


1 创建SB应用


14.png

UserController:

13.png

DemoFilter:

12.png

调用接口后日志:

11.png

业务代码竟被执行两次?预期是 Filter 的业务执行不会影响核心业务,所以当抛异常时,还是会调chain.doFilter。

但有时,会忘记及时返回而误闯其它chain.doFilter,最终导致自定义过滤器被执行多次。检查代码时,往往不能光速看出问题,所以这是类典型错误,虽然原因很简单。

来分析为何执行两次。


2 源码解析

2.1 责任链模式

Tomcat的Filter实现ApplicationFilterChain,采用责任链模式,像递归调用,区别在于:


递归调用,同一对象把子任务交给同一方法本身

责任链,一个对象把子任务交给其它对象的同名方法

核心在于上下文 FilterChain 在不同对象 Filter 间的传递与状态的改变,通过这种链式串联,即可对同种对象资源实现不同业务场景的处理,实现业务解耦。


FilterChain结构

10.png


请求来临时,执行到 StandardWrapperValve#invoke() ,创建 ApplicationFilterChain,并通过 ApplicationFilterChain#doFilter() 触发过滤器执行

ApplicationFilterChain#doFilter() 会执行其私有方法 internalDoFilter

在 internalDoFilter 方法中获取下一个Filter,并使用 request、response、this(当前ApplicationFilterChain 实例)作为参数来调用 doFilter():

public void doFilter(ServletRequest request, ServletResponse response,

FilterChain chain) throws IOException, ServletException

在 Filter 类的 doFilter() 中,执行Filter定义的动作并继续传递,获取第三个参数 ApplicationFilterChain,并执行其 doFilter()

此时会循环执行进入第 2 步、第 3 步、第 4 步,直到第3步中所有的 Filter 类都被执行完毕为止

所有的Filter过滤器都被执行完毕后,会执行 servlet.service(request, response) 方法,最终调用对应的 Controller 层方法

负责请求处理的触发时机:


StandardWrapperValve#invoke()

FilterChain 在何处被创建?

又在何处进行初始化调用,从而激活责任链开始链式调用?

public final void invoke(Request request, Response response)

   throws IOException, ServletException {

   // ...

   // 创建filterChain

   ApplicationFilterChain filterChain =

       ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

// ...

try {

   if ((servlet != null) && (filterChain != null)) {

       // Swallow output if needed

       if (context.getSwallowOutput()) {

            // ...

            // 执行责任链

            filterChain.doFilter(request.getRequest(),

                           response.getResponse());

            // ...

        }

// ...

}



FilterChain能被链式调用的细节:


ApplicationFilterFactory.createFilterChain()

public static ApplicationFilterChain createFilterChain(ServletRequest request,

       Wrapper wrapper, Servlet servlet) {

   // ...

   ApplicationFilterChain filterChain = null;

   if (request instanceof Request) {

       // ...

       // 创建FilterChain

       filterChain = new ApplicationFilterChain();

       // ...

   }

   // ...

   // Add the relevant path-mapped filters to this filter chain

   for (int i = 0; i < filterMaps.length; i++) {

       // ...

       ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)

           context.findFilterConfig(filterMaps[i].getFilterName());

       if (filterConfig == null) {

           continue;

       }

       // 增加filterConfig到Chain

       filterChain.addFilter(filterConfig);

   }

   // ...

   return filterChain;

}



它创建 FilterChain,并将所有 Filter 逐一添加到 FilterChain 中。


继续查看


ApplicationFilterChain

javax.servlet.FilterChain 的实现类


9.png

管理特定请求的一组过滤器的执行。 当所有定义的过滤器都执行完毕后,对 doFilter() 的下一次调用将执行 servlet#service() 本身。


实例变量

过滤器集

8.png


过滤器链中当前位置:


7.png

链中当前的过滤器数:

6.png

5.png

addFilter

每个被初始化的 Filter 都会通过 filterChain.addFilter() ,加入Filters,并同时更新n,使其等于 Filters数组长度。


至此,Spring 完成对 FilterChain 创建准备工作。


doFilter()

调用此链中的下一个过滤器,传递指定请求、响应。 若此链无更多过滤器,则调用 servlet#service()

4.png

被委派到当前类的私有方法


internalDoFilter(过滤器逻辑的核心)

每被调用一次,pos 变量值自增 1,即从类成员变量 Filters 中取下一个 Filter:

3.png

filter.doFilter(request, response, this) 会调用过滤器实现的 doFilter(),第三个参数为 this,即当前ApplicationFilterChain实例 ,即用户需要在过滤器中显式调用一次 javax.servlet.FilterChain#doFilter,才能完成整链路。


当pos < n,说明已执行完所有过滤器,才调用 servlet.service(request, response) 执行真正业务。从 internalDoFilter() 执行到 Controller#saveUser() 。

2.png


回到案例,DemoFilter#doFilter() 捕获异常的部分执行了一次,随后在 try 外面又执行一次,因而抛异常时,doFilter() 会被执行两次,相应的 servlet.service(request, response) 方法及对应的 Controller 处理方法也被执行两次。


3 修正

除去重复的 filterChain.doFilter(request, response) :

1.png

使用过滤器时,切忌多次调用 FilterChain#doFilter() 。


4 FAQ

假设一个过滤器因为种种原因,其doFilter()方法一次也没有调用,会有何结果?就像:


@Component

public class DemoFilter implements Filter {

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

       System.out.println("do some logic");

   }

}


自定义的filter中不调用 chain.doFilter() ,由于还在if (pos < n) {}作用域中,又没有继续调用下一个filter,就会直接return,无法执行核心业务代码 servlet.service(request, response);

目录
相关文章
|
6月前
|
JSON Java 数据格式
使用`MockMvc`额外的补充和高级用法
使用`MockMvc`额外的补充和高级用法
72 3
|
3月前
|
Apache
多应用模式下,忽略项目的入口文件,重写Apache规则
本文介绍了在多应用模式下,如何通过编辑Apache的.htaccess文件来重写URL规则,从而实现忽略项目入口文件index.php进行访问的方法。
|
5月前
|
安全 编译器 程序员
开发与运维调用问题之add(1.0f, 2.0)这个调用会匹配哪个版本的add函数如何解决
开发与运维调用问题之add(1.0f, 2.0)这个调用会匹配哪个版本的add函数如何解决
37 5
|
数据安全/隐私保护
fastadmin中写接口是时Validate规则验证自定义如何用
fastadmin中写接口是时Validate规则验证自定义如何用
264 0
|
XML Java 应用服务中间件
Filter 过滤器--基本原理--Filter 过滤器生命周期--过滤器链--注意事项和细节--全部应用实例--综合代码示例
Filter 过滤器--基本原理--Filter 过滤器生命周期--过滤器链--注意事项和细节--全部应用实例--综合代码示例
197 0
|
7月前
|
Java 数据安全/隐私保护
Filter概述、执行流程、拦截路径配置及过滤器链
Filter概述、执行流程、拦截路径配置及过滤器链
96 0
|
存储 XML 消息中间件
filter功能演示-鉴权、声明缓存
filter功能演示-鉴权、声明缓存
167 0
|
分布式计算 Spark
教材P164操作题。编写Spark Steaming程序,使用leftOuterJoin操作及filter方法过滤掉黑名单的数据
教材P164操作题。编写Spark Steaming程序,使用leftOuterJoin操作及filter方法过滤掉黑名单的数据
hook+ts业务开发思路5-完成列表页面的编写
hook+ts业务开发思路5-完成列表页面的编写
68 0
hook+ts业务开发思路5-完成列表页面的编写
流程定义查询和删除
流程定义查询流程定义查询和删除