spring5源码篇(12)——spring-mvc请求流程
spring-framework 版本:v5.3.19
文章目录
- 一、请求流程
- 1、处理器映射器
- 1.1、 RequestMappingHandlerMapping
- 1.2、获取对应的映射方法
- 1.3、添加拦截器
- 2、获取合适的处理器适配器
- 3、通过处理器适配器执行处理器方法
- 3.1、拦截器的前置后置
- 3.2、处理器的执行
- 3.2.1 参数解析器解析参数和执行处理器方法
- 3.2.2 结果处理器处理结果
- 3.2.3 消息转化器
- 4、视图解析器
- 二、请求流程中的一些问题
- 1、各种组件如处理器映射器,处理器适配器,视图解析器等是如何注入到spring容器的?
- 2、RequestMappingInfoHandlerMapping 处理器映射器中维护的映射map (即MapingRegister) 是如何初始化的?
- 3、处理器映射器中的拦截器是如何添加的?
- 4、处理器适配器的参数解析器和结果处理器是如何来的?
- 5、参数解析器/结果处理器中的消息转化器是如何来的?
一、请求流程
总体流程在 DispatchServelt#doDispatch 方法
1、处理器映射器
首先会获取根据url去映射对应的处理器(即接口执行方法)
看到对应的 getHandler 方法
为方便阅读,进入debug。可以看到springmvc默认为我们注册了三个handlerMapping。
springMvc中的各个组件如处理器映射器,处理器适配器,视图解析器等是如何注入到容器的,先暂时不看,第二部分在看(问题1)。
1.1、 RequestMappingHandlerMapping
而其中我们最常用的一个就是RequestMappingHandlerMapping,所以这里只看这个类的 getHandler 方法(其实也是其抽象父类的方法)。
1.2、获取对应的映射方法
从 getHandlerInternal 方法入手,看看是如何把一个请求映射到对应的方法的。
这里首先会从 mappingRegister 中去找可以根据url直接得到的映射(即没有替换符),如果找不到再去所有注册的映射中找,然后返回一个最匹配的映射对应的处理器。
mappingRegister 可以简单的理解成 Map<url,handler>。实际上,为提供效率一共维护了3个map
这些map是如何初始化的,第二部分再讲(问题2)。
至于是如何匹配这些映射的,从 addMatchingMappings 入手
这里其实就是把匹配逻辑抽象成一个个condition,如果满足所有 condition 说明这是一个可用的处理器。注意,仅是可用而已,并不一定会用这个处理器。因为可能同时会匹配到多个可用的处理器,但是最终只会返回最匹配的那一个。
1.3、添加拦截器
在前面的总体流程中注释到,获取处理器的时候,返回的是一个处理器链。因为可能会配置有拦截器,而拦截器的添加从 getHandlerExecutionChain 入手。
可以看到,就只是遍历当前 handlerMapping 所添加的所有拦截器,若匹配,则将该拦截器添加到处理器链中而已。至于 handlerMapping 的拦截器是如何添加的,第二部分再讲(问题3)。
2、获取合适的处理器适配器
Spring MVC可能会有多种类型的处理器,例如控制器(Controller)、RESTful控制器等。处理器适配器负责选择并调用合适的处理器来处理请求。这一步就是去获取一个合适的处理器适配器,其实就是一个简单遍历获取,在spring mvc 默认提供的几种适配器中,最常用的还是 RequestMappingHandlerAdapter
3、通过处理器适配器执行处理器方法
3.1、拦截器的前置后置
在正式看处理器之前,顺便提一嘴拦截器的执行。
在执行实际业务方法之前会先执行处理器链中拦截器前置方法。同理,执行完业务方法后会执行处理器链中拦截器的后置方法。
拦截器的执行就是一个遍历,就不上代码了。
3.2、处理器的执行
从 ha.handle 入手
不管是哪种适配器,最终都会依次通过 参数解析器、处理器、结果处理器 将方法返回值封装成 ModelAndView 对象。
3.2.1 参数解析器解析参数和执行处理器方法
从 invokeForRequest 入手
处理器方法的执行就是反射实现的,没啥好看的。主要看参数解析器是如何解析参数的。
InvocableHandlerMethod 中维护了名为 resolvers 的 HandlerMethodArgumentResolverComposite 对象(多个参数解析器的封装),并且这个变量的值是处理器适配器传过来的。至于处理器适配器的参数解析器是哪来的,先暂时不看,后面第二部分再看(问题4)。这里只需知道参数解析器是在这里解析的以及值是处理器适配器传过来的就可以了。
3.2.2 结果处理器处理结果
从 returnValueHandlers.handleReturnValue 入手
**跟参数解析器维护的复合对象一样,也维护了一个名为 returnValueHandlers 的 HandlerMethodReturnValueHandlerComposite 复合对象,并且这个变量的值也是处理器适配器传过来的。**至于处理器适配器的结果处理器是哪来的,也先暂时不看。
一般来说参数解析器的命名为 xxxMethodArgumentResolver 而结果处理器的命名为 xxxMethodReturnValueHandler。但是如果即是参数解析器又是结果处理器的命名就会xxxMethodProcessor。而我们常用的 RequestResponseBodyMethodProcessor 就是这种类型。
3.2.3 消息转化器
首先并不是所有的参数解析器或者结果处理器都会用到消息转化器。,但至少我们最常用的 **RequestResponseBodyMethodProcessor 无论是解析参数还是处理结果,都会用到消息转化器。**对应的方法分别是 readWithMessageConverters,writeWithMessageConverters。并且write是直接写到 http 的response,这也意味着 @ResponseBody 不需要通过视图解析器解析渲染视图。
在 RequestResponseBodyMethodProcessor 中无论是read还是write,都会去遍历参数解析器/结果处理器中维护的消息转化器列表。至于 参数解析器/结果处理器的消息转化器列表是哪里来的,也先暂时不看,后面在看(问题5)。
4、视图解析器
试想这么一个接口,没有 @ResponseBody 并且返回值是一个字符串。
如果有 @ResponseBody 注解,ModelAndView为null,无需解析渲染视图。
在我们配置如下的视图解析器时会访问到servlet容器下的mv.html页面。
从字符串到返回的具体页面,这正是视图解析器所做的事情。
关于视图解析器,从 processDispatchResult 入手
解析:如果 ModelAndView 有视图名(在上面的例子中视图名就是方法返回的字符串)就会调用视图解析器去解析视图名得到一个视图 View 对象,反之就从 ModelAndView 中直接获取 View 对象。总之,不管怎样,这里一定要有 View 对象,没有就报错。
渲染:然后再调用上一步得到的 View 对象 render 方法并将 ModelAndView 里的 Model 数据传入,从而将数据渲染带视图上(如:jsp)。最后将渲染后的结果返回至前端。
至此一个完整的请求流程就看完了。为更形象记忆 spring mvc 各组件之间的关系,这里附上一张网图
二、请求流程中的一些问题
1、各种组件如处理器映射器,处理器适配器,视图解析器等是如何注入到spring容器的?
答:@EnableWebMvc注解(相当于xml配置: <mvc:annotation-driven/>)。
@EnableWebMvc 引入了 DelegatingWebMvcConfiguration,而这个类中就默认注入了 spring mvc 的各种组件。
比如:
2、RequestMappingInfoHandlerMapping 处理器映射器中维护的映射map (即MapingRegister) 是如何初始化的?
答:在 RequestMappingInfoHandlerMapping bean生命周期的 afterPropertiesSet。
3、处理器映射器中的拦截器是如何添加的?
答:以 RequestMappingInfoHandlerMapping 为例,在 DelegatingWebMvcConfiguration 注入处理器映射器的时就同时会去set拦截器。
其中 getInterceptors 代码如下
说白了就是 getInterceptors 方法,会依次调用所有的 WebMvcConfigurer.addInteceptors 方法将自定义配置的拦截器添加到处理器映射器中的 InterceptorRegistry。之后处理器映射器在映射处理器时,将其中匹配的拦截器添加到处理链,进而实现拦截器的效果。
正是因为自动注入的原理,所以我们平时配置spring mvc时只需注入一个实现 WebMvcConfigurer 接口的类。
但其实截至目前为止添加的还只是一个原始版本的拦截器,真正使用的拦截器是适配后的版本。
适配的时机是在处理器映射器生命周期的 setApplicationContext 方法。(最后会调用到 initApplicationContext)
4、处理器适配器的参数解析器和结果处理器是如何来的?
答:在 RequestMappingHandlerAdapter bean生命周期的 afterPropertiesSet。
具体添加了哪些参数解析器,结果处理器就不细看了,太多了…
5、参数解析器/结果处理器中的消息转化器是如何来的?
答:来自处理器适配器,而处理器适配器的消息转化器来自 WebMvcConfigurer 配置或者默认配置。
如下图:
(参数解析器/结果处理器中的消息转化器来自处理器适配器)
(适配器的来自 WebMvcConfigurer 配置或者默认配置)
至于this.configurers.configureMessageConverters(converters);
首先configurers在前面第三个问题已经看过了,就是自动注入的 WebMvcConfigurer 总和。所以这句总的来说就是依次调用所有的 WebMvcConfigurer.configureMessageConverters 方法将自定义配置的消息添加到处理器适配器中。
相关文章:

