源码分析Spring boot拦截器执行顺序

Posted 爱上口袋的天空

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析Spring boot拦截器执行顺序相关的知识,希望对你有一定的参考价值。

一、提出问题

  1. 项目中存在多个拦截器,那么他们的执行顺序是如何的?
  2. 如何设置拦截器执行顺序?

二、前期准备

项目结构:

主要代码如下,有拦截器 A、B、C,代码基本与下一致:

/**
 * 拦截器 A
 *
 * @author ouyang
 * @version 1.0
 * @date 2020/7/30 15:18
 **/
public class AInterceptor implements HandlerInterceptor 

    private final Logger logger = LoggerFactory.getLogger(AInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
        logger.info("拦截器 A preHandle 方法执行");
        return true;
    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception 
        logger.info("拦截器 A postHandle 方法执行");
    

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception 
        logger.info("拦截器 A afterCompletion 方法执行");
    

 配置拦截器类:

/**
 * 拦截器配置
 * @author ouyang
 * @version 1.0
 * @date 2020/7/30 15:19
 **/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer 

    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(new AInterceptor())
                .addPathPatterns("/**");

        registry.addInterceptor(new BInterceptor())
                .addPathPatterns("/**");

        registry.addInterceptor(new CInterceptor())
                .addPathPatterns("/**");
    


三、拦截器实际运行结果 

首先启动项目,任意访问一个接口,控制台打印如下:

2020-07-31 21:43:55.891  INFO 4284 --- [nio-8080-exec-1] com.study.aop.AInterceptor               : 拦截器 A preHandle 方法执行
2020-07-31 21:43:55.891  INFO 4284 --- [nio-8080-exec-1] com.study.aop.BInterceptor               : 拦截器 B preHandle 方法执行
2020-07-31 21:43:55.891  INFO 4284 --- [nio-8080-exec-1] com.study.aop.CInterceptor               : 拦截器 C preHandle 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.CInterceptor               : 拦截器 C postHandle 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.BInterceptor               : 拦截器 B postHandle 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.AInterceptor               : 拦截器 A postHandle 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.CInterceptor               : 拦截器 C afterCompletion 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.BInterceptor               : 拦截器 B afterCompletion 方法执行
2020-07-31 21:43:55.936  INFO 4284 --- [nio-8080-exec-1] com.study.aop.AInterceptor               : 拦截器 A afterCompletion 方法执行

从中可以看出,拦截器 A,B,C 中 preHandle 执行顺序与 registry.addInterceptor() 一致postHandle 和 afterCompletion 反之


 四、源码分析拦截器顺序

接下来我们跟踪一下源码,分析为什么会是上述现象:
首先 WebMvcConfigurationSupport 类中 requestMappingHandlerMapping 方法会将配置好的拦截器通过 InterceptorRegistry 类中 getInterceptors() 方法加载到 RequestMappingHandlerMapping;另外在请求时,通过 DispatcherServlet 中的 doDispatch 方法进行调用拦截器

如下是 doDispatch 方法:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception 
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try 
            try 
                ModelAndView mv = null;
                Object dispatchException = null;

                try 
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) 
                        this.noHandlerFound(processedRequest, response);
                        return;
                    

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) 
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) 
                            return;
                        
                    
					// 执行拦截器的全部 preHandle 方法
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) 
                        return;
                    

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) 
                        return;
                    

                    this.applyDefaultViewName(processedRequest, mv);
                    // 执行拦截器的全部 postHandle 方法
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                 catch (Exception var20) 
                    dispatchException = var20;
                 catch (Throwable var21) 
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                
				
				// 执行拦截器的全部 afterCompletion 方法
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
             catch (Exception var22) 
                // 异常后,执行拦截器的全部 afterCompletion 方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
             catch (Throwable var23) 
                // 异常后,执行拦截器的全部 afterCompletion 方法
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            

         finally 
            if (asyncManager.isConcurrentHandlingStarted()) 
                if (mappedHandler != null) 
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                
             else if (multipartRequestParsed) 
                this.cleanupMultipart(processedRequest);
            

        
    

首先我们先来看 mappedHandler.applyPreHandle() 方法实际方法:

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;
    

