前言

springmvc拦截器是我们项目开发中用到的一个功能,常常用于对Handler进行预处理和后处理。本案例来演示一个较简单的springmvc拦截器的使用,并通过分析源码来探究拦截器的执行顺序是如何控制的。

1、springmvc拦截器使用

1.1 项目初始搭建

1.1.1 创建一个maven的war工程

该步骤不再截图说明

1.1.2 引入maven依赖

org.springframework

spring-context

5.0.2.RELEASE

org.springframework

spring-web

5.0.2.RELEASE

org.springframework

spring-webmvc

5.0.2.RELEASE

javax.servlet

servlet-api

2.5

provided

javax.servlet.jsp

jsp-api

2.0

provided

1.2.3 配置web.xml

配置springmvc核心控制器DispatcherServlet,由于需要加载springmvc.xml,所以需要创建一个springmvc.xml文件(文件参考源码附件)放到classpath下

dispatcherServlet

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:springmvc.xml

1

dispatcherServlet

/

1.2 拦截器开发

1.2.1 准备两个拦截器

两个拦截器分别命名为MyInterceptor1、MyInterceptor2

publicclassMyInterceptor1implementsHandlerInterceptor{

publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler){

System.out.println("==1-1====前置拦截器1 执行======");

returntrue; //ture表示放行

}

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

System.out.println("==1-2=====后置拦截器1 执行======");

}

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

System.out.println("==1-3======最终拦截器1 执行======");

}

}public class MyInterceptor2 implements HandlerInterceptor {

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

System.out.println("==2-1====前置拦截器2 执行======");

return true; //ture表示放行

}

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {

System.out.println("==2-2=====后置拦截器2 执行======");

}

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

System.out.println("==2-3======最终拦截器2 执行======");

}

}

1.2.2 在springmvc.xml中拦截器

这两个拦截器拦截规则相同,并且配置顺序拦截器1在拦截器2之前!

1.3 测试拦截器效果

1.3.1 准备测试Controller

@Controller

publicclassBizController{

@RequestMapping("testBiz")

public String showUserInfo(Integer userId, Model model){

System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);

User user = new User(userId);

user.setName("宙斯");

model.addAttribute("userInfo",user);

return"user_detail";

}

}

该controller会转发到user_detail.jsp页面

1.3.2 准备user_detail.jsp

detail

用户详情:

${userInfo.id}:${userInfo.name}

>>>>jsp页面的输出为:");%>

1.3.3 测试效果

启动项目后,在地址栏访问/testBiz?userId=1,然后查看IDE控制台打印:

==1-1====前置拦截器1 执行======

==2-1====前置拦截器2 执行======

>>>>>业务代码执行-查询用户ID为:1

==2-2=====后置拦截器2 执行======

==1-2=====后置拦截器1 执行======

>>>>>jsp页面的输出为:宙斯

==2-3======最终拦截器2 执行======

==1-3======最终拦截器1 执行======

通过打印日志发现,拦截器执行顺序是: 拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终

2、源码分析

经过测试发现拦截器执行顺序如下:

拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终

我们通过分析源码来探究下拦截器是如何执行的

2.1 DispatcherServlet

当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServlet的doDispatch方法,我们将其取出并观察其核心代码(省略非关键代码)

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

//...

try {

try {

ModelAndView mv = null;

Object dispatchException = null;

try {

processedRequest = this.checkMultipart(request);

multipartRequestParsed = processedRequest != request;

//1.获取执行链

mappedHandler = this.getHandler(processedRequest);

if (mappedHandler == null) {

this.noHandlerFound(processedRequest, response);

return;

}

//2.获取处理器适配器

HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

//...

//【3】.执行前置拦截器

if (!mappedHandler.applyPreHandle(processedRequest, response)) {

return;

}

//4.执行业务handler

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {

return;

}

this.applyDefaultViewName(processedRequest, mv);

//【5】.执行后置拦截器

mappedHandler.applyPostHandle(processedRequest, response, mv);

} catch (Exception var20) {

dispatchException = var20;

} catch (Throwable var21) {

dispatchException = new NestedServletException("Handler dispatch failed", var21);

}

//【6】.处理页面响应,并执行最终拦截器

this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

} catch (Exception var22) {

this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);

} catch (Throwable var23) {

this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));

}

}finally {

//...

}

}

