SpringMVC源码深度解析(中)

        接上一遍博客《SpringMVC源码深度解析(上)》继续聊。最后聊到了SpringMVC的九大组建的初始化,以 HandlerMapping为例,SpringMVC提供了三个实现了,分别是:BeanNameUrlHandlerMapping、RequestMappingHandlerMapping、RouterFunctionMapping,其中最常用的就是 RequestMappingHandlerMapping,与@RequestMapping注解相关,以它为例,先看看类的继承图:

130df131cd6b4f899649c07e59227094.png

        可知该类实现了InitializingBean接口,这是Spring的扩展点,Bean对象在初始化的时候,会调用 RequestMappingHandlerMapping#afterPropertiesSet()方法。如果使用@EnableWebMvc注解或者手动往Spring容器中注入 RequestMappingHandlerMapping对象,Web容器在调用refresh()方法的过程中,会调用afterPropertiesSet()方法,如果不是通过以上两种方式的话,由上一篇博客 《SpringMVC源码深度解析(上)》可知,会读取dispatcherservlet.properties文件,获取默认的HandlerMapping的实现类,但是由于在前面已经调用了Web容器的refresh()方法,完成了容器的刷新,因此,这里需要特殊处理,代码如下:

c667fc6d4dbc4b6986c31a798e9878b7.png

aa6fd44d8300406baa73b21734bdf5c5.png

d61f5b8f4b4c4203ab6de72f00766b93.png

        可知,这里会显示的调用AutowireCapableBeanFactory#createBean()方法,才会经历Bean的实例化与初始化,才会调用 InitializingBean#afterPropertiesSet()方法。我专门提到这个方法,也侧面说明这个方法很重要,看看这个方法,代码如下:

d9b84c5e710d412a8c9a08fd41519fb5.png

0343cd486c47429a87cac516188b1c36.png

47addf5813f3427a95352d660b971e60.png

a7db417aae9c4d18acf3be0d92038314.png

        重点看看AbstractHandlerMethodMapping#getMappingForMethod()方法,代码如下:

0a86ddf09067421e9d366917bcef95f6.png

459a2689b116474bb763f5840e3f41c4.png

        再看看AbstractHandlerMethodMapping#registerHandlerMethod()方法,代码如下:

07e2baec26a747e889467b542de4de64.png

eedb5f6669b049aea481e17185a7e9b1.png

030b3f5080714aefafa2b31f8202edc7.png

        到这里为止就可以知道:RequestMappingHandlerMapping在初始化的时候,就会获取到所有被@Controller/@RequestMapping注解修饰的类,并且得到该类中所有被@Requestmapping注解修饰的方法,每个被@RequestMapping注解修饰的方法,最终生成一个对应的HandlerMethod对象(存储被@Controller/@RequestMapping注解修饰的对象和被@RequestMapping注解修饰的方法),并存储在内部类RequestMappingHandlerMapping.MappingRegistry的registry(Map)属性中,key为RequestMappingInfo对象,value为MappingRegistration对象(存储HandlerMethod对象)。

        聊完了RequestMappingHandlerMapping在初始化,再聊聊SpringMVC是如何处理请求。其实就是要搞清楚DispatcherServlet的原理,它是一个Servlet,在《SpringMVC源码深度解析(上)》中也说到了,创建了DispatcherServlet对象后,会添加到Servlet容器中。如果有请求来了,会解析url并匹配,如果是符合DispatcherServlet的匹配路径,则调用 GenericServlet#service()方法,这是一个抽象方法,因此实际调用的是HttpServlet#service()方法,代码如下:

df8ce42de145419eae7ed5cbfdb74626.png

5786dc6ea931490291ea4d35fb6e9f95.png

        以POST请求为例,则调用的是HttpServlet#doPost()方法,代码如下:

7cf0bb1a7b8840b5b477cb82ac756d03.png

        其实对于DispatcherServlet来讲,不管是哪种请求,最终都是调用FrameworkServlet#processRequest()方法,代码如下:

