likes
comments
collection
share

看完springboot拦截器源码,发现百万阅读文章竟是错误的

作者站长头像
站长
· 阅读数 16

[TOC]

前言

在研究拦截器源码的时候看到一篇百万阅读的文章,看完源码发现这篇文章有个点不太对。 看完springboot拦截器源码,发现百万阅读文章竟是错误的 这里给大家框出来了,看看哪一句不对劲呢? 注:我看的源码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());
    }
}

随意写一个接口进行调用,查看日志: 看完springboot拦截器源码,发现百万阅读文章竟是错误的

可以归纳为先进后出的策略。 先进指的是preHandle按拦截器顺序执行,其他按倒序执行。

为什么?

经过debug之后找到了源码执行的地方。 看完springboot拦截器源码,发现百万阅读文章竟是错误的 从图中圈出来的点可以清晰的看到,拦截器在什么时候会调用。

preHandle的执行

源码中是先调用applyPreHandle()方法,执行拦截器的前置拦截,再看下前置如何处理的。 看完springboot拦截器源码,发现百万阅读文章竟是错误的 其实就是遍历所有的拦截器一一调用前置方法,如果返回false则再触发afterCompletion拦截方法。

呐呐,源码就在这里写着了,开头那篇文章怎么说的

返回false不会触发afterCompletion。

目前看这是不完全对的。为什么呢? 在某些条件下确实是执行不了afterCompletion的,这里要注意后面一段代码。

this.interceptorIndex = i

这里将已经执行的过位置赋值给了这个变量,这个变量实际上是会影响afterCompletion的执行。

后面看afterCompletion的实现时我们再讲。

postHandle的执行

可以看到源码中第三个框的地方,这里就是后置拦截postHandle的执行位置。 看完springboot拦截器源码,发现百万阅读文章竟是错误的 这里可以看到在遍历的时候是先从最后一个开始的,然后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执行的位置。 看完springboot拦截器源码,发现百万阅读文章竟是错误的 看一下它的内部代码怎么实现的: 看完springboot拦截器源码,发现百万阅读文章竟是错误的 这里有一点不同,这边是按照interceptiorIndex的位置开始执行的。

在前面preHandle的时候我们注意到了这个变量,它决定afterCompletion能不能运行。

正常情况下,所有的拦截器都运行了preHandle那么这里的变量就是最后一个拦截器的位置。

假设是5个拦截器,那么正常这个变量就为4,再按照倒序执行afterCompletion方法。

假设第一个拦截器的preHandle成功,第二个返回false了,那么这里还是能执行第一个拦截器的afterCompletion方法的。

所以说上面那篇百万阅读的文章不完全正确,有兴趣可以自己验证一下,这里我贴一下我验证的结果:

看完springboot拦截器源码,发现百万阅读文章竟是错误的 第一个拦截器成功执行了afterCompletion方法,至此破案了。

总结

拦截器是spring提供的一种对于请求的前后及完成状态的钩子,用于开发自定义处理。

它有3个常用的钩子,分别是:

  • preHandle:请求处理之前的钩子,按拦截器顺序执行。
  • postHandle:请求完成之后的钩子,按拦截器倒序执行。
  • afterCompletion:原本用于视图渲染之后调用的,现在来说用于请求不管是否异常后的清场工作。,倒序执行。

注意:preHandle并不是返回false就一定不会触发afterCompletion钩子。

preHandle成功了几个就必然会执行几个afterCompletion,如果存在必须要执行的afterComletion方法的话一定要确保它前面和自己的拦截器成功执行。

ps:看啥文章都不如自己实际走一遍来的好,如果有帮助还请三连。。。