代码中有关拦截器执行的位置我都添加了注释,其中注释中标识的步骤中,3、5、6步骤是拦截器的关键步骤

其中,第一步中"获取执行链",执行链内容可以通过debug调试查看内容:

可以看到我们自定义的两个拦截器按顺序保存

2.2 拦截器步骤解析

在doDispatch方法中,我们添加的注释的第【3】、【5】、【6】步骤是对拦截器的执行处理,现在分别来查看第【3】、【5】、【6】步骤执行的具体方法的源码

2.2.1 第【3】步骤

//3.执行前置拦截器中的详细代码

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {

//获得本次请求对应的所有拦截器

HandlerInterceptor[] interceptors = this.getInterceptors();

if (!ObjectUtils.isEmpty(interceptors)) {

//按照拦截器顺序依次执行每个拦截器的preHandle方法.

//并且,interceptorIndex值会一次 + 1 (该值是给后面的最终拦截器使用的)

for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {

HandlerInterceptorinterceptor = interceptors[/color][i][color=black];

//只要每个拦截器不返回false,则继续执行,否则执行最终拦截器

if (!interceptor.preHandle(request, response, this.handler)) {

this.triggerAfterCompletion(request, response, (Exception)null);

returnfalse;

}

}

}

//最终返回true

returntrue;

}

我们可以看到拦截器的preHandler(前置处理)方法是按拦截器(拦截器1、拦截器2)顺序执行的,然后我们再来看步骤【5】

2.2.2 第【5】步骤

//5.执行后置拦截器

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {

//获得本次请求对应的所有拦截器

HandlerInterceptor[] interceptors = this.getInterceptors();

if (!ObjectUtils.isEmpty(interceptors)) {

//按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandle

for(int i = interceptors.length - 1; i >= 0; --i) {

HandlerInterceptor interceptor = interceptors[/color][color=black];

interceptor.postHandle(request, response, this.handler, mv);

}

}

}

会发现,后置处理是按照拦截器顺序倒叙处理的!

我们最后来看下最终拦截器

2.2.3 第【6】步骤

//执行的方法

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {

//...

if (mv != null && !mv.wasCleared()) {

//处理响应

this.render(mv, request, response);

if (errorView) {

WebUtils.clearErrorRequestAttributes(request);

}

} elseif (this.logger.isDebugEnabled()) {

this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");

}

if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {

if (mappedHandler != null) {

//6、执行拦截器的最终方法们

mappedHandler.triggerAfterCompletion(request, response, (Exception)null);

}

}

}

其中,有一个render()方法,该方法会直接处理完response。再后则是触发triggerAfterCompletion方法:

//6、执行拦截器的最终方法

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {

HandlerInterceptor[] interceptors = this.getInterceptors();

if (!ObjectUtils.isEmpty(interceptors)) {

//倒叙执行每个拦截器(interceptorIndex为前置拦截器动态计算)的afterCompletion方法

for(int i = this.interceptorIndex; i >= 0; --i) {

HandlerInterceptor interceptor = interceptors[/color][/i][color=black][i];

try {

interceptor.afterCompletion(request, response, this.handler, ex);

} catch (Throwable var8) {

logger.error("HandlerInterceptor.afterCompletion threw exception", var8);

}

}

}

}

由此可以看到,拦截器的最终方法的执行也是按照倒叙来执行的,而且是在响应之后。

3、总结

