前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义Filter后,我的业务代码怎么被执行了多次?

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

作者头像
JavaEdge
发布2023-02-13 15:08:00
8020
发布2023-02-13 15:08:00
举报
文章被收录于专栏:JavaEdge

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

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

1 创建SB应用

UserController:

DemoFilter:

调用接口后日志:

业务代码竟被执行两次?预期是 Filter 的业务执行不会影响核心业务,所以当抛异常时,还是会调chain.doFilter。 但有时,会忘记及时返回而误闯其它chain.doFilter,最终导致自定义过滤器被执行多次。检查代码时,往往不能光速看出问题,所以这是类典型错误,虽然原因很简单。 来分析为何执行两次。

2 源码解析

2.1 责任链模式

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

  • 递归调用,同一对象把子任务交给同一方法本身
  • 责任链,一个对象把子任务交给其它对象的同名方法

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

FilterChain结构
  1. 请求来临时,执行到 StandardWrapperValve#invoke() ,创建 ApplicationFilterChain,并通过 ApplicationFilterChain#doFilter() 触发过滤器执行
  2. ApplicationFilterChain#doFilter() 会执行其私有方法 internalDoFilter
  3. 在 internalDoFilter 方法中获取下一个Filter,并使用 request、response、this(当前ApplicationFilterChain 实例)作为参数来调用 doFilter(): public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
  4. 在 Filter 类的 doFilter() 中,执行Filter定义的动作并继续传递,获取第三个参数 ApplicationFilterChain,并执行其 doFilter()
  5. 此时会循环执行进入第 2 步、第 3 步、第 4 步,直到第3步中所有的 Filter 类都被执行完毕为止
  6. 所有的Filter过滤器都被执行完毕后,会执行 servlet.service(request, response) 方法,最终调用对应的 Controller 层方法

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

StandardWrapperValve#invoke()

  • FilterChain 在何处被创建?
  • 又在何处进行初始化调用,从而激活责任链开始链式调用?
代码语言:javascript
复制
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()

代码语言:javascript
复制
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 的实现类

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

实例变量

过滤器集

过滤器链中当前位置:

链中当前的过滤器数:

addFilter

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

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

doFilter()

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

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

internalDoFilter(过滤器逻辑的核心)

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

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

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

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

3 修正

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

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

4 FAQ

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

代码语言:javascript
复制
@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);

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023/01/22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 创建SB应用
  • 2 源码解析
    • 2.1 责任链模式
      • FilterChain结构
    • StandardWrapperValve#invoke()
      • ApplicationFilterFactory.createFilterChain()
        • ApplicationFilterChain
          • 实例变量
          • addFilter
          • doFilter()
          • internalDoFilter(过滤器逻辑的核心)
      • 3 修正
      • 4 FAQ
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档