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

news2024/11/15 17:21:03

若要求构建的过滤器针对全局路径有效,且无任何特殊需求(主要针对 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 在何处被创建?
  • 又在何处进行初始化调用,从而激活责任链开始链式调用?
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 的实现类

管理特定请求的一组过滤器的执行。 当所有定义的过滤器都执行完毕后,对 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()方法一次也没有调用,会有何结果?就像:

@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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分享130个ASP源码,总有一款适合您

ASP源码 分享130个ASP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 130个ASP源码下载链接&#xff1a;https://pan.baidu.com/s/13ZxaHPHdgorjGV1wWvx1WQ?pwd7z4p 提取码&#x…

07_FreeRTOS任务调度器的挂起和恢复

任务调度器的挂起和恢复 挂起任务调度器,调用此函数不需要关闭中断 使用格式示例: 1.与临界区不一样的是,挂起任务调度器,未关闭中断; 2.它仅仅是防止&#xff1b;饿任务之间的资源争夺,中断照样可以直接响应; 3.挂起调度器的方式,适合于临界区位于任务与任务之间;既不用去延…

MySQL 8.0.31 集合操作INTERSECT和EXCEPT

对于聚合的功能MySQL是都是默默的发展。在最新的8.0.31版本中提供对集合操作INTERSECT和EXCEPT。这样一来&#xff0c;集合操作功能基本圆满了。MySQL5.7.40版本是不支持这个集合的。 In this release MySQL adds support for the SQL standard INTERSECT and EXCEPT table op…

Linux常用命令——ssh命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) ssh openssh套件中的客户端连接工具 补充说明 ssh命令是openssh套件中的客户端连接工具&#xff0c;可以给予ssh加密协议实现安全的远程登录服务器。 语法 ssh(选项)(参数)选项 -1&#xff1a;强制使用ssh协…

基于FPGA的时间数字转换(TDC)设计(四)

1. 基于IODELAY的TDC设计原理 在第一篇中讲过,基于FPGA开发的TDC常见的有直接计数法,多相位时钟采样法,抽头延迟线法等等。前面3篇讲解了基于多相位的TDC,接下来主要讲解基于抽头延迟线的方法。在Xilinx FPGA开发中,要实现抽头延迟线,主要由进位链(Carry4)和IODELAY模块…

寒假题练——day(6)

题目1&#xff1a; 有一种兔子&#xff0c;从出生后第3个月起每个月都生一只兔子&#xff0c; 小兔子长到第三个月后每个月又生一只兔子。 例子&#xff1a;假设一只兔子第3个月出生&#xff0c;那么它第5个月开始会每个月生一只兔子。 一月的时候有一只兔子&#xff0c;假如兔…

检索方案优化

文章目录 1. Flab框架概览2. Flab框架各个层在基金检索的具体应用2.1. 前置检查Check层2.2. 多路召回Recall层2.3. 结果集过滤2.3.1. 问财和我们召回结果的交集2.4. 排序Rank2.5. 缓存Cache2.6. 封装Assmeble1. Flab框架概览 Fly like a bird 寓意灵活 2. Flab框架各个层在基金…

2023年大年初一 —— 牛客网刷题经验分享~

2023年大年初一 —— 牛客网刷题经验分享~&#x1f60e;大年初一 —— 牛客网刷题经验分享~&#x1f60e;)前言&#x1f64c;BC94 反向输出一个四位数 &#x1f60a;BC95 小乐乐与进制转换 &#x1f60a;BC96 [NOIP2015]金币&#x1f60a;BC97 回文对称数 &#x1f60a;总结撒花…

pytorch 神经网络基础入门笔记【b站小土堆】

文章目录python深度学习配置环境anacondapycharmpytorchpython学习中的两大法宝函数加载数据Tensorboard使用torchvision中的transformstensor数据类型transform该如何使用为什么我们需要Tensor类型更好的使用transformsToTensorNormalizeResizeComposeRandomCrop总结torchvisi…

