springmvc拦截器入门及其执行顺序源码分析
springmvc拦截器是偶尔会用到的一个功能,本案例来演示一个较简单的springmvc拦截器的使用,并通过源码来分析拦截器的执行顺序的控制。
具体操作步骤为:
1、maven项目引入spring依赖
2、配置web.xml中的DispatcherServlet
3、准备两个拦截器,并在springmvc配置文件中进行配置管理
4、准备业务类,该类转发到一个JSP页面,并在页面做后台打印
5、测试发送请求到业务类,查看执行顺序
6、源码分析
7、总结以及代码附件
————————————————————————————————————————————————————————————
下面开始开发!
1、maven项目引入spring依赖
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> </dependencies> |
2、配置web.xml中的DispatcherServlet
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<!-- 前端控制器(加载classpath:springmvc.xml 服务器启动创建servlet) --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置初始化参数,创建完DispatcherServlet对象,加载springmvc.xml配置文件 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> |
3、准备两个拦截器,并在springmvc配置文件中进行配置管理
两个拦截器代码如下:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public class MyInterceptor1 implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("==1-1====前置拦截器1 执行======"); return true; //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 执行======"); }} |
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
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 执行======"); }} |
springmvc配置如下(此处只挑选拦截器配置,具体代码参见附件):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
<!--配置拦截器--> <mvc:interceptors> <!--配置拦截器--> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor1" /> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/**" /> <bean class="com.itheima.interceptor.MyInterceptor2" /> </mvc:interceptor> </mvc:interceptors> |
注意配置顺序拦截器1先于拦截器2,并且都拦截所有的方法
4、准备业务类,该类转发到一个JSP页面,并在页面做后台打印
|
01
02
03
04
05
06
07
08
09
10
11
|
@Controllerpublic class BizController { @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"; }} |
响应的页面如下:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
<html><head> <title>detail</title></head><body> 用户详情: ${userInfo.id}:${userInfo.name} <%System.out.print(">>>>>jsp页面的输出为:");%> <%System.out.println(((User)request.getAttribute("userInfo")).getName());%></body></html> |
5、测试发送请求到业务类,查看执行顺序
启动项目后,在浏览器中访问/testBiz?userId=1,会在后台打印如下内容:
|
1
2
3
4
5
6
7
8
|
==1-1====前置拦截器1 执行========2-1====前置拦截器2 执行======>>>>>业务代码执行-查询用户ID为:1==2-2=====后置拦截器2 执行========1-2=====后置拦截器1 执行======>>>>>jsp页面的输出为:宙斯==2-3======最终拦截器2 执行========1-3======最终拦截器1 执行====== |
6、源码分析
通过打印结果会发现,执行顺序为:拦截器1的前置>拦截器2的前置>业务代码>拦截器2后置>拦截器1后置>拦截器2最终>拦截器1最终
我们可以通过源码来分析一下为何是该打印顺序。
最关键的为DispatcherServlet的核心方法——doDispatch。
当浏览器发送/testBiz?userId=1的请求时,会经过DispatcherServlet的doDispatch方法,省略掉部分代码,贴出如下(注释有说明):
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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 { //... } } |
其中,第一步中"获取执行链",执行链内容可以截图查看:
<ignore_js_op>![]()
可以看到我们自定义的两个拦截器按顺序保存。
在doDispatch方法中,我们注释的第3、5、6步骤是对拦截器的执行处理,现在分别来查看第3、5、6步骤的源码。
第三步骤源码如下:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
//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++) { HandlerInterceptor interceptor = interceptors[/color][i][color=black]; //只要每个拦截器不返回false,则继续执行,否则执行最终拦截器 if (!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } //最终返回true return true; } |
我们可以看到拦截器的preHandler方法是按拦截器顺序执行的。紧接着贴出第5步的后置拦截器源码:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
//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); } } } |
会发现,后置处理是按照拦截器顺序倒叙处理的。最后,我们贴出执行最终拦截器方法的代码:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
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); } } else if (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方法:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
//6、执行拦截器的[align=left][color=#b00000]最终[/color][/align]方法 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); } } } } |
由此可以看到,拦截器的最终方法的执行也是按照倒叙来执行的,而且是在响应之后。
7、总结
我们可以从源码中看到,拦截器类似于对我们业务方法的环绕通知效果,并且是通过循环集合来控制每个拦截器方法的执行顺序。
源码如下: <ignore_js_op>
mvc_interceptor.zip (13.49 KB, 下载次数: 106)
springmvc拦截器入门及其执行顺序源码分析的更多相关文章
- Springboot中mybatis执行逻辑源码分析
Springboot中mybatis执行逻辑源码分析 在上一篇springboot整合mybatis源码分析已经讲了我们的Mapper接口,userMapper是通过MapperProxy实现的一个动 ...
- SpringMVC拦截器及多拦截器时的执行顺序
本文链接:https://blog.csdn.net/itcats_cn/article/details/80371639拦截器的配置步骤 springmvc.xml中配置多个拦截器配置自定义拦截器并 ...
- struts2拦截器的实现原理及源码剖析
拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理.同时,拦截器也可以让你将通用的 ...
- springmvc执行流程 源码分析
进入DispatcherServlet 执行onRefresh,然后执行初始化方法initStrategies.然后调用doService——>doDispatch. 根据继承关系执行Servl ...
- SpringMVC类型转换、数据绑定详解[附带源码分析]
目录 前言 属性编辑器介绍 重要接口和类介绍 部分类和接口测试 源码分析 编写自定义的属性编辑器 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那 ...
- springcloud 入门 5 (feign源码分析)
feign:(推荐使用) Feign是受到Retrofit,JAXRS-2.0和WebSocket的影响,它是一个jav的到http客户端绑定的开源项目. Feign的主要目标是将Java Http ...
- springboot Properties加载顺序源码分析
关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...
- (五)myBatis架构以及SQlSessionFactory,SqlSession,通过代理执行crud源码分析---待更
MyBatis架构 首先MyBatis大致上可以分为四层: 1.接口层:这个比较容易理解,就是指MyBatis暴露给我们的各种方法,配置,可以理解为你import进来的各种类.,告诉用户你可以干什么 ...
- springMvc的执行流程(源码分析)
1.在springMvc中负责处理请求的类为DispatcherServlet,这个类与我们传统的Servlet是一样的.我们来看看它的继承图 2. 我们发现DispatcherServlet也继承了 ...
随机推荐
- NGINX常用模块(二)
5.Nginx日志配置 Nginx有非常灵活的日志记录模式.每个级别的配置可以有各自独立的访问日志.日志格式 通过log_format命令定义格式 1.log_format指令 # 配置语法:包括:e ...
- 利用FastJson,拼接复杂嵌套json数据&&直接从json字符串中(不依赖实体类)解析出键值对
1.拼接复杂嵌套json FastJson工具包中有两主要的类: JSONObject和JSONArray ,前者表示json对象,后者表示json数组.他们两者都能添加Object类型的对象,但是J ...
- 国产手机早就异军突起,为何还是有很多人“迷恋”iPhone?
近几年,国产手机不论是从外观上还是设计上,以及销量上都比前有了长足的进步,手机的品质和售后也在不断提升.这也让中国手机厂商不断的推出了拥有自己品牌特色的产品.特别是2018年不少国产手机品牌的旗舰级产 ...
- [前端] VUE基础 (6) (v-router插件、获取原生DOM)
一.v-router插件 1.v-router插件介绍 v-router是vue的一个核心插件,vue+vue-router主要用来做SPA(单页面应用)的. 什么是SPA:就是在一个页面中,有多个页 ...
- Microsoft COCO 数据集
本篇博客主要以介绍MS COCO数据集为目标,分为3个部分:COCO介绍,数据集分类和COCO展示. 本人主要下载了其2014年版本的数据,一共有20G左右的图片和500M左右的标签文件.标签文件标记 ...
- 吴裕雄--天生自然python TensorFlow图片数据处理:解决TensorFlow2.0 module ‘tensorflow’ has no attribute ‘python_io’
tf.python_io出错 TensorFlow 2.0 中使用 Python_io 暂时使用如下指令: tf.compat.v1.python_io.TFRecordWriter(filename ...
- python处理nii格式文件
网上已经有很多代码了,但是注释的都不全,看起来很费解,我自己加了一些注释,重新发出来,尽可能的通俗易懂 读取前需要先安装库 pip install nibabel pip install matplo ...
- Matlab高级教程_第二篇:Matlab相见恨晚的模块_02_并行运算-2
1 MATLAB并行计算-从个人桌面到远程集群和云(陈伟/魏奋)视频摘录笔记 https://cn.mathworks.com/videos/parallel-computing-with-matla ...
- 吴裕雄--天生自然python Google深度学习框架:MNIST数字识别问题
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data INPUT_NODE = 784 ...
- day31-hmac模块检测客户端是否合法
#如果客户端知道服务端的ip地址和端口,就可以连接服务端,信息不安全. #使用os.urandam随机生成32位bytes,然后hmac加密之后再发送给客户端. #server: import soc ...