看完springboot拦截器源码,发现百万阅读文章竟是错误的
[TOC]
前言
在研究拦截器源码的时候看到一篇百万阅读的文章,看完源码发现这篇文章有个点不太对。 这里给大家框出来了,看看哪一句不对劲呢? 注:我看的源码springboot版本为2.2.10.RELEASE
例子
创建2个拦截器,按顺序添加进拦截链中。
public class FirstInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor 执行 preHandle--------------------");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor 执行 postHandle--------------------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor 执行 afterCompletion--------------------");
}
}
public class SecondInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SecondInterceptor 执行 preHandle--------------------");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("SecondInterceptor 执行 postHandle--------------------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("SecondInterceptor 执行 afterCompletion--------------------");
}
}
添加进链:
@Configuration
public class WebConfiguer extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new FirstInterceptor());
registry.addInterceptor(new SecondInterceptor());
}
}
随意写一个接口进行调用,查看日志:
可以归纳为先进后出的策略。 先进指的是preHandle按拦截器顺序执行,其他按倒序执行。
为什么?
经过debug之后找到了源码执行的地方。 从图中圈出来的点可以清晰的看到,拦截器在什么时候会调用。
preHandle的执行
源码中是先调用applyPreHandle()方法,执行拦截器的前置拦截,再看下前置如何处理的。 其实就是遍历所有的拦截器一一调用前置方法,如果返回false则再触发afterCompletion拦截方法。
呐呐,源码就在这里写着了,开头那篇文章怎么说的
返回false不会触发afterCompletion。
目前看这是不完全对的。为什么呢? 在某些条件下确实是执行不了afterCompletion的,这里要注意后面一段代码。
this.interceptorIndex = i
这里将已经执行的过位置赋值给了这个变量,这个变量实际上是会影响afterCompletion的执行。
后面看afterCompletion的实现时我们再讲。
postHandle的执行
可以看到源码中第三个框的地方,这里就是后置拦截postHandle的执行位置。 这里可以看到在遍历的时候是先从最后一个开始的,然后i--依次执行,这里也就解释了为什么后置拦截postHandle是倒序执行了。 但是为什么要这样设计呢? 我找了下作者的备注:
* DispatcherServlet processes a handler in an execution chain, consisting
* of any number of interceptors, with the handler itself at the end.
* With this method, each interceptor can post-process an execution,
* getting applied in inverse order of the execution chain
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
翻译之后也只是说使用这个方法可以进行后处理,以执行链相反的顺序执行,并没有解释说为什么这样设计,大家有兴趣可以继续深挖一下。
afterCompletion的执行
现在我们知道了前置和后置是怎么执行的了,那么afterCompletion在什么时候调用呢?
细心一下看源码图其实可以看到最后一个红框的地方,在这里就是afterCompletion执行的位置。 看一下它的内部代码怎么实现的: 这里有一点不同,这边是按照interceptiorIndex的位置开始执行的。
在前面preHandle的时候我们注意到了这个变量,它决定afterCompletion能不能运行。
正常情况下,所有的拦截器都运行了preHandle那么这里的变量就是最后一个拦截器的位置。
假设是5个拦截器,那么正常这个变量就为4,再按照倒序执行afterCompletion方法。
假设第一个拦截器的preHandle成功,第二个返回false了,那么这里还是能执行第一个拦截器的afterCompletion方法的。
所以说上面那篇百万阅读的文章不完全正确,有兴趣可以自己验证一下,这里我贴一下我验证的结果:
第一个拦截器成功执行了afterCompletion方法,至此破案了。
总结
拦截器是spring提供的一种对于请求的前后及完成状态的钩子,用于开发自定义处理。
它有3个常用的钩子,分别是:
- preHandle:请求处理之前的钩子,按拦截器顺序执行。
- postHandle:请求完成之后的钩子,按拦截器倒序执行。
- afterCompletion:原本用于视图渲染之后调用的,现在来说用于请求不管是否异常后的清场工作。,倒序执行。
注意:preHandle并不是返回false就一定不会触发afterCompletion钩子。
preHandle成功了几个就必然会执行几个afterCompletion,如果存在必须要执行的afterComletion方法的话一定要确保它前面和自己的拦截器成功执行。
ps:看啥文章都不如自己实际走一遍来的好,如果有帮助还请三连。。。