在使用SpringMVC拦截器的时候,我们接触的最多的便是HandlerInterceptor接口,因为我们所有的自定义拦截器都必须要实现HandlerInterceptor接口,那么就先从HandlerInterceptor接口开始一步步分析。

一、HandlerInterceptor接口

包含三个方法:

default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {}

根据注释部分我们知道如下内容:

  1. preHandle是在找到处理handler对象的HandlerMapping之后,HandlerAdapter调度handler之前执行。

  2. postHandle是在HandlerAdapter调度handler之后,DispatcherServlet渲染视图之前执行,可以通过ModelAndView来向视图中添加一些信息等。

  3. afterCompletion是在渲染视图结束后执行,主要可以用来进行事后的资源清理。

  4. 其中postHandle和afterCompletion方法是反顺序执行的。也就是说第一个拦截器会最后一个执行。关于HandlerInterceptor的执行顺序我们可以在HandlerExecutionChain类中找到。

二、HandlerExecutionChain类

这个类由一个handler和若干的HandlerInterceptor构成。那么这个类的作用就显而易见了,就是将拦截器和handle组合起来执行。就是对handle进行了包装。

这个类中有几个主要的方法:

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {//按顺序执行for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = 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);}}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = 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 ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}
}

从函数名中我们可以看出这些方法分别是做什么的,分别是执行interceptorList中所有interceptor的preHandle、postHandle和afterCompletion方法

先从applyPreHandle()看起,我们发现这个方法就是做的这样一个工作,按照列表中interceptor的顺序来执行它们的preHandle方法,直到有一个返回false。再看一下返回false后这个方法所做的工作,这时会调用triggerAfterCompletion方法,此时this.interceptorIndex指向上一个返回true的interceptor的位置,所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。换言之,也就是对于任意的返回false的interceptor都不会执行afterCompletion方法。而且是中断之前所有的preHandle执行完成之后才会执行afterCompletion方法。接下来是applyPostHandle(),这个方法较为简单,就是按照逆序执行所有interceptor的postHandle方法。最后的triggerAfterCompletion()也是一样,就是从最后一次preHandle成功的interceptor处逆序执行afterCompletion。

三、HandlerMapping接口

HandlerExecutionChain是通过HandlerMapping的getHandler方法返回的。继承该接口的类是来实现请求和handler对象的映射关系的。

这个接口中只有这样一个方法

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

根据函数名,参数及返回值我们不难猜出这个接口的作用,就是根据request返回HandlerExecutionChain。至于HandlerMapping在springMVC中有多种实现,我们此处就不深究了。

对于getHandler最后的调度部分便是springMVC的最外层DispatcherServlet类了

四、DispatcherServlet类

DispatcherServlet类中调用HandlerMapping的getHandler的方法为getHandler(同名)

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping hm : this.handlerMappings) {if (logger.isTraceEnabled()) {logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");}HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}}return null;
}

从代码中不难看出整个逻辑就是依次判断servlet中的每个handlerMapping是否能够匹配该请求,直到找到那个匹配的然后返回处理结果。

对于HandlerExecutionChain的调用我们可以在doDispatch()中找到

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 此处用processedRequest  需要注意的是:若是处理上传,processedRequest 将和request不再指向同一对象HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//主要用来管理异步请求的处理。什么时候要用到异步处理呢?就是业务逻辑复杂(或者其他原因),为了避免请求线程阻塞,需要委托给另一个线程的时候。WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {//检查是否为上传文件请求//checkMultipart 这个方法很重要,判断是否是上传需求。且看下面的具体分析://如果请求是POST请求,并且请求头中的Context-Type是以multipart/开头的就认为是文件上传的请求processedRequest = checkMultipart(request);//标记一下:是否是文件上传的requestmultipartRequestParsed = (processedRequest != request);// 步骤1,获取执行链,重要// 找到一个处理器,如果没有找到对应的处理类的话,这里通常会返回404,如果throwExceptionIfNoHandlerFound属性值为true的情况下会抛出异常mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 步骤2,获取适配器,重要// 根据实际的handler去找到一个合适的HandlerAdapter,方法详细逻辑同getHandler,因此不再解释HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());//如果是GET请求,如果内容没有变化的话,则直接返回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;}}// 步骤3,执行拦截器pre方法,重要// 这段代码很有意思:执行处理器连里的拦截器们,具体参阅下面详细:if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}//步骤4,真正处理逻辑,重要// 真正执行我们自己书写的controller方法的逻辑。返回一个ModelAndView// 这也是一个很复杂的过程(序列化、数据绑定等等),需要后面专题讲解mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 如果异步启动了,这里就先直接返回了,也就不会再执行拦截器PostHandle之类的if (asyncManager.isConcurrentHandlingStarted()) {return;}//意思是:如果我们没有设置viewName,就采用默认的。否则采用我们自己的applyDefaultViewName(processedRequest, mv);//步骤5,执行拦截器post方法,重要// 执行所有的拦截器的postHandle方法,并且把mv给他// 这里有一个小细节:这个时候拦截器是【倒序】执行的mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}//步骤6,处理视图,重要//这个方法很重要,顾名思义,他是来处理结果的,渲染视图、处理异常等等的  下面详细分解processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {//步骤7,执行拦截器收尾方法,重要triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

从上面代码中我们可以验证第一部分所说的HandlerInterceptor接口中三个方法的执行顺序:

  1. preHandle是在找到处理handler对象的HandlerMapping之后,HandlerAdapter调度handler之前执行。
  2. postHandle是在HandlerAdapter调度handler之后,DispatcherServlet渲染视图之前执行
  3. afterCompletion是在渲染视图结束后执行

五、例子

  1. 四个拦截器A\B\C\D
@Component
public class A implements HandlerInterceptor {//在Controller执行之前调用,如果返回false,controller不执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("---------A.preHandle--------");return true;}//controller执行之后,且页面渲染之前调用@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("---------A.postHandle--------");}//页面渲染之后调用,一般用于资源清理操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("---------A.afterCompletion--------");}
}
@Configuration
public class MvcConfiguration implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new A());registry.addInterceptor(new B());registry.addInterceptor(new C());registry.addInterceptor(new D());WebMvcConfigurer.super.addInterceptors(registry);}
}

