springmvc拦截器及源码分析

news2024/12/28 18:39:35

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

1、springmvc拦截器使用

1.1 项目初始搭建

1.1.1 创建一个maven的war工程

该步骤不再截图说明

1.1.2 引入maven依赖

<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>

1.2.3 配置web.xml

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

<!-- 前端控制器(加载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>

1.2 拦截器开发

1.2.1 准备两个拦截器

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

publicclassMyInterceptor1implementsHandlerInterceptor{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 执行======");}}
publicclassMyInterceptor2implementsHandlerInterceptor{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 执行======");}}

1.2.2 在springmvc.xml中拦截器

<!--配置拦截器--><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之前!

1.3 测试拦截器效果

1.3.1 准备测试Controller

@ControllerpublicclassBizController{@RequestMapping("testBiz")publicStringshowUserInfo(Integer userId,Model model){System.out.println(">>>>>业务代码执行-查询用户ID为:"+ userId);User user =newUser(userId);
        user.setName("宙斯");
        model.addAttribute("userInfo",user);return"user_detail";}}
该controller会转发到user_detail.jsp页面

1.3.2 准备user_detail.jsp

<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>

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方法,我们将其取出并观察其核心代码(省略非关键代码)

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{//...}}

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

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

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

2.2 拦截器步骤解析

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

2.2.1 第【3】步骤

//3.执行前置拦截器中的详细代码booleanapplyPreHandle(HttpServletRequest request,HttpServletResponse response)throwsException{//获得本次请求对应的所有拦截器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);returnfalse;}}}//最终返回truereturntrue;}

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

2.2.2 第【5】步骤

//5.执行后置拦截器voidapplyPostHandle(HttpServletRequest request,HttpServletResponse response,@NullableModelAndView mv)throwsException{//获得本次请求对应的所有拦截器HandlerInterceptor[] interceptors =this.getInterceptors();if(!ObjectUtils.isEmpty(interceptors)){//按倒叙执行每个拦截器的postHandle方法——所以我们看到先执行的拦截器2的postHandle,再执行拦截器1的postHandlefor(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】步骤

//执行的方法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方法:

//6、执行拦截器的最终方法voidtriggerAfterCompletion(HttpServletRequest request,HttpServletResponse response,@NullableException ex)throwsException{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 MVC框架是什么?有什么特点?

Spring MVC是Spring提供的一个实现了Web MVC设计模式的轻量级Web框架。它与Struts2框架一样,都属于MVC框架,但其使用和性能等方面比Struts2更加优异。

Spring MVC具有如下特点:

是Spring框架的一部分,可以方便的利用Spring所提供的其他功能。

灵活性强,易于与其他框架集成。

提供了一个前端控制器DispatcherServlet,使开发人员无需额外开发控制器对象。

可自动绑定用户输入,并能正确的转换数据类型。

内置了常见的校验器,可以校验用户输入。如果校验不能通过,那么就会重定向到输入表单。

支持国际化。可以根据用户区域显示多国语言。

支持多种视图技术。它支持JSP、Velocity和FreeMarker等视图技术。

使用基于XML的配置文件,在编辑后,不需要重新编译应用程序。

除上述几个优点外,Spring MVC还有很多其他优点,由于篇幅有限,这里就不一一列举了。在接下来的学习中,读者会逐渐的体会到Spring MVC的这些优点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/188992.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何用ffmpeg截取视频片段截取时间不准确的坑

之前在工作中&#xff0c;有遇到需要程序化截取视频片段的场景&#xff0c;这里使用ffmpeg命令行就可以很容易实现&#xff0c;这里也记录下我们使用过程中遇到的坑&#xff0c;希望对大家也有所帮助。    举个例子&#xff0c;当我们要截取视频文件中input.mp4的第15秒到第9…

windows+python+bleak+BLE低功耗蓝牙通讯连接

前言 1.为什么选bleak   参考这篇知乎&#xff1a;https://zhuanlan.zhihu.com/p/577687336   windows端使用python连接常规的BLE设备&#xff08;蓝牙4.0&#xff09;&#xff0c;仅考虑bleak模块&#xff08;排除pybluez、pybluez2、pygatt&#xff09;。 2.本文主要参…

【c语言】对结构体数组按照某项规则进行排序

这是基于qsort()函数进行的简单排序。&#xff08;附带其他类型的数组使用qsort()进行的排序&#xff09; 目录 一、qsort()函数 二、compare()函数 1.结构体数组 1&#xff09;升序实现 2&#xff09;降序实现 2.整型数组 为什么不直接返回 a>b&#xff08;a&#x…

CentOS 下PostgreSQL安装、简单配置及数据迁移(存储目录迁移)

目录 数据库安装 数据库初始化 配置修改 1、修改监听范围 2、修改数据库用户密码 3、开启远程连接 附件内容&#xff1a;PostgreSQL数据迁移 方式一&#xff0c;从新初始化数据库在导出导入 方式二&#xff1a;存储文件物理迁移 数据库安装 安装包下载请参考PG官网(根据…

OAuth2介绍(一)

目录 1. 什么是OAuth2.0 2. OAuth2中的角色 3. 认证流程 4. 生活中的Oauth2思维 5. 令牌的特点 6. OAuth2授权方式 6.1 授权码 6.2 隐藏方式 6.3 密码方式 6.4 凭证方式 7. 流程 7.1 资源所有者 7.2 客户 7.3 客户 7.4 认证服务器 7.5 客户 7.6 资源服务器 1.…

【高并发】- 分布式事务都不会?

前言 本章主要对分布式事务进行梳理和讲解。可能在业务设计过程中&#xff0c;各微服务都采用了独立数据库&#xff0c;所以&#xff0c;这些微服务之间的数据共享有了更高的要求&#xff1a;要解决数据一致性的问题。 1. 数据一致性 数据一致性是指&#xff1a;数据被多次操作…

【自然语言处理】主题建模评估:连贯性分数(Coherence Score)

主题建模评估&#xff1a;连贯性分数&#xff08;Coherence Score&#xff09;1.主题连贯性分数 主题连贯性分数&#xff08;Coherence Score&#xff09;是一种客观的衡量标准&#xff0c;它基于语言学的分布假设&#xff1a;具有相似含义的词往往出现在相似的上下文中。 如果…

如何使用ArcGIS计算道路中心线

1.概述 在制图等应用的时候&#xff0c;有时需要将双线的面状道路提取中心线&#xff0c;转换为线状的道路。 由于道路多为不规则的图形&#xff0c;提取难度比较高&#xff0c;加上能提取中心线的软件有限&#xff0c;更加增加了提取的难度。 ArcGIS虽然提供了提取中心线的…

C语言文件操作(二)

文件的随机读写fseek函数#include <stdio.h>int main() {FILE* pf fopen("test.txt", "r");if (NULL pf){perror("fopen");return 1;}char ch fgetc(pf);printf("%c\n", ch);fseek(pf, 2, SEEK_SET);ch fgetc(pf);printf(&q…

Mysql第四期 运算符规则计算】

文章目录写在前面1.算数运算符2.比较运算符3.逻辑运算符4.位运算符5.运算符的优先级拓展&#xff1a;使用正则表达式查询写在前面 基本的运算符号在计算机编程领域都是相通的&#xff0c;会有自己的一些特定符号语言&#xff0c;就像是各地的普通话一样&#xff0c;尽管语音描…

C语言小题,又3个学生的信息,放在结构体数组中,要求输出全部学生的信息。(指向结构体数组的指针)

前言&#xff1a; 此篇是针对 指向结构体数组的指针 方面的练习。 解题思路&#xff1a; 用指向结构体变量的指针来处理&#xff1a; &#xff08;1&#xff09;声明结构体类型 struct Student &#xff0c;并定义结构体数组&#xff0c;同时使之初始化&#xff1b; &#xff…

SpringBoot项目如何引入外部jar及将外部jar打包到项目发布war包

1 Springboot项目如何打成war包 1.1 环境准备 打包成war整体思路就是排查web容器依赖&#xff0c;添加maven-war-plugin插件。接下来就使用Tomcat容器给大家做个示范&#xff0c;亲测有效。 在讲解下说明一下环境&#xff0c;避免因为环境的问题&#xff0c;给大家带来不必要…

设计师都在用的5个设计素材库

作为一名设计师推荐几个设计素材网站&#xff0c;建议收藏起来&#xff01; 1、菜鸟图库 https://www.sucai999.com/?vNTYxMjky 站内平面海报、UI设计、电商淘宝、高清图片、样机模板等素材非常齐全。还有在线抠图、CDR版本转换功能&#xff0c;能有效的为设计师节省找素材时…

嵌入式Linux-线程属性

1. 线程的属性 1.1 概念 如前所述&#xff0c;调用 pthread_create()创建线程&#xff0c;可对新建线程的各种属性进行设置。在 Linux 下&#xff0c;使用pthread_attr_t 数据类型定义线程的所有属性。 调用 pthread_create()创建线程时&#xff0c;参数 attr 设置为 NULL&a…

Three.js 初阶入门篇(一)

系列文章目录 文章目录系列文章目录学习背景一、什么是3D&#xff08;直接看作品吧&#xff09;&#xff1f;汽车作品欣赏鼠标可以随意转动角度打开机盖&#xff08;交互效果&#xff09;尾部3D链接&#x1f517;如下&#xff08;链接打开会有一些慢&#xff09;二、如何创建一…

零入门容器云网络实战-7->Mac环境下为虚拟机磁盘空间进行扩容

在Mac环境下&#xff0c;使用PD软件创建的虚拟机磁盘空间不够时&#xff0c;如何扩容呢&#xff1f; 主要分两大步骤&#xff1a; 先通过PD界面&#xff0c;设置增加多少空间进入虚拟机里&#xff0c;通过fdisk等相关命令&#xff0c;使其增加的空间生效 1、第一大步&#xf…

机器学习之线性模型

定义 线性模型非常常见&#xff0c;但详细了解其中原理是必要的。 一般将样本特征进行线性组合达到预测的目标&#xff0c;如表达式yf(X;W)byf(X;W)byf(X;W)b,其中XXX为输入的样本数据&#xff0c;WWW为权重系数&#xff0c;bbb为偏置系数。 如对于图片样本&#xff0c;一种…

兔年春晚一大怪像,影视演员变成了万能,专业歌手却被晾在一边

怪事年年有&#xff0c;今年特别多。谁也没有想到&#xff0c;兔年春节还没有过去&#xff0c;就出现了一种奇怪的现象。中央电视台春晚&#xff0c;曾经执全国春晚之牛耳&#xff0c;然而谁能想到&#xff0c;四十多年后的今天&#xff0c;曾经的扛把子却变成了鸡肋。 今年央视…

【C++初阶】七、STL---vector(总)|vector的介绍|vector的使用

目录 一、vector的介绍 二、vector的使用 2.1 Construct 2.2 operator 2.3 Iterators 2.4 Capacity 2.5 Element access 2.6 Modifiers 一、vector的介绍 前面学习了 string类&#xff0c;所以 vector 的学习成本很低&#xff0c;因为接口都大致相同&#xff0c;功能也…

【促进开发】上海道宁与DHTMLX为您提供易于使用且功能丰富的JavaScript组件

DHTMLX提供 有效且专业设计的 JavaScript/HTML5工具 使开发人员 能够以更少的时间和精力 创建具有丰富界面和快速性能的 复杂Web和移动应用程序 DHTMLX使用 JavaScript UI 库促进开发 易于使用且功能丰富的 JavaScript组件 非常适合您在任何领域和 任何复杂性中的解…