2c8ba08a6bfe4c6aad18dbd6e6752f54.png

8cf4766a9c11457e879eabe7a6764de8.png

d7ee9911e4954b109147fcd7f740f611.png

        DispatcherServlet#doDispatch()方法是处理请求的核心,会重点讲,代码如下:

0fae340fd041460c9a9b609b45e108cc.png

47bf6a5d9a744b54b4c80b8fa2e8d75e.png

fb8f78df6b54462a9b015115fe7aa275.png

95b168e8df68427682f2ee2c5dca5c1f.png

87dcea8e204a466db070d6683d1a286b.png

        在AbstractHandlerMethodMapping#lookupHandlerMethod()方法中,主要是进行路径的匹配,由于在前面RequestMappingInfoHandlerMapping初始化的时候,已经存储好了所有的匹配路径及其HandlerMethod,存放在 RequestMappingInfoHandlerMapping.MappingRegistry的pathLookup属性中,因此如果满足匹配要求,是可以获取到HandlerMethod对象的,这块就说了,涉及到匹配逻辑,比较复杂,感兴趣可以自己研究。回到AbstractHandlerMapping#getHandler()方法中,看看AbstractHandlerMapping#getHandlerExecutionChain()方法,代码如下:

d35f0f23aaf14c20a9f59202bb3a0c76.png

        可知这这个方法中,就是创建HandlerExecutionChain对象,并将HandlerInterceptor设置到HandlerExecutionChain对象的interceptorList属性中。再回到DispatcherServlet#doDispatch()方法中,代码如下:

610000c0dbe24c8386a65301a37b6423.png

fe99698349434210ac4f7a7295c0d21f.png

bb314fe87516442a89a90ad8a16a539a.png

3a0cc90b5ffd41b9b9933816ee0ed98e.png

        因此返回的HandlerAdapter对象是RequestMappingHandlerAdapter,这里当用到的是适配器设计模式,为什么要做适配呢?还记得之前HandlerMapping由三个实现类的,实际上处理的是三种不同的Controller,因此需要有对应的适配器处理不同的情况,看看HandlerAdapter#handle()方法就知道了,具体如下:

b865b6a9f7a54848870ae985a1493218.png

c10ce604743f434b917aaaf9d75f6b54.png

b93ca25bc2ab4f968b9d99e00eec9fbd.png

a041b5b6706e43f28f32d336b3f3b22a.png

        主要是处理这三种情况:

                ① 一种是被@Controller/@RestController/@RequestMapping注解修饰的类;

                ② 实现了HttpRequestHandler接口的类;

                ③ 实现了Controller接口的类;

        这三种情况的类,都属于Controller,因此需要不同的适配器处理,我主要讲①,剩下的两种,有兴趣可以试试。回到DispatcherServlet#doDispatch()方法继续往下看,代码如下:

2e281ea548cd4333a99090581cb7fab7.png

        执行拦截器的前置方法,就是遍历HandlerExecutionChain的interceptorList属性,执行HandlerInterceptor#preHandle()方法,代码如下:

327a2e5d938146bda0f30f07763642de.png

        执行拦截器的后置方法,也是如此,代码如下:

b4120edb01b3438f819bd9a0bbe4e455.png

        当然,如果执行某个拦截器的前置方法,返回的是 false,则直接return,不往下走了。在执行拦截器的前置方法后,后置方法前,会执行HandlerAdapter#handle()方法,代码如下:

1cf683bf4e2a4caaa494c89fcc7bf1a3.png

ed04b3f6c8274393b44fe68f7f833b90.png

381a585ef6d645f4a27bac4450870a8c.png

        顺便说一下,RequestMappingHandlerAdapter也实现了InitializingBean接口,因此也会实现afterPropertiesSet()方法,代码如下:

b33c2f17d23c4c83a7dcfbbbd155b31f.png

50903d44e8e5420d8d5b9120cabeaa34.png

795c878f1c7d45e4aa30958c55ec9a59.png

