过滤器 和 拦截器 的区别

2023-11-01

1、过滤器(Filter)

过滤器配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,
看到Filter 接口中定义了三个方法:
- init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。
  注意:这个方法必须执行成功,否则过滤器会不起作用。
- doFilter() :容器中的每一次请求都会调用该方法,FilterChain 用来调用下一个过滤器Filter。
- destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,
  在过滤器 Filter 的整个生命周期也只会被调用一次。
 

@Component
public class MyFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 处理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

        System.out.println("Filter 后置");
    }
}

2、拦截器(Interceptor)

拦截器是链式调用,一个应用中可以同时存在多个拦截器Interceptor, 一个请求也可以触发多个拦截器,而每个拦截器的调用会  依据它的声明顺序依次执行


首先编写一个简单的拦截器处理类,请求的拦截是通过 HandlerInterceptor 来实现,
看到HandlerInterceptor 接口中也定义了三个方法:
- preHandle() :这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false,
  将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
- postHandle():只有在 preHandle() 方法返回值为true 时才会执行。
  会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 
  有意思的是:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,
  先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
- afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。
  在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。

@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("Interceptor 处理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("Interceptor 后置");
    }
}

将自定义好的拦截器处理类进行注册,并通过addPathPatternsexcludePathPatterns等属性设置需要拦截或需要排除的 URL

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

3、过滤器 和 拦截器 的区别

过滤器拦截器 均体现了 AOP的编程思想,都可以实现诸如 日志记录登录鉴权等功能。

3.1 实现原理不同

过滤器 是基于 函数回调 实现的;拦截器 是基于Java的 反射机制(动态代理)实现的。

重点说下过滤器:

在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,
而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 
doFilter() 方法就是回调方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

 

ApplicationFilterChain里面能拿到我们自定义的xxxFilter类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter过滤器,并执行 doFilter() 方法。 

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //获取第pos个filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

而每个xxxFilter 会先执行自身的 doFilter() 过滤逻辑,最后在执行结束前会执行
filterChain.doFilter(servletRequest, servletResponse),也就是回调ApplicationFilterChain
的doFilter() 方法,以此循环执行实现函数回调。

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    filterChain.doFilter(servletRequest, servletResponse);
}

3.2 使用范围不同

过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。

拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于ApplicationSwing等程序中。

3.3 触发时机不同

过滤器 和 拦截器的触发时机也不同,我们看下边这张图:

过滤器 Filter 是在请求进入容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。

拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

3.4 拦截的请求范围不同

在上边我们已经同时配置了过滤器和拦截器,再建一个Controller接收请求测试一下。 

@Controller
@RequestMapping()
public class Test {

    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
        System.out.println("我是controller");
        return null;
    }
}

项目启动过程中发现,过滤器的init()方法,随着容器的启动进行了初始化。 

此时浏览器发送请求,F12 看到居然有两个请求,一个是我们自定义的 Controller 请求,另一个是访问静态图标资源的请求。 

看到控制台的打印日志如下:

执行顺序 :Filter 处理中 -> Interceptor 前置 -> 我是controller -> Interceptor 处理中 -> Interceptor 处理后

Filter 处理中
Interceptor 前置
Interceptor 处理中
Interceptor 后置
Filter 处理中

过滤器Filter执行了两次,拦截器Interceptor只执行了一次。这是因为过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对Controller中请求或访问static目录下的资源请求起作用。 

3.5 注入Bean情况不同

在实际的业务场景中,应用到过滤器或拦截器,为处理业务逻辑难免会引入一些service服务。下边我们分别在过滤器和拦截器中都注入service,看看有什么不同?

@Component
public class TestServiceImpl implements TestService {

    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是方法A”。 

@Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 处理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }
Filter 处理中
我是方法A
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor 后置

 这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。 

拦截器:老子今天要进洞房;
Spring:兄弟别闹,你媳妇我还没生出来呢!

解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。注意:在registry.addInterceptor()注册的是getMyInterceptor() 实例。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

3.6 控制执行顺序不同

实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。

过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。

postHandle() 方法被调用的顺序跟 preHandle() 居然是相反的!如果实际开发中严格要求执行顺序,那就需要特别注意这一点。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 处理中
Interceptor2 处理中
Interceptor1 处理中
Interceptor 后置
Interceptor2 处理后
Interceptor1 处理后