spring5源码篇(12)——spring-mvc请求流程
spring-framework 版本:v5.3.19 文章目录 一、请求流程1、处理器映射器1.1、 RequestMappingHandlerMapping1.2、获取对应的映射方法1.3、添加拦截器 2、获取合适的处理器适配器3、通过处理器适配器执行处理器方法3.1、拦截器的前置后置3.2、处理器的执行3.2.1 参数…...

风辞远的科技茶屋:来自未来的信号枪
很久之前,有位朋友问我,现在科技资讯这么发达了,你们还写啊写做什么呢? 我是这么看的。最终能够凝结为资讯的那个新闻点,其实是一系列事情最终得出的结果,而这个结果又会带来更多新的结果。其中这些“得出”…...
MongoDB教程-8
ObjectId 在之前的所有章节中,我们一直在使用MongoDB的Object Id。在本章中,我们将了解ObjectId的结构。 ObjectId是一个12字节的BSON类型,具有以下结构-- 1. 前4个字节代表自unix epoch以来的秒数 接下来的3个字节是机器标识符 接下来的2…...
Redis 理论部分
前面写了很多redis项目,今天在通过redis的理论加深redis的了解,顺便做个总结 Redis 理论部分 1.redis 速度快的原因 纯内存操作单线程操作,避免频繁的上下文切换以及资源争用的问题,多线程需要占用更多的cpu资源采用非阻塞I/O多…...
Android—Monkey用法
文章目录 Monkey知识 Monkey知识 介绍 Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软…...

