源码分析Spring boot拦截器执行顺序
Posted 爱上口袋的天空
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码分析Spring boot拦截器执行顺序相关的知识,希望对你有一定的参考价值。
一、提出问题
- 项目中存在多个拦截器,那么他们的执行顺序是如何的?
- 如何设置拦截器执行顺序?
二、前期准备
项目结构:
主要代码如下,有拦截器 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拦截器执行顺序的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot 统一功能处理(用户登录权限效验-拦截器异常处理数据格式返回)