65a8168b433f40f3afabd977391e53bd.png

        这里设置的这些默认处理器,后续会用到,包括参数的解析、绑定、返回值的处理等。

        再看看 RequestMappingHandlerAdapter#invokeAndHandle()方法,这里是执行目标类中目标方法的关键,代码如下:

7949a157ccdf4ee79ca3d2d6d0568842.png

aa8eb9de3082453e950fa17035a0431a.png

6deb2c171efc43bfa8f9874ce0623886.png

6b132bbe69db4c9ba17ceb25fa4835c6.png

322377a70d88464ca2f070c50e91004a.png

        以PathVariableMethodArgumentResolver为例看看,代码如下:

61be92a661604d16b6cd5ea136b24aca.png

        回到InvocableHandlerMethod#getMethodArgumentValues()方法,看看是如何解析参数的,主要看:HandlerMethodArgumentResolverComposite#resolveArgument()方法,代码如下:

fc07f27ba80b41b694a9be88398c098d.png

        还是以PathVariableMethodArgumentResolver,看看它的resolveArgument()方法,代码如下:

0d4d72b5935044b1b6879d98e5d4e3ba.png

1f6c2c84c11d4c90bb5b0fd367f43680.png

636cbac0db064a599df2bc6735241e6b.png

b954217928e94758807639ac298a5ebe.png

        POST请求为:http://127.0.0.1:9000/hello/sayHello/张三

        79fbc0c3b4c44875bc57d8fef2bec266.png

55dc47e10b5447c89cc13fc3324727a7.png

9edce2b60db54d6d8013991a1ff2fd07.png

                最后将解析获取到的参数值,放入Request的Attribute中。当然,除了这个之外,常用的是在请求体中会放入Json数据,最后参数中接受的是一个对象,SpringMVC是怎么对Json格式的数据进行处理的呢?这里是通过RequestResponseBodyMethodProcessor处理的,代码如下:

e3340f428e054709aae96df8d64e5d10.png

9deb43c41b5e40b58ad954b9f40f1666.png

05a932d23f6b4cc7a62b61b683eab521.png

ab1c99404dde446d86e212f4e0905576.png        一共有八个转换器,这是在哪里添加的呢?后面讲@EnableWebMvc注解的时候会讲到,再看看HttpMessageConverter#read()方法,代码如下:

396e2ad309ff43de8660ee6bf05aec96.png

a293c84178db4b1291674bdd421541be.png

c7a26495dcfa417abd08d57838fe961d.png

4376064fe345496e94c9205c9a19368d.png

1706014d74ae428f831104c18dc401f7.png

6d0a43a5ff654a3eaf47b8193965e015.png

dc0faa3b0bd2406fb49e4736e32fe649.png

aeb2c91f27a846908f8b843703f30e74.png

        到这里可以知道,接收的Java对象,必须有Setter方法,因为这里是直接通过反射调用Setter方法进行赋值的。到这个为止,完成了Json的解析以及对象的赋值,即下图:

81848aa611e54b40880b854ab89f2544.png

        还有一些其他的注解,比如@RequestParam,感兴趣的自己研究。InvocableHandlerMethod#invokeForRequest(),参数处理完了,再调用InvocableHandlerMethod#doInvoke()方法,代码如下:        

182a3cee11144e4390e1a4225fc9ff16.png

847ebdc5a59d43188dc7901314e4017b.png

        反射调用玩目标方法后,会有返回值,也需要处理返回值。回到ServletInvocableHandlerMethod#invokeAndHandle()方法,代码如下:

522d85640998488c985457fd5502d189.png

28367dcba67c4c3195ada2bd10315ea5.png

3dc57b8f87264459afe31ad1e3a25a46.png

由于返回的是对象,因此需要处理,由于HelloController上添加的是@RestController,这个注解相当于@Controller+@ResponseBody,因此最终讲返回的对象反序列化为Json格式的数据,并返回给前端。这里还是需要看RequestResponseBodyMethodProcessor#handleReturnValue()方法,代码如下:

caa845cd98ae401ba99e3d8d4628327e.png

        该方法较长,我只截取重点讲。

61ce4b130bba429fbe0eb9a5fa6dec7d.png

93ab2179205d4a6791a67b162aabf63e.png

5f50a937721d4440a339a15ddf51920d.png

bbae688437cd45bf83340bac5deaf2d0.png

7a3ee708c2f94eb7abe6e0a3b1f2aaf8.png

be58d942fb7a484e9cb1c6e256ddec32.png

a296216657ba4e0dbb854c9ff545815c.png

30db83edcf234ed185f7c923eafbd339.png

7d4e4d3df5af4d0385b1a19dc2491beb.png

44aaa6652cc14f73abe7826250f10751.png

5700846bd782452d99844dae134da757.png

faadf2f4ac494f62a92207135b6b8c9f.png

ce1b8884fa1a4005816d242627a7b64f.png

800e6cdc836741cfb697ec7bfdbc5cb4.png

763d2830189f401e9ee466bbe7a68d63.png69e940f440f245e9a0cec55a0b7f7888.png

        到这里为止,把返回的对象进行序列化,只是将其转成字符并缓存起来,回到ObjectWriter#writeValue()方法,代码如下:

350e52530add4abc8a87f900faec8db3.png

8968041089af49fa8f0a29329e48c69b.png

2a72ff28175243409bff75d10097e30d.png

        到这里为止,SpringMVC的返回值处理讲完了,SpringMVC剩下的内容将在下一篇博文中讲,敬请期待~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

相关文章

AI一站式科研写作利器:PaperPal

文章目录 在线编辑Word 插件 在线编辑 PaperPal是一款面向科研人员的写作工具,支持在线编辑以及Word插件。下面以在线编辑为例,详述PaperPal的润色功能。 进入网页后,点击【新建在线文档】,即可开启润色功能,界面如下…

《昇思25天学习打卡营第24天|基于 MindSpore 实现 BERT 对话情绪识别》

1. BERT 模型概述 BERT(Bidirectional Encoder Representations from Transformers)是一个预训练的语言表示模型,可以应用于多种自然语言处理任务,包括对话情绪识别。BERT 通过双向 Transformer 编码器,能同时考虑句子…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 图像物体的边界(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

word 设置目录中英文字母大写改为小写

选中目录右击“字体”,设置“小型大写字母”。

【Vue3】工程创建及目录说明

【Vue3】工程创建及目录说明 背景简介开发环境开发步骤及源码 背景 随着年龄的增长,很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来,技术出身的人总是很难放下一些执念,遂将这些知识整理成文,以纪念曾经努力学习奋斗的日…

【LeetCode】从中序与后序遍历序列构造二叉树

目录 一、题目二、解法完整代码 一、题目 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7], …

谷粒商城实战笔记-40-前端基础-Vue-计算属性、监听器、过滤器

文章目录 一,计算属性1,用途2,用法2.1 定义View2.2 声明计算属性 3,注意事项 二,监听器1. 使用 watch 监听属性的变化 三,过滤器1,定义局部过滤器2,定义全局过滤器3,使用…

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(六)-无人机直接C2通信

目录 引言 5.4 直接C2通信 5.4.1 概述 5.4.2 A2X直接C2通信服务的授权策略 5.4.3 USS使用A2X直接C2通信服务的C2授权程序 5.4.4 直接C2通信建立程序 引言 3GPP TS 23.256 技术规范,主要定义了3GPP系统对无人机(UAV)的连接性、身份识别…

bs4模块使用(一)

1. 安装BS4 pip install beautifulsoup4 pip install lxml2. 对象种类 bs4将html文档解析成一个树形结构,每个节点都是python对象,大概可分为下面四种: 2.1 Tag 后面再详细介绍,先介绍最重要的两个属性 name soup Beautiful…

数据结构——队列(链式结构)