几个影响 cpu cache 性能因素及 cache 测试工具介绍
》内核新视界文章汇总《 文章目录 1 cache 性能及影响因素1.1 内存访问和性能比较1.2 cache line 对性能的影响1.3 L1 和 L2 缓存大小1.4 指令集并行性对 cache 性能的影响1.5 缓存关联性对 cache 的影响1.6 错误的 cacheline 共享 (缓存一致性)1.7 硬件设计 2 cpu cache benc…...

Java从入门到精通(二)· 基本语法
Java从入门到精通(二) 基本语法 一 变量 1.字面量 计算机是用来处理数据的,字面量就是告诉程序员:数据在程序中的书写格式。 特殊的字符: \n 表示换行, \t 表示一个制表符,即一个tab 2.变量…...
云安全攻防(三)之 面向云原生环境的安全体系
面向云原生环境的安全体系 根据云原生环境的构成,面向云原生环境的安全体系可包含三个层面的安全体制,它们分别是容器安全、编排系统安全和云原生应用安全,下面,我们逐步来讲解这三点: 容器安全 容器环境࿰…...

BGP汇总和破解水平分割
一,BGP的宣告问题 在BGP协议中每台运行BGP的设备上,宣告本地直连路由在BGP协议中运行BGP协议的设备来宣告,通过IGP学习到的,未运行BGP协议设备产生的路由; 在BGP协议中宣告本地路由表中路由条目时,将携带本…...