那为什么会这样呢? 得到答案就只能看源码了,我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()preHandle()方法便是在其中调用的。 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        try {
         ...........
            try {
           
                // 获取可以执行当前Handler的适配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 执行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看两个方法applyPreHandle()applyPostHandle()具体是如何被调用的,就明白为什么postHandle()preHandle() 执行顺序是相反的了。 

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }

发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的。。。,导致postHandle()preHandle() 方法执行的顺序相反。 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

过滤器 和 拦截器 的区别 的相关文章

随机推荐

  • postgresql jdbc连接参数

    jdbc postgresql 192 168 1 23 12308 test useUnicode true amp characterEncoding gbk amp allowEncodingChanges true 见官网 http
  • 2023-2030老龄化(>=65)比率预测模型

    目录 前言 一 数据来源和处理 二 相关性检验 图形说明 相关矩阵 三 LSTM算法预测 四 BP神经网络预测 前言 如何预测未来八年老龄化比率 作者从以下四个角度出发 GDP 医院数 医院包括综合医院 中医医院 中西医结合医院 民族医院
  • 1_有关 01背包问题 和 完全背包问题 的详解

    目录 一 01背包问题 物品仅可选一次 1 题目 2 版本1 题解代码 代码 详解 3 版本2 题解代码 优化为一维 代码 详细 4 版本3 题解代码 优化输入 优选 代码 详解 二 完全背包问题 物品可选无限次 1 题目 2 基础版本1
  • OpenCV——中值滤波

    目录 一 原理概述 二 C 代码 三 结果展示 1 原图 2 3x3滤波 3 9x9滤波 四 python代码 一 原理概述 中值滤波 Median Filter 是一种非线性滤波技术 其基本思想是在单通道中将像素点邻域的灰度值进行排序 取
  • 启明云端分享

    1 Sstar System Tool说明 软件开发人员访问SigmaStar芯片寄存器 必须使用Debug Tool硬件工具和Sstar System Tool软件工具 Debug Tool硬件工具 如图所示 使用USB延长线连接PC机
  • 设计模式-策略模式

    策略模式是一种行为型设计模式 其主要目的是允许在运行时选择算法的行为 在Java中 我们可以使用策略模式来根据不同的条件动态地选择不同的算法 下面是一个示例代码 展示了如何在Controller中确定是什么策略 以及如何调用相应的Servi
  • php函数漏洞

    1 intval intval 获取变量的整数值 说明 int intval mixed var int base 10 通过使用指定的进制 base 转换 默认是十进制 返回变量 var 的 integer 数值 intval 不能用于
  • 区块链面临的挑战(一)

    id BSN 2021 公众号 BSN研习社 分布式存储 加密算法 共识机制 具有这些典型技术特征的区块链技术自一诞生 就被诸多行业看好 蕴含巨大的潜力 从实践进展来看 区块链技术在商业银行的应用大部分仍在构想和测试之中 距离在生活 生产中
  • 来自美团资深技术专家亲笔的400页的高并发系统设计,近50个核心难点,让你面试直接起飞!

    前言 在现今IT界特别是程序员 如果你对于高并发都没有接触和了解过未免也有些太孤陋寡闻 而作为一个优秀的程序员 高并发系统架构设计师必须要掌握的 很简单 现在大多数互联网公司都会用到高并发系统架构设计 像常见的 秒杀活动 抢红包 微博热搜
  • 【JAVAWEB开发】基于Java+Servlet+Ajax+jsp网上购物系统设计实现

    哈喽 大家好呀 这篇给的大家带来的是网上购物系统设计 在传统电商时代 用户是先有需求再购买 用户对平台较为依赖 商家对消费者很难有直接的影响力 而如今社交 电商解决了产品质量的信息不对称问题 电商已经成为当今经济发展的一个重要领域 而网上购
  • 一张图看明白GPU原理

    GPU直通实现方式 通过虚拟化平台的直通技术可以将显卡直接给虚拟机使用 与物理机接入显卡效果基本一致 在询价上只要安装了对应显卡的显示驱动 显卡就可以为这个虚拟机提供高性能的图形能力 GPU虚拟化 共享能够将一个物理存在的显卡分享给多个虚拟
  • QPainterPath全功能解锁

    QPainterPath可以自动计算bounding和shap 前者决定了重绘区域 后者决定了碰撞边界 可以说 QPainterPath是绘制的最优解之一 但QPainterPath内并未直接提供缩放 旋转等功能 很多人借助QPainter
  • Qt QList和QLinkedList使用

    文章目录 1 QList 1 1 链表基础使用 添加 修改 查找 删除 1 2 迭代器使用 STL风格 Java风格 2 QLinkedList 1 QList 1 1 链表基础使用 添加 修改 查找 删除 链表初始化 添加元素 QList
  • 2022第三届全国大学生网络安全精英赛练习题(6)

    全国大学生网络安全精英赛 2022第三届全国大学生网络安全精英赛练习题 6 文章目录 全国大学生网络安全精英赛 2022第三届全国大学生网络安全精英赛练习题 6 总结 501 下列有关代理服务器说法错误的是 A 代理服务器访问模式是浏览器不
  • sort函数与结构体

    include
  • Java高级编程实验_java高级编程项目实践.ppt

    java高级编程项目实践 ppt 由会员分享 可在线阅读 更多相关 java高级编程项目实践 ppt 32页珍藏版 请在人人文库网上搜索 1 Java高级编程项目实践 徐铭 课程目录 第一部分 需求定义 第二部分 用户界面设计 第三部分 数
  • 停止开发GPT-4?我更加关注数据版权、信息安全和数字鸿沟问题

    近日 随着ChatGPT和GPT 4的迅猛发展 人工智能对于人类社会以及文明的影响将是我们需要重视的问题 有人认为ChatGPT的表现引人入胜 但同时也让人感到毛骨悚然 因此 AI是否可靠 是否会导致灾难 机器智能超过人类的 奇点 是否真正
  • 公共IPV6 dns大全

    dns是什么和公共ipv4可阅读本篇文章 dns大全 一 阿里ipv6 dns 阿里的dns好在于自家的服务器遍布全球 加上自家研究的CDN技术快稳定 强大的阿里云团队技术坚持也是国内首家支持IPv4和IPv6 双端加持 安全快速 2400
  • js数组常见操作方法总结

    0 将数组中所有name改成ChName Name改成EnName var arr1 name aa Name ss children name ww Name nn name ff Name ee let arr2 JSON parse
  • 过滤器 和 拦截器 的区别

    1 过滤器 Filter 过滤器配置比较简单 直接实现Filter 接口即可 也可以通过 WebFilter注解实现对特定URL拦截 看到Filter 接口中定义了三个方法 init 该方法在容器启动初始化过滤器时被调用 它在 Filter