当全部拦截器的preHandle返回值都是true时,调用拦截器返回如下:

当C的preHandle返回false时,返回如下:

SpringMVC拦截器HandlerInterceptor原理及使用相关推荐

  1. SpringMVC拦截器HandlerInterceptor拦截后返回数据或视图View

    SpringBoot版本:2.1.6.RELEASE SpringMVC版本:5.1.8.RELEASE SpringMVC拦截器 比如说在SpringMVC Web环境下,需要实现一个权限拦截的功能 ...

  2. SpringMVC拦截器HandlerInterceptor使用

    Spring MVC 拦截器(HandlerInterceptor)使用 Spring 拦截器--HandlerInterceptor 转载于:https://www.cnblogs.com/goto ...

  3. java面试 拦截器问题_面试必问:给我说一下Spring MVC拦截器的原理?

    拦截器是每个Web框架必备的功能,也是个老生常谈的主题了.本文将分析SpringMVC的拦截器功能是如何设计的,让读者了解该功能设计的原理. 重要接口及类介绍1. HandlerExecutionCh ...

  4. 面试:给我说一下Spring MVC拦截器的原理?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://urlify.cn/namaQ ...

  5. 面试官:给我说一下 Spring MVC 拦截器的原理?

    前言 SpringMVC是目前主流的Web MVC框架之一. 拦截器是每个Web框架必备的功能,也是个老生常谈的主题了. 本文将分析SpringMVC的拦截器功能是如何设计的,让读者了解该功能设计的原 ...

  6. SpringMVC拦截器与Filter过滤器

    SpringMVC拦截器与Filter过滤器 SpringMVC拦截器与Filter过滤器 Spring MVC拦截器的定义 SpringMVC拦截器的配置 SpringMVC拦截器HandlerIn ...

  7. SpringMVC→拦截器、SpringMVC拦截器实现、多个拦截器工作原理、拦截器使用场景、拦截器Interceptor与过滤器Filter区别

    拦截器 拦截器实现 多个拦截器工作原理 拦截器使用场景 请求编码设置及请求登录Session校验 使用时间段设置 拦截器Interceptor与过滤器Filter区别

  8. springMVC 拦截器

    为什么80%的码农都做不了架构师?>>>    实现springMVC 拦截器步骤: 1.定义拦截器类××××HandlerInterceptor  继承HandlerInterce ...

  9. springmvc 拦截器、国际化、验证

    2019独角兽企业重金招聘Python工程师标准>>> springmvc 拦截器 继承了HandlerIntercepter的类可以作为拦截器类: package com.yawn ...

最新文章

  1. android window 大小,android popupWindow 中宽度莫名很大,求帮助?
  2. php tp3 操作绑定到类,快速入门 17:操作绑定到类
  3. Git复习(十一)之常见命令用法
  4. 微信朋友圈五月十大谣言:60岁以上老人打962899可享免费服务
  5. MAC下MySQL初始密码忘记怎么办
  6. AJAX 跨域访问 — 方法大全
  7. CBR编码与VBR编码
  8. USYD悉尼大学DATA 2002 【R语言学习1】【介绍R】Introduction to R「虽迟但到」
  9. MapReduce剥洋葱
  10. 关于25Qxx踩坑总结(无法写入)
  11. Android点选下拉列表框选项,获取选项内容
  12. Mybatis配置分页
  13. 移动端弹出层滚动时禁止body滚动
  14. 迪米特法则(LOD)
  15. MySQL命令rename:修改表名
  16. TCPDF生成PDF文件方法教程说明
  17. js处理本地.bin音频文件和node.js的fs模块处理本地.bin音频区别
  18. 51单片机在Ubuntu和MacOS下程序开发和下载
  19. TB6600驱动器调试42电机(stm32)
  20. 产品经理(14)-金融圈需求app

热门文章

  1. Jquery----实现抽奖效果(根据姓名抽奖)
  2. Leetcode-937-Reorder Log Files-(Easy)
  3. ActiveMQ broker 集群, 静态发现和动态发现
  4. 数论-扩展中国剩余定理
  5. 【Unity3D自学记录】判断物体是否在镜头内
  6. 用MOS管防止电源反接的原理
  7. PHP (20140508)
  8. ORACLE创建用户,建表空间,授予权限
  9. Windows 8 动手实验系列教程 实验6:设置和首选项
  10. Go 语言web 框架 Gin 练习5