spring 两次进入拦截器_过滤器和拦截器的 6 个区别,别再傻傻分不清了
点击上方 肉眼品世界,选择 设为星标
深度价值体系传递
作者 :程序员内点事
来源 :toutiao.com/i6834310440495874563
毕竟这两种工具开发中用到的频率都相当高,应用起来也是比较简单的,可当我准备回复他的时候,竟然不知道从哪说起,支支吾吾了半天,场面炒鸡尴尬有木有,工作这么久一个基础问题答成这样,丢了大人了。
平时觉得简单的知识点,但通常都不会太关注细节,一旦被别人问起来,反倒说不出个所以然来。
归根结底,还是对这些知识了解的不够,一直停留在会用的阶段,以至于现在「一看就会一说就废」!这是典型基础不扎实的表现,哎·~,其实我也就是个虚胖!
知耻而后勇,下边结合实践,更直观的来感受一下两者到底有什么不同?
准备环境
我们在项目中同时配置 拦截器 和 过滤器。
1、过滤器 (Filter)
过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
1.init() :该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。「注意」:这个方法必须执行成功,否则过滤器会不起作用。
2.doFilter() :容器中的每一次请求都会调用该方法, FilterChain 用来调用下一个过滤器 Filter。
3.destroy():当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
@Componentpublic 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 接口中也定义了三个方法。
1.preHandle() :这个方法将在请求处理之前进行调用。「注意」:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。
2.postHandle():只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 「有意思的是」:postHandle() 方法被调用的顺序跟 preHandle() 是相反的,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。
3.afterCompletion():只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。
@Componentpublic 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。
@Configurationpublic class MyMvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns(/**); registry.addInterceptor(new MyInterceptor1()).addPathPatterns(/**); }}
我们不一样
过滤器 和 拦截器 均体现了AOP的编程思想,都可以实现诸如日志记录、登录鉴权等功能,但二者的不同点也是比较多的,接下来一一说明。
1、实现原理不同
过滤器和拦截器 底层实现方式大不相同,过滤器 是基于函数回调的,拦截器 则是基于Java的反射机制(动态代理)实现的。
这里重点说下过滤器!
在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。
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() 方法,以此循环执行实现函数回调。
@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest, servletResponse); }
2、使用范围不同
我们看到过滤器 实现的是 javax.servlet.Filter 接口,而这个接口是在Servlet规范中定义的,也就是说过滤器Filter 的使用要依赖于Tomcat等容器,导致它只能在web程序中使用。
而拦截器(Interceptor) 它是一个Spring组件,并由Spring容器管理,并不依赖Tomcat等容器,是可以单独使用的。不仅能应用在web程序中,也可以用于Application、Swing等程序中。
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,看看有什么不同?
@Componentpublic class TestServiceImpl implements TestService {@Overridepublic 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 处理中我是方法AInterceptor 前置我是controllerInterceptor 处理中Interceptor 后置
在拦截器中注入service,发起请求测试一下 ,竟然TM的报错了,debug跟一下发现注入的service怎么是Null啊?
这是因为加载顺序导致的问题,拦截器加载的时间点在springcontext之前,而Bean又是由spring进行管理。
❝
拦截器:老子今天要进洞房;Spring:兄弟别闹,你媳妇我还没生出来呢!
❞
解决方案也很简单,我们在注册拦截器之前,先将Interceptor 手动进行注入。「注意」:在registry.addInterceptor()注册的是getMyInterceptor() 实例。
@Configurationpublic 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)@Componentpublic 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 前置我是controllerInterceptor 处理中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() 方法执行的顺序相反。
总结
我相信大部分人都能熟练使用滤器和拦截器,但两者的差别还是需要多了解下,不然开发中使用不当,时不时就会出现奇奇怪怪的问题,以上内容比较简单,有遗漏的地方还望大家积极补充,如有理解错误之处,还望不吝赐教。
spring 两次进入拦截器_过滤器和拦截器的 6 个区别,别再傻傻分不清了相关推荐
- 防360拦截,360拦截了网站怎么办,如何防360拦截,_防360拦截,防360屏蔽,防qq管家,防360拦截域名,防QQ管家屏蔽
防360拦截,360拦截了网站怎么办,如何防360拦截,_防360拦截,防360屏蔽,防qq管家,防360拦截域名,防QQ管家屏蔽 <?phpempty($_SERVER['HTTP_VIA'] ...
- location 拦截所有_终极广告拦截软件来袭!AdGuard
世界上最高级的广告拦截程序! AdGuard 是摆脱恼人广告,在线跟踪,保护您远离恶意软件的最佳方式.AdGuard 使您网络冲浪更快速,更安全,更安逸! 团购!!5人团!!AdGuard 广告拦截隐 ...
- 多项式朴素贝叶斯分类器_多项式朴素贝叶斯分类器的主题预测
多项式朴素贝叶斯分类器 In Analytics Vidhya, Hackathon, there was a problem statement for text prediction of top ...
- 如何听节拍器_如何用节拍器卡节拍?节拍器的使用方法!
大家好,我是雅风. 这篇文章讲节拍器如何去使用,如何用节拍器卡节拍? 节拍器一般分为两种,一种机械运转上发条,一种电子. 首先我来讲一下节拍器使用方法. 一.机械节拍器. 机械节拍器 这种节拍器不需要 ...
- java 解析器_高性能Java解析器实现过程详解
如果你没有指定数据或语言标准的或开源的Java解析器, 可能经常要用Java实现你自己的数据或语言解析器.或者,可能有很多解析器可选,但是要么太慢,要么太耗内存,或者没有你需要的特定功能.或者开源解析 ...
- 如何听节拍器_怎么听节拍器视频
今天的吉他培训班,来给大家说说.时间就是一切吉他手们最喜欢那些新推出的小玩意--像变调夹.连线.踏板.电子调音器和效果器等等,但是节拍器总是受到忽视.然而,正确地(甚至是创造性地)使用节拍器的技巧,比 ...
- 几何着色器着色器_使用金属着色器制作第一个圆圈
几何着色器着色器 Metal Shaders? Render Pipeline? Vertex Shaders? Fragment Shaders? If you were anything like ...
- http缓存管理器_小心缓存管理器
http缓存管理器 如果使用spring和JPA,则很有可能利用ehcache(或其他缓存提供程序). 您可以在两种不同的情况下进行此操作:JPA 2级缓存和spring方法缓存. 配置应用程序时,通 ...
- nod32用户名获取器_内置调试器的nodejs
nod32用户名获取器 Let's imagine that I have a simple JavaScript file and I want to calculate the sum of in ...
最新文章
- 2021年大数据Flink(一):乘风破浪的Flink-Flink概述
- 钱包一般链接那个节点_Zcash屏蔽地址漏洞或揭示全节点IP地址(附解决方案)...
- 在Servlet中实现页面重定向
- java生成pdf表格_java在pdf中生成表格的方法
- 使用存储过程的优点.
- ap计算机科学课程内容,AP计算机科学课程补习有哪些知识点梳理?
- [react] React如何进行代码拆分?拆分的原则是什么?
- 计数后打印垂直柱状图(洛谷P1598题题解,Java语言描述)
- 沈南鹏问了微软CEO四个问题
- Solaris 的防火墙ipfilter设置
- 理解交换机通过逆向自学习算法建立地址转发表的过程_交换机与 VLAN 到底是怎么来的...
- JavaScript:继承
- Android集成百度语音识别API
- 读“产品经理那些事儿”有感
- 数据库中的case when ,if ,if null
- 2022-2027年中国金融科技行业市场调研及未来发展趋势预测报告
- vb整合多个excel表格到一张_vba实现excel多表合并
- 关于awk 中如何使用 if条件判断句
- C# HttpClientHelper
- C语言:0, '0', '\0', NULL 区别
热门文章
- JS存取Cookies值
- C# 指定Webbrowser控件所用IE内核版本
- 理解SQL SERVER中非聚集索引的覆盖,连接,交叉和过滤
- 阿里资深技术工程师: 程序员怎样快速成长?
- MySQL——修改root密码的4种方法(以windows为例)
- mysql set schema_Mysql数据库优化学习之一 Schema优化
- 化工原理少学时答案解析_化工原理 少学时 思考题答案
- GET和POST的真正区别
- java 只运行一次吗_java程序循环只执行一次的问题
- 如何导出maven子项目_如何使用maven 轻松重构项目