【Java】过滤器和拦截器区别

news2024/12/22 20:12:42

文章目录

  • 前言
  • 1、过滤器 (Filter)
  • 2、拦截器 (Interceptor)
  • 3、我们不一样
    • 3.1、实现原理不同
    • 3.2、使用范围不同
    • 3.3、触发时机不同
  • 4、拦截的请求范围不同
  • 5、注入Bean情况不同
  • 6、控制执行顺序不同
  • 总结

前言

准备环境
我们在项目中同时配置 拦截器 和 过滤器。

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 后置");
    }
}

将自定义好的拦截器处理类进行注册,并通过addPathPatterns、excludePathPatterns等属性设置需要拦截或需要排除的 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程序中,也可以用于Application、Swing等程序中。

在这里插入图片描述

3.3、触发时机不同

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

在这里插入图片描述

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

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

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目录下的资源请求起作用。

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 后置

在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊?
在这里插入图片描述

这是因为加载顺序导致的问题,拦截器加载的时间点在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("/**");
    }
}

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() 执行顺序是相反的了。

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

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

总结

我相信大部分人都能熟练使用滤器和拦截器,但两者的差别还是需要多了解下,不然开发中使用不当,时不时就会出现奇奇怪怪的问题,以上内容比较简单,新手学习老鸟复习,有遗漏的地方还望大家积极补充,如有理解错误之处,还望不吝赐教。

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

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

相关文章

Nginx反向代理联动Tomcat实现多实例部署、动静分离、负载均衡

文章目录 1. 配置反向代理1.1 前置准备1.2 代理服务器配置1.3 真实服务器配置1.4 客户机配置 2. Tomcat 多实例部署2.1 部署JDK2.2 设置JDK环境变量2.3 部署Tomcat服务2.4 路径启动 3. Nginx联动Tomcat实现动静分离、负载均衡3.1 基本原理3.2 前置准备3.3 配置nginx1实现四层代…

2023.9.8 基于传输层协议 UDP 和 TCP 编写网络通信程序

目录 UDP 基于 UDP 编写网络通信程序 服务器代码 客户端代码 TCP 基于 TCP 编写网络通信程序 服务器代码 客户端代码 IDEA 打开 支持多客户端模式 UDP 特点&#xff1a; 无连接性&#xff1a;发送端和接收端不需要建立连接也可相互通信&#xff0c;且每个 UDP 数据包都…

sql注入基本概念

死在山野的风里&#xff0c;活在自由的梦里 sql注入基本概念 MYSQL基本语法union合并查询2个特性&#xff1a;order by 排序三个重要的信息 Sql Server MYSQL 基本语法 登录 mysql -h ip -u user -p pass基本操作 show databases; 查看数据库crea…

串行数据发送器

框图 THR&#xff1a;发送保持寄存器 定义了两种状态&#xff1a;空&#xff0c;满数据写入端口地址&#xff1a;00H状态读出端口地址&#xff1a;00H当THR不满时&#xff0c;可以向THR写入数据 TSR&#xff1a;发送移位寄存器 一旦TSR空而THR中有数据时&#xff0c;THR中的数…

Ubuntu服务器安装Nvidia显卡驱动各类失败问题的解决方案集合

前言 给实验室服务器安装显卡驱动&#xff0c;总是遇到各种各样的问题。故而专门开一个文章记录一下遇到的各类问题。 正常安装方法 在这里安装CUDA&#xff0c;选择最新版本后根据系统配置点选即可&#xff0c;会自动生成对应的链接&#xff0c;如下图。这里选runfile&…

Mysql join用法详解

本篇文章旨在详细讲解Mysql 中join的用法&#xff0c;并附上例题。 一. left join 首先附上图 这个查询语句最根本的是要找出A表中所有的行&#xff0c; 所以如图所示&#xff0c;A表整个被涂蓝 A与B交叉的那部分可以视为ON后所跟的条件 重点是&#xff1a; 如果A表中有一条…

C#学习 - 方法的定义、调用、调试

方法 方法&#xff08;Method&#xff09;是由C/C中的函数&#xff08;Function&#xff09;发展而来的 //C语言 #include <stdio.h> int Add(int x, int y) {return x y; }//函数 int main(void) {int a 4;int b 2;int c Add(a, b);printf("%d %d %d\n&quo…

mysql 删除索引 索引长度还在,数据内存 索引内存没有减少

在我们针对数据表进行了大量删除或者删除掉某个无用的索引之后&#xff0c;该表占的数据内存或者索引并没有明显的变换&#xff0c;这是因为删除操作后在数据文件中留下碎片所致。 查看数据与索引 SELECTTABLE_NAME,concat( TRUNCATE ( data_length / 1024 / 1024, 2 ), MB …