一、队列链式结构定义 队列的链式存储结构是一种用链表实现的队列,它不像顺序存储结构那样需要预先分配固定大小的空间。链式存储结构的队列由节点组成,每个节点包括数据和指向下一个节点的指针。队列的链式存储结构可以动态地分配内存,更灵活地处理数据。在链式存储结构中…

【java SE语法篇】1.运算符

目录 1. 运算符和表达式2. 算数运算符3. 隐式转换4. 强制转换5. 自增自减运算符6. 赋值运算符7. 扩展运算符8. 关系运算符9. 逻辑运算符9.1 & 和 | 的使用:9.2 ^(异或)的使用:9.3 !(取反)的使用&#x…

监控易直播回放2407期:IT监控平台可视化运维

主播:监控易技术经理 刘美玲 大家好,现在是7月18号下午2点,欢迎大家来到本次的直播分享。今天,我们将深入探讨IT监控平台的可视化实现。在这个信息化时代,IT设备的稳定性对于业务的连续性起着至关重要的作用。而IT监控…

Unity入门——引擎窗口

主要内容 1.窗口布局 2.Hierarchy层级窗口 3.Scene场景窗口 窗口布局 右上角layout选项 层级和场景窗口 Scene和Hierarchy 场景窗口和层级窗口是息息相关的,层级窗口中看到的内容就是场景窗口中的显示对象。 Hierar层级窗口 我们可以在Hierarchy窗口中创建…

项目部署上线(跨域问题的处理)

前言: 项目想要上线,前提是肯定需要一台服务器 我下面使用的是腾讯服务器和宝塔Linux的管理工具来进行项目的部署上线 服务器配置(初始化)-CSDN博客 项目部署: 我原来那篇文章写了用nginx的原生部署,不过我觉得那…

数据闭环的核心-Auto-labeling方案分享

1. 简介 BEV算法的开发已经到了深水区,各家都投入了大量的精力去做bev的落地开发,其中一块最关键的就是如何高效的完成BEV的数据标注,无论是BEV 3D 目标,BEV 去高精地图或者是BEV Occupancy。 相比于车端的感知算法,…

exo 大模型算力共享;Llama3-70B是什么

目录 exo 大模型算力共享 exo框架的特点 如何使用exo框架 注意事项 结论 Llama3-70B是什么 一、基本信息 二、技术特点 三、性能与应用 四、未来发展 exo 大模型算力共享 exo框架的特点 异构支持:支持多种不同类型的设备,包括智能手机、平板电脑、笔记本电脑以及高…

java之log4j反序列化

1 审计思路 以Log4j漏洞审计为案例,谈一谈审计如何快速的锁定通用型漏洞 1.1 确定源码是否引用了漏洞所属的开源组件 该项目是一个maven项目,直接在Pom文件中搜索log4j的jar包及版本引用问题,如果该版本受影 响,进入下一步 1.2 寻找漏洞的入口 1.3 逐个排查入口是否有效…

[MySQL][深入理解隔离性][上][MVCC]详细讲解

目录 0.铺垫1.初识MVCC2.三个记录隐藏列字段1.是什么?2.示例 3.undo日志4.模拟MVCC5.思考 0.铺垫 在RR级别的时候,多个事务的update,多个事务的insert,多个事务的delete,是否会有加锁现象? 现象结果是&…

[计算机基础]一、计算机组成原理

计算机组成原理的考察目标为: 1. 掌握单处理器计算机系统中主要部件的工作原理、组成结构以及相互连接方式。 2. 掌握指令集体系结构的基本知识和基本实现方法,对计算机硬件相关问题进行分析,并能够对相关部件进行设计。 3. 理解计算机系统的…

MVCC数据库并发控制技术

一、引言 MVCC(Multi-Version Concurrency Control)是一种广泛使用的数据库并发控制技术,它允许数据库读操作和写操作并发执行,而无需加锁整个表或行,从而大大提高了数据库的并发性能和吞吐量。MVCC主要被应用于支持事…