拦截器常用于初始化资源,权限监控,会话设置,资源清理等的功能设置,就需要我们对它的执行顺序完全掌握,我们通过源码可以看到,拦截器类似于对我们业务方法的环绕通知效果,并且是通过循环收集好的拦截器集合来控制每个拦截器方法的执行顺序,进而可以真正做到深入掌握拦截器的执行机制!

spring拦截器覆盖_springmvc拦截器及源码分析相关推荐

  1. 修改拦截器里的请求头_OkHttp4 源码分析(1) 请求流程分析

    square/okhttp​github.com 本文基于OkHttp4.7.1分析 同步请求示例代码 OkHttpClient client = new OkHttpClient.Builder() ...

  2. Spring配置详解,Spring配置元信息详解,Spring配置大全及源码分析

    文章目录 一.Spring都可以配置哪些元信息 二.Spring Bean 配置元信息 1.GenericBeanDefinition 2.RootBeanDefinition 3.Annotated ...

  3. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  4. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  5. [Abp vNext 源码分析] - 3. 依赖注入与拦截器

    一.简要说明 ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包.这里与原来的 ABP 框架 ...

  6. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  7. mybatis源码分析之事务管理器

    2019独角兽企业重金招聘Python工程师标准>>> 上一篇:mybatis源码分析之Configuration 主要分析了构建SqlSessionFactory的过程中配置文件的 ...

  8. JStorm与Storm源码分析(六)--收集器 IOutputCollector 、OutputCollector

    在Storm中,多个地方使用了IOutputCollector收集器接口,收集器OutputCollector的接口就是IOutputCollector.所以有必要对接口IOutputCollecto ...

  9. JStorm与Storm源码分析(三)--Scheduler,调度器

    Scheduler作为Storm的调度器,负责为Topology分配可用资源. Storm提供了IScheduler接口,用户可以通过实现该接口来自定义Scheduler. 其定义如下: public ...

最新文章

  1. 重大改变!Python 或将取代 VBA 成为 Excel 官方脚本语言
  2. DHCP Snooping的作用
  3. (017)java后台开发之客户端通过HTTP获取接口Json数据
  4. php 新浪面试题,php程序员面试题(新浪篇)
  5. STM32网络电路设计
  6. 格力电器上半年净利94.57亿元,同比增长48.64%
  7. 使用Python批量替换指定目录所有文件中的指定文本
  8. 栈的输出_C|简单实现栈、及利用递归将十进制数用其他进制来输出
  9. (转)SDL1.2到2.0的迁移指南
  10. Redis 的操作为什么是的原子性的详解
  11. Vue入门之常用指令
  12. 用友漏洞php,用友NC任意文件上传漏洞复现
  13. 量子力学 计算机应用,有了九章计算机:但我们离量子力学还很远~
  14. [供应链·案例篇]疫情影响下的全球十大零售商都做了些什么
  15. 百度网盘PC端登录安全验证显示空白页
  16. wordpress模板_如何在15分钟内定制WordPress模板
  17. 动态路由、OSPF多区域、ABR,ASBR
  18. 常规设置-Sinon.JS
  19. P1395 会议 题解
  20. 算法题Nuts and Bolts(螺母螺钉)快速排序详细讲解(含流程图)

热门文章

  1. 3、解读中台 -- 中台的发展与进化
  2. 关于在uni-app中使用swiper(APP实测可用)
  3. cst操作——做倒角、走线、螺旋、两种局部拉伸的方法、参数扫描
  4. 联通宽带我世界改为使用路由器多台电脑上网的配置
  5. 分层图池化:Hierarchical Graph Representation Learning with Differentiable Pooling
  6. Win7分割合并磁盘
  7. Linux下Operation `users.findOne()` buffering timed out after 10000ms和admin user not authorized错误总结
  8. 为什么今年支付宝没有年账单?
  9. 前端表格数据导出为 Excel 的简单方法
  10. php中法兰克福的时区,法兰克福三大购物中心 | Global Blue