【Java】过滤器和拦截器区别
文章目录
- 前言
- 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 {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("Filter 前置");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("Filter 处理中");filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic 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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("Interceptor 前置");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("Interceptor 处理中");}@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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")@ResponseBodypublic 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 {@Overridepublic void a() {System.out.println("我是方法A");}
}
过滤器中注入service,发起请求测试一下 ,日志正常打印出“我是方法A”。
@Autowiredprivate TestService testService;@Overridepublic 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 {@Beanpublic MyInterceptor getMyInterceptor(){System.out.println("注入了MyInterceptor");return new MyInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");}
}
6、控制执行顺序不同
实际开发过程中,会出现多个过滤器或拦截器同时存在的情况,不过,有时我们希望某个过滤器或拦截器能优先执行,就涉及到它们的执行顺序。
过滤器用@Order注解控制执行顺序,通过@Order控制过滤器的级别,值越小级别越高越先执行。
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {
拦截器默认的执行顺序,就是它的注册顺序,也可以通过Order手动设置控制,值越小越先执行。
@Overridepublic 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() 方法执行的顺序相反。
总结
我相信大部分人都能熟练使用滤器和拦截器,但两者的差别还是需要多了解下,不然开发中使用不当,时不时就会出现奇奇怪怪的问题,以上内容比较简单,新手学习老鸟复习,有遗漏的地方还望大家积极补充,如有理解错误之处,还望不吝赐教。
相关文章:
【Java】过滤器和拦截器区别
文章目录 前言1、过滤器 (Filter)2、拦截器 (Interceptor)3、我们不一样3.1、实现原理不同3.2、使用范围不同3.3、触发时机不同 4、拦截的请求范围不同5、注入Bean情况不同6、控制执行顺序不同总结 前言 准备环境 我们在项目中同时配置 拦截器 和 过滤器。 1、过滤器 (Filter…...
es滚动查询分析和使用步骤
ES在进行普通的查询时,默认只会查询出来10条数据。我们通过设置es中的size可以将最终的查询结果从10增加到10000。如果需要查询数据量大于es的翻页限制或者需要将es的数据进行导出又当如何? Elasticsearch提供了一种称为"滚动查询"(…...
飞书公式总结
文本处理函数 飞书中的表格,需要实现 内容内容1条件1内容2条件2内容3条件3今天是周一;可以出去玩今天是周一能今天天气不错不能可以出去玩能 条件1,2,3为能时,把对应的内容1,内容2,内容3复制到内容 TEX…...
vue3.2 导出pdf文件或表格数据
要在Vue 3中导出PDF文件,你可以使用第三方库来处理PDF生成和导出。一个常用的库是jspdf,它允许你在客户端或服务器端生成PDF文档。 以下是在Vue 3中使用jspdf库导出PDF文件的基本步骤: 第一步首先,安装jspdf库。你可以使用npm或ya…...
Linux学习--MySQL学习之查询语句
所有实现基于mysql8.0.26实现,事例代码 1:常用函数 字符函数数学函数日期函数聚集函数数学计算if函数case函数 函数:MySQL服务内置命令 语法:函数名(表头名) select格式 SELECT 函数(表头名) FROM 库名.表名; SE…...
三、视频设备的枚举以及插拔检测
一、前言 本章主要讲述,如何获取设备名称以及guid,采集设备的采集格式识别,设备的插拔 设备列表以及属性的获取使用的directshow(后续的MediaFoundation无法获取OBS摄像头) 设备的插拔使用的是QT 捕获系统消息,捕获到设备插拔后&a…...
Qt开发_调用OpenCV(4.x)完成人脸检测并绘制马赛克(摄像头实时数据)
一、前言 这个基于Qt和OpenCV的人脸检测和人脸打码项目是通过实时视频流中的人脸识别来保护隐私。 该项目目的是保护隐私并确保人脸数据安全。在某些情况下,使用实时视频流进行人脸检测和识别可能涉及对个人隐私的侵犯。通过在图像中打码人脸区域,可以避免未经许可的人脸出…...
ssl证书有效期为什么越来越短?
随着互联网的发展和网络安全意识的提升,SSL证书在保护网络通信安全方面起到了极为重要的作用。SSL证书通过加密通信数据,确保信息在传输过程中不被窃取或篡改,为用户提供了安全可靠的网络环境。然而,近年来SSL证书的有效期不断缩短…...
XFF漏洞利用([SWPUCTF 2021 新赛]Do_you_know_http)
原理 常见的存在HTTP头注入的参数 User-Agent:使得服务器能够识别客户使用的操作系统,浏览器版本等.(很多数据量大的网站中会记录客户使用的操作系统或浏览器版本等存入数据库中) Cookie:网站为了辨别用户身份进行se…...
Java——》JVM对原生的锁做了哪些优化
推荐链接: 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…...
华为云云耀云服务器L实例评测|用docker搭建frp服务测试
华为云云耀云服务器L实例评测|用docker搭建frp服务测试 0. 环境 华为云耀云L实例EulerOS 1. 安装docker 检查yum源,本EulerOS的源在这里: cd /etc/yum.repos.d 更新源 yum makecache 安装 yum install -y docker-engine 运行测试 d…...
群狼调研(长沙满意度调查专业公司)开展公交车乘客满意度调查
在城市的喧嚣中,公交车是我们日常出行的重要工具。那么,公交车的服务是否满足我们的期待?它还有哪些改进的空间?在这篇文章中,群狼调研**(长沙汽车制造商满意度调查)**通过一个深入的公交车乘客满意度调查,为您解答这些问题。 …...
Spring与OAuth2:实现第三方认证和授权的最佳实践
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
Mysql的定时备份与恢复
MySQL定时备份与还原 MySQL备份方案 物理备份:这种备份方式将数据库的实际文件复制到另一个位置。常见的物理备份工具包括mysqldump和Percona XtraBackup。具体操作如下: 使用mysqldump 命令备份数据库: mysqldump -u username -p database_…...
学习Java基础面试题第五天
一、Java面试题第五天 1.spring字符串的最大长度 转自:String字符串的最大长度是多少?在学习和开发过程中,我们经常会讨论 short ,int 和 long 这些基本数据类型的取值范围,但是对于 String 类型我们好像很少注意它的…...
(10)(10.9) 术语表(一)
文章目录 前言 1 2.4Ghz 2 AGL 3 AHRS 4 APM 5 AMA 6 Arduino 7 APM (AutoPilot Mega) 8 ATC 9 Copter 10 Plane 11 Rover 12 BEC 13 Bootloader 14 COA 15 DCM 16 Eagle file 17 ESC 18 Firmware 19 FPV 20 FTDI 前言 !Note 术语表未编入索…...
面试(类加载器)
一、目标 类加载器(ClassLoader)是Java虚拟机(JVM)的一部分,用于加载Java类文件到内存中,并生成对应的Class对象。类加载器负责在运行时查找和加载类文件,为Java程序提供动态加载和运行时扩展的…...
二维差分---基础算法
书接上回 a二维数组是b二维数组的前缀和数组,b二维数组是a二维数组的差分数组,也就是说a[i][j]b[1][1]b[1][2] ......b[i][1] b[i][2] ...... b[i][j] ,下图是b的二维数组 如图,当你想要整个矩阵中的一个子矩阵都加上一个C,如果我们将b[x1][x2]加上C,那么a数组右下角所有的…...
C++之结构体智能指针shared_ptr实例(一百九十四)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...
初出茅庐的小李博客之根据编译时间生成软件版本号
为什么要软件版本号呢? 生成软件版本号是在软件开发和维护过程中非常重要的一项任务,它有很多意义和好处,同时也有多种常见的方法。 标识和追踪:软件版本号是唯一的标识符,用于区分不同版本的软件。这有助于开发人员和…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