JVM快速入门学习笔记(三)

9. 栈 栈&#xff1a;8大基本类型对象引用 栈运行原理&#xff1a;栈帧 程序正在执行的方法&#xff0c;一定在栈的顶部 9.1 JVM数据区 先上一张Java虚拟机运行时数据区中堆、栈以及方法区存储数据的概要图&#xff0c;如下所示&#xff1a; 9.2 堆 堆是存储时的单位&…

美团出品 | YOLOv6 v3.0 is Coming(超越YOLOv7、v8)

&#x1f680;&#x1f680;&#x1f680;美团出品 | YOLOv6 v3.0 is Coming &#xff01;&#xff01;✨✨✨ 一、前言简介 &#x1f384;&#x1f388; &#x1f4da; 代码地址&#xff1a;美团出品 | YOLOv6 3.0代码下载地址 &#x1f4da; 文章地址&#xff1a;https://a…

四、python-pyecharts图表可视化(黑马程序猿-python学习记录)

黑马程序猿的python学习视频&#xff1a;https://www.bilibili.com/video/BV1qW4y1a7fU/ 目录 1. 官网链接 2. 下载pyecharts 3. 编写一个折线图 4. 隐藏线段上的数据 5. 绘制柱状图 6. 柱状图的xy轴反转 7. 柱状图设置提示在最右边 8. 时间柱状图 9. 时间柱状图设置颜色主题 …

第三章 AOP

1.AOP基本概念*什么是AOP&#xff1a;面向切面编程&#xff0c;利用AOP可以对业务逻辑的各个部分进行隔离&#xff0c;从而使得业务逻辑各个部分之间的耦合度降低&#xff0c;提高程序的可重用性&#xff0c;同时提高开发效率&#xff08;不通过修改源代码方式&#xff0c;在主…

深入跨域问题(2) - 利用 CORS 解决跨域

目录 1.搭建跨域环境(先展示一下跨域请求的情况)&#xff1a; 2.处理非预请求 3.处理 POST 预请求 4.总结&#xff1a; 1.搭建跨域环境(先展示一下跨域请求的情况)&#xff1a; 模拟客户端请求&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <…

客快物流大数据项目(一百零七):物流信息查询服务接口开发解决方案

文章目录 物流信息查询服务接口开发解决方案 一、业务需求

探究数据库mysql的vachar、test、longtext存储极限

文章目录背景介绍项目实操如果想要自己尝试&#xff0c;使用Apipost工具&#xff0c;调用接口测试即可mysql类型如果感觉有点意思点个关注&#xff0c;一键三连吧&#xff01;蟹蟹&#xff01;&#xff01;&#xff01;背景 想要清晰的了解到&#xff0c;使用longtext类型&…

C++:类的构造函数与析构函数

目录 一.前言 二.类的构造函数 1.构造函数基本概念与语法细则 2.编译器默认生成的无参构造函数和自定义构造函数 3.构造函数的特性(可重载) 4.关于构造函数的注意事项 5.构造函数的应用示例&#xff1a; 三.类的拷贝构造函数 1.拷贝构造函数基本概念 2.编译器默认生成…

零入门容器云实战之文章目录列表

建议: 1、网盘资源 零入门容器云网络实战 链接: https://pan.baidu.com/s/1nPLRkAwjItAHmtEU2T1F4g 提取码: rrpd 2、技术交流群 QQ群&#xff1a; 342498897 3、发布说明 绿色字体&#xff0c; 表示已经发布&#xff0c;可以观看 灰色字体&#xff0c; 表示未发布 发布频…

汽车研究(科普)

什么是汽车的排量&#xff0c;1.5L与2.0T又是指什么? 汽车的动力来源于燃油在气缸内爆燃产生的力&#xff0c;力推动活塞连着曲轴传到离合变速箱&#xff0c;通过后桥作用让车轮转&#xff0c;排量1.5、2.0指的就是气缸的容量&#xff0c;如果是带增压的用字母T表示&#xff0…