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 | publicclassMyInterceptor1 implementsHandlerInterceptor {        publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        System.out.println("==1-1====前置拦截器1 执行======");        returntrue; //ture表示放行    }    publicvoidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {        System.out.println("==1-2=====后置拦截器1 执行======");    }    publicvoidafterCompletion(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 | publicclassMyInterceptor2 implementsHandlerInterceptor {    publicbooleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {        System.out.println("==2-1====前置拦截器2 执行======");        returntrue; //ture表示放行    }    publicvoidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {        System.out.println("==2-2=====后置拦截器2 执行======");    }    publicvoidafterCompletion(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:mappingpath="/**"/>            <beanclass="com.itheima.interceptor.MyInterceptor1"/>        </mvc:interceptor>        <mvc:interceptor>            <mvc:mappingpath="/**"/>            <beanclass="com.itheima.interceptor.MyInterceptor2"/>        </mvc:interceptor>    </mvc:interceptors> | 
注意配置顺序拦截器1先于拦截器2,并且都拦截所有的方法
4、准备业务类,该类转发到一个JSP页面,并在页面做后台打印
| 01 02 03 04 05 06 07 08 09 10 11 | @ControllerpublicclassBizController {    @RequestMapping("testBiz")    publicString showUserInfo(Integer userId, Model model){        System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);        User user = newUser(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 | protectedvoiddoDispatch(HttpServletRequest request, HttpServletResponse response) throwsException {        //...        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 = newNestedServletException("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, newNestedServletException("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.执行前置拦截器中的详细代码    booleanapplyPreHandle(HttpServletRequest request, HttpServletResponse response) throwsException {        //获得本次请求对应的所有拦截器        HandlerInterceptor[] interceptors = this.getInterceptors();        if(!ObjectUtils.isEmpty(interceptors)) {            //按照拦截器顺序依次执行每个拦截器的preHandle方法.            //并且,interceptorIndex值会一次 + 1 (该值是给后面的最终拦截器使用的)            for(inti = 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);                    returnfalse;                }            }        }        //最终返回true        returntrue;    } | 
我们可以看到拦截器的preHandler方法是按拦截器顺序执行的。紧接着贴出第5步的后置拦截器源码:
| 01 02 03 04 05 06 07 08 09 10 11 12 | //5.执行后置拦截器    voidapplyPostHandle(HttpServletRequest request, HttpServletResponse response, @NullableModelAndView mv) throwsException {        //获得本次请求对应的所有拦截器        HandlerInterceptor[] interceptors = this.getInterceptors();        if(!ObjectUtils.isEmpty(interceptors)) {            //按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandle            for(inti = 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 | privatevoidprocessDispatchResult(HttpServletRequest request, HttpServletResponse response, @NullableHandlerExecutionChain mappedHandler, @NullableModelAndView mv, @NullableException exception) throwsException {    //...    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方法:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | //6、执行拦截器的[align=left][color=#b00000]最终[/color][/align]方法    voidtriggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @NullableException ex) throwsException {        HandlerInterceptor[] interceptors = this.getInterceptors();        if(!ObjectUtils.isEmpty(interceptors)) {            //倒叙执行每个拦截器(interceptorIndex为前置拦截器动态计算)的afterCompletion方法            for(inti = 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)
 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也继承了 ... 
随机推荐
- PIL对象和numpy三维数组的互相转换
			#https://stackoverflow.com/questions/384759/how-to-convert-a-pil-image-into-a-numpy-array from PIL i ... 
- LGOJ1264 K-联赛
			这题其实不难想到 Description link 题意太长了,概括不来,去题库里扫一眼吧(但是很好懂) Solution \[Begin\] 考虑一个事情:每一个队伍的输局是没有用的 贪心一下,让每 ... 
- 图论模型--dijstra算法和floyd算法
			matlab代码实现:https://blog.csdn.net/weixin_40108753/article/details/81237585 python代码实现: 
- Codeforces 1288C - Two Arrays
			题目大意: 给定n和m,有两个数组,两个数组的长度都等于m 数组内每个元素都在1到n中 对于两个数组对应的位置i,必须满足a[i]<=b[i] a数组必须是不下降的序列 b数组必须是不上升的序列 ... 
- Django专题-Cookie
			Cookie Cookie的由来 大家都知道HTTP协议是无状态的. 无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响, ... 
- Python模块——base64
			简介 base64模块是用来作base64编码解码,常用于小型数据的传输.编码后的数据是一个字符串,其包括a-z.A-Z.0-9./.+共64个字符,即可用6个字节表示,写出数值就是0-63.故三个字 ... 
- linux中常见压缩文件格式
			文件后缀名 说明 *.zip zip 程序打包压缩的文件 *.rar rar 程序压缩的文件 *.7z 7zip 程序压缩的文件 *.tar tar 程序打包,未压缩的文件 *.gz gzip 程序( ... 
- php面向对象理解(一)
			常用的继承过程,以及对public.private.protected修饰符的理解: /*****************************父类************************* ... 
- cmd释放重新获取IP
			1.打开电脑的命令提示符运行设置窗口之后,我们收入 ipconfig/release ,然后点击回车键 ,释放之前获取的IP地址 2.释放之前的IP地址之后,我们在输入 ipconfig/re ... 
- Navicat for MySQL远程连接报10038的错误
			#################################################### """ 1.网络检测 1)ping主机可以: 2)telnet ... 