BUG:pm2启动verdaccio报错:Invalid or unexpected toke
输入命令: pm2 state verdaccio 问题描述: pm2 logs verdaccio报错翻译:数据格式错误 导致我呢提原因,没有找到运行文件, 发现问题:因为命令默认查找verdaccio是去系统盘查找。 解决方式 1:…...

Zookeeper笔记
为什么要使用Zookeeper dubbo需要一个注册中心,而Zookeeper是我们在使用Dubbo是官方推荐的注册中心 Zookeeper介绍 Zookeeper的集群机制 Zookeeper是为了其他分布式程序提供服务的,所以不能随便就挂了。Zookeeper的集群机制采取的是半数存活机制。也…...

【视觉SLAM入门】5.1. 特征提取和匹配--FAST,ORB(关键点描述子),2D-2D对极几何,本质矩阵,单应矩阵,三角测量,三角化矛盾
"不言而善应" 0. 基础知识1. 特征提取和匹配1.1 FAST关键点1.2 ORB的关键点--改进FAST1.3 ORB的描述子--BRIEF1.4 总结 2. 对极几何,对极约束2.1 本质矩阵(对极约束)2.1.1 求解本质矩阵2.1.2 恢复相机运动 R , t R,t R,…...

【能量管理系统( EMS )】基于粒子群算法对光伏、蓄电池等分布式能源DG进行规模优化调度研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

绘制Circos基因圈图
写在前面 昨天在绘制Circos圈图,已经隔了2年左右没有做这类的图了。这时间过得真是快,但是文章和成果依旧是没有很明显的成效。只能安慰自己,后面的时间继续加油吧!关于Cirocs图的制作,我从刚开始到现在都是是使用TBt…...

openGauss学习笔记-26 openGauss 高级数据管理-约束
文章目录 openGauss学习笔记-26 openGauss 高级数据管理-约束26.1 NOT NULL约束26.2 UNIQUE约束26.3 PRIMARY KEY26.4 FOREIGN KEY26.5 CHECK约束 openGauss学习笔记-26 openGauss 高级数据管理-约束 约束子句用于声明约束,新行或者更新的行必须满足这些约束才能成…...

学习React(四)
学习React(四) componentWillMount(被放弃使用)rendercomponentDidMountshouldComponentUpdate(nextProps,nextState)componentWillUpdate(被放弃使用)componentDidUpdatecomponentWillReceiveProps&#x…...
如何将单体项目拆分成微服务
1、如何将单体项目拆分成微服务 如何拆分微服务?其实对不同的业务项目场景,对应有不同的拆分方案。需要项目人员详细的分析项目需求、团队现状、业务边界、业务逻辑等方方面面,拆分的粒度既不能过细,也不能过粗,需要把…...

【Vue框架】Vuex状态管理
前言 在上一篇 【Vue框架】Vue路由配置 结尾时说到store.js,在代码里new Vuex.Store()传入了getters对象;本篇专门针对getters的内容进行整理。 1、getters.js 1.1 代码 // 用于存储获取状态的方法 const getters {// 这里的state参数,是…...

Linked List
文章目录 链表定义专业术语代码链表分类常见算法链表创建和常用算法 链表总结 链表 补充知识 typedef 给类型换名字,比如 typedef struct Student {int sid;char name[100];char sex; }ST;//ST就代表了struct Student //即这上方一大坨都可以用ST表示 //原先结构体…...
javascript数组基础
文章和代码已经归档至【Github仓库:https://github.com/timerring/front-end-tutorial 】或者公众号【AIShareLab】回复 javascript 也可获取。 文章目录 数组的基本使用定义数组和数组单元访问数组和数组索引数据单元值类型数组长度属性操作数组 数组:(…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...