腾讯云4核8G服务器CVM S5性能测评(CPU/流量/系统盘)

腾讯云4核8G服务器CVM标准型S5实例性能测评&#xff0c;包括CPU型号、内存、系统盘、CVM实例规格性能测评&#xff0c;腾讯云4核8G租用优惠价格表&#xff0c;腾讯云服务器网分享腾讯云4核8G服务器CVM S5性能测评和租用费用&#xff1a; 目录 腾讯云4核8G服务器CVM S5性能测评…

【C++】简单理解:将整数(浮点数)转换为字符串(string),将字符串(string)转换为整数(浮点数)方法

用stringstream类&#xff0c;口诀&#xff1a;过滤一下就转化 头文件#include<sstream> 例子&#xff1a;将整数12和浮点数12.34转化为字符串 int main() {int x 12;double d 12.34;string s;//创建一下对象strstringstream str;//过滤一下就转化str << x;st…

分享芯片行业有趣的小知识

随着拜登签署《芯片与科学家法案》&#xff0c;全球芯片争夺战进一步升温&#xff0c;我国芯片市场再次面临霸权主义的挑战。尽管困难重重&#xff0c;我们决不会停止在芯片领域的探索与发展&#xff0c;这彰显了芯片对国家的至关重要性。除了其重要性&#xff0c;芯片还有许多…

矿山边坡安全监测及预警系统解决方案

1.建设背景 近年来&#xff0c;矿山安全问题一直受到国家和社会的高度关注。为了全面提升矿山安全生产水平&#xff0c;国家矿山安全监察局和各省级非煤矿山安全监管部门开展了一项重大举措&#xff1a;推广并实施露天矿山边坡监测系统。 矿山边坡和排土场安全是露天矿山安全生…

vs2019配置sfml外部库出现的问题

问题描述 在debug模式下程序正常运行&#xff0c;在release模式中报错 解决 C/C Windows环境下 boost 安装使用教程 Debug 通常称为调试版本&#xff0c;它包含调试信息&#xff0c;并且不作任何优化&#xff0c;便于程序员调试程序。 Release&#xff1a;Release通常称为发…

【HCIE】03.BGP高级特性

每一条BGP路由都可以携带多个路径属性&#xff0c;针对其属性也有特有的路由匹配工具&#xff0c;包括&#xff1a;AS Path Filter和Community Filter。 import方向的属性&#xff0c;出现在如策略里面&#xff0c;加入到BGP路由表中&#xff0c;再传给路由表里&#xff0c;出去…

产品波士顿矩阵

随着公司产品的增多&#xff0c;每个产品的生命周期节点各不相同&#xff0c;很多时候我们往往在产品结构、资源分配方面会产生各种问题&#xff0c;导致需要发展的产品得不到资源&#xff0c;消耗资源的产品却有无法增长&#xff0c;所谓不聚焦导致的问题其实是资源和发展错配…

RP9学习-1

一.基础 1.10个面板位置示意图&#xff1a; 2.常用英文 1.鼠标点击&#xff1a;click or tap 3.工作区 1.恢复默认工作区&#xff1a; view-->reset view 2.自定义工作区&#xff1a; 可以用鼠标左键拖动面板到独立的位置或者吸附到其他面板上 3.自定义工具栏 view-->T…

4核8G服务器腾讯云CVM S5性能如何?CPU型号及租用价格

腾讯云4核8G服务器CVM标准型S5实例性能测评&#xff0c;包括CPU型号、内存、系统盘、CVM实例规格性能测评&#xff0c;腾讯云4核8G租用优惠价格表&#xff0c;腾讯云服务器网分享腾讯云4核8G服务器CVM S5性能测评和租用费用&#xff1a; 目录 腾讯云4核8G服务器CVM S5性能测评…

让AI帮你做出回答-钉钉问答机器人来啦

什么是问答机器人 问答机器人是一种人工智能系统&#xff0c;它可以根据用户提出的问题&#xff0c;自动地搜索并提供相关的答案。这些机器人通常基于自然语言处理技术&#xff0c;可以理解用户提出的问题&#xff0c;并能够快速地给出答案。 问答机器人通常会在多个数据源中…

怎么给商品图片去除背景,制作商品白底图?

很多商家在上架新产品时平台都会要求上传一张白底图&#xff0c;上传商品白底图不仅可以更快速的通过资源位审核&#xff0c;还更容易获得平台的免费曝光机会&#xff01; 我们在编辑商品主图、上传商品素材图片的时候&#xff0c;经常需要用到白底图。但是很多朋友都不知道怎…