我们可以看出 applyPreHandle 是按照 HandlerInterceptor[] interceptors = this.getInterceptors(); 顺序执行的,也就是说 默认(不设定拦截器顺序) 是按照 registry.addInterceptor() 的顺序执行的
接下来我们看 mappedHandler.applyPostHandle() ,实际执行方法如下:

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

    

可以看出是与 applyPreHandle 正好相反的顺序执行的;mappedHandler.triggerAfterCompletion() 方法如下所示:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception 
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) 
            for(int i = this.interceptorIndex; i >= 0; --i) 
                HandlerInterceptor interceptor = interceptors[i];

                try 
                    interceptor.afterCompletion(request, response, this.handler, ex);
                 catch (Throwable var8) 
                    logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
                
            
        
    

可以看出和 applyPostHandle 方法一致。

最后我们来看一下 InterceptorRegistry 类的 addInterceptor 和 getInterceptors 方法,源码如下:

public class InterceptorRegistry 
    private final List<InterceptorRegistration> registrations = new ArrayList();
    private static final Comparator<Object> INTERCEPTOR_ORDER_COMPARATOR;

    public InterceptorRegistry() 
    

    public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) 
        InterceptorRegistration registration = new InterceptorRegistration(interceptor);
        this.registrations.add(registration);
        return registration;
    

    public InterceptorRegistration addWebRequestInterceptor(WebRequestInterceptor interceptor) 
        WebRequestHandlerInterceptorAdapter adapted = new WebRequestHandlerInterceptorAdapter(interceptor);
        InterceptorRegistration registration = new InterceptorRegistration(adapted);
        this.registrations.add(registration);
        return registration;
    

    protected List<Object> getInterceptors() 
        return (List)this.registrations.stream().sorted(INTERCEPTOR_ORDER_COMPARATOR).map(InterceptorRegistration::getInterceptor).collect(Collectors.toList());
    

    static 
        INTERCEPTOR_ORDER_COMPARATOR = OrderComparator.INSTANCE.withSourceProvider((object) -> 
            if (object instanceof InterceptorRegistration) 
                InterceptorRegistration var10000 = (InterceptorRegistration)object;
                ((InterceptorRegistration)object).getClass();
                return var10000::getOrder;
             else 
                return null;
            
        );
    

不难看出, addInterceptor 方法是将拦截器放到 List<InterceptorRegistration> registrations = new ArrayList(); 中,另外需要特别注意 getInterceptors 方法,有代码 this.registrations.stream().sorted(INTERCEPTOR_ORDER_COMPARATOR),我们也可以看出 registrations 按照 order 升序排序,其中 InterceptorRegistration 类默认的 private int order = 0; ,可以通过 order(int order) 方法设置顺序


5、总结 

根据实践和源码分析,当项目中存在多个拦截器,那么他们的执行顺序是根据方法而定的,拦截器所有的 preHandle 方法是按照拦截器的 order 升序执行的如果 order 一致,则按照添加顺序执行postHandle 和 afterCompletion 方法执行顺序反之
另外我们可以通过使用 order(int order) 设定执行的先后顺序,如下代码所示。

@Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(new AInterceptor())
                .addPathPatterns("/**")
                .order(3);

        registry.addInterceptor(new BInterceptor())
                .addPathPatterns("/**")
                .order(2);

        registry.addInterceptor(new CInterceptor())
                .addPathPatterns("/**")
                .order(1);
    

综上所述,默认情况下(不设定拦截器顺序),preHandle 方法执行顺序与拦截器注册时顺序一致,postHandle 和 afterCompletion 方法执行顺序反之;如果我们给每个拦截器设定 order ,则 preHandle 方法执行顺序是按照 order 升序执行,postHandle 和 afterCompletion 方法执行顺序反之

以上是关于源码分析Spring boot拦截器执行顺序的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis拦截器的执行顺序引发的MyBatis源码分析

spring事物源码分析篇三:业务代码的执行--事物增强器

Spring Boot启动流程源码分析

Spring Boot 统一功能处理(用户登录权限效验-拦截器异常处理数据格式返回)

Spring源码剖析-Transactional 事务执行流程

头秃系列,二十三张图带你从源码分析Spring Boot 启动流程~