【Spring6源码・MVC】请求处理流程源码解析

news2024/11/24 14:33:25

上一篇《【Spring6源码・MVC】初始化registry,完成url和controller的映射关系》我们知道,在IOC容器加载的同时,初始化了registry这个HashMap,这个HashMap中存放了请求路径和对应的方法。当我们请求进来,会通过这个registry去获取对应的方法。

在SpringBoot项目启动的时候,会加载一个线程:

在这里插入图片描述
步入run方法:

在这里插入图片描述
步入run方法:

当前这个类是Worker:Class Worker 主要维护运行任务的线程的中断控制状态,以及其他次要簿记。此类适时地扩展了 AbstractQueuedSyncer,以简化获取和释放围绕每个任务执行的锁。这可以防止旨在唤醒等待任务的工作线程而不是中断正在运行的任务的中断。我们实现了一个简单的非重入互斥锁,而不是使用 ReentrantLock,因为我们不希望工作线程任务在调用池控制方法(如 setCorePoolSize)时能够重新获取锁。此外,为了在线程实际开始运行任务之前抑制中断,我们将锁定状态初始化为负值,并在启动时清除它(在 runWorker 中)。

这个run方法主要是启动主运行循环,以此从队列中取出task执行。

在这里插入图片描述

当我们发送一个请求时:http://localhost:8081/user/test

会调用这个runWorker方法中的task.run()

runWorker这个方法是主工作线程运行循环。反复从队列中获取任务并执行它们,同时处理许多问题:

  1. 我们可以从初始任务开始,在这种情况下,我们不需要获取第一个任务。否则,只要池正在运行,我们就从getTask获取任务。如果它返回 null,则工作线程由于池状态或配置参数更改而退出。其他退出是由外部代码中的异常抛出引起的,在这种情况下,complete突然持有,这通常会导致processWorkerExit替换此线程。
  2. 在运行任何任务之前,获取锁以防止在任务执行时出现其他池中断,然后我们确保除非池停止,否则该线程没有设置中断。
  3. 每次任务运行之前都会调用 beforeExecute,这可能会引发异常,在这种情况下,我们会导致线程死亡(以 completeA 突然为 true 中断循环)而不处理任务。
  4. 假设 beforeExecute 正常完成,我们运行任务,收集其抛出的任何异常以发送到 afterExecute。我们分别处理 RuntimeException、Error(规范保证我们捕获这两个错误)和任意 Throwable。因为我们无法在 Runnable.run 中重新抛出 Throwable,所以我们在出路时将它们包装在 Errors 中(到线程的 UncaughtExceptionHandler)。任何抛出的异常也保守地导致线程死亡。
  5. task.run 完成后,我们调用 afterExecute,这也可能会抛出异常,这也会导致线程死亡。根据 JLS Sec 14.20,即使 task.run 抛出,此异常也会生效。异常机制的净效果是,afterExecute 和线程的 UncaughtExceptionHandler 具有尽可能准确的信息,我们可以提供有关用户代码遇到的任何问题的信息。

在这里插入图片描述
当我们发送http://localhost:8081/user/test请求时,步入这个task.run()方法中:

在这里插入图片描述

步入doRun方法中:

在这里插入图片描述



最终经过数十个调用,会在一个filterChanin链中调用servlet.service(request, response)方法:

在这里插入图片描述
然后会判断当前请求中的方法是否包含规定的方法:HTTP_SERVLET_METHODS.contains(request.getMethod())

在这里插入图片描述
然后会调用doGet方法:
在这里插入图片描述
之后调用经典的doService方法:

在这里插入图片描述
步入doService方法之后,又会调用doDispatch方法:

在这里插入图片描述
看一看这个核心方法:doDispatch。

在这里插入图片描述

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	...
	try {
		...
		try {
			// 检查是否文件请求
			processedRequest = checkMultipart(request);
			...
			// 根据请求找到对应的控制器执行器链HandlerExecutionChain 
			mappedHandler = getHandler(processedRequest);
			...
			// 找到对应控制器的适配器,用来执行操作的
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
			...
			// 执行拦截器前置
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// 真正执行控制器逻辑
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			...
			// 执行拦截器后置
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		...
		// 处理返回结果
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	...
}

先看checkMultipart(request)方法,它是用来检查是否是文件上传,如果有文件上传,则将request包装成StandardMultipartHttpServletRequest

关于mappedHandler = getHandler(processedRequest);HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());这两个方法,和前面AOP讲到的获取拦截器和适配器的逻辑差不多,循环去匹配,这里就不展开了。

重点看一下mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这个真正执行控制器逻辑的方法。

步入handle方法:最终来到这:

在这里插入图片描述

步入invokeHandlerMethod方法:

首先创建ServletInvocableHandlerMethod对象,再去调用。

在这里插入图片描述
步入invokeAndHandle方法:

首先去执行控制器的逻辑,如果有结果再去处理结果。

在这里插入图片描述

看看如何执行控制器的逻辑的,步入invokeForRequest方法:

首先获取方法参数,再去invoke:

如何实现的就不看了吧,最后肯定是调用本地方法去执行控制器的逻辑。

感兴趣可以翻一翻我的博客,有一篇是如何解析本地方法的。可以看看cpp是如何实现的。
在这里插入图片描述
最后,我们能得到返回的结果:

在这里插入图片描述
最后会去调用这行代码:

this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

步入handleReturnValue方法:

在这里插入图片描述
步入handleReturnValue方法:

在这里插入图片描述
步入writeWithMessageConverters方法:

最后经过层层包装,调用((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);方法:

在这里插入图片描述
步入write方法:

在这里插入图片描述
步入writeInternal方法:

在这里插入图片描述
步入copy方法:

落叶归根,结束。

在这里插入图片描述
其实关于SpringMVC还有很多没写,但是首先知道这个流程就基本够用了,之后用到哪些的哪些的时候,再按着这个流程看就好。

值得一提的是我们这个demo没有文件,也没有参数,所以撸流程还是很容易的,如果有参数还要注意是如何解析参数的,如果用@RequestParam注解的话,直接通过反射就可以获取到,Spring也提供了处理这个注解的解析器,如果不加注解,会默认使用名称绑定,底层用asm框架读取字节码来获取参数名称,所以编码记得用@RequestParam声明参数,之后会放进一个缓存数组中,在ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);代码执行时,就会封装成ServletInvocableHandlerMethod 对象。

其他的暂且先不提了,都比较简单,点点看看就行了。

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

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

相关文章

合并两个有序链表-力扣21-java双百方案

一、题目描述将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1:输入:l1 [1,2,4], l2 [1,3,4]输出:[1,1,2,3,4,4]示例 2:输入:l1 [], l2 []输出&#xff1…

C++中编译静态库与动态库

1.库的理解库就是写好的现有的,成熟的,可复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,是预编译代码的集…

【Vue3】element-plus中el-tree的递归处理赋值回显问题

目录一:先获取所有权限tree二:在获取所有该角色能有的权限tree三:递归处理勾选tree节点由于项目是从0-1开始构建的 rbac都需要重新构建对接 所以涉及到了权限管理和菜单管理 一级菜单包含多个二级菜单 若二级不全选,则一级显示 半…

scipy超几何函数

文章目录hyp2f1广义超几何函数其他超几何函数hyp2f1 当c不是0,−1,⋯0,-1,\cdots0,−1,⋯时&#xff0c;对于∣z∣<1|z|<1∣z∣<1&#xff0c;超几何函数可表示为 2F1(a;b;c;z)∑n0∞a(n)b(n)c(n)znn!_2F_1(a;b;c;z)\sum^\infty_{n0}\frac{a^{(n)}b^{(n)}}{c^{(n)}}\…

TOOM告诉你企业舆情监测的重要性,企业舆情监测的意义

企业舆情监测是一种有效的企业管理手段&#xff0c;能够帮助企业了解舆情信息&#xff0c;从而更好地管理企业、保护企业利益&#xff0c;TOOM告诉你企业舆情监测的重要性&#xff0c;企业舆情监测的意义。 一、企业舆情监测的重要性 声誉管理&#xff1a;通过对企业在线和离…

pixhawk2.4.8使用调试记录—APM固件

目录一、硬件准备二、APM固件、MP地面站下载三、地面站配置1 刷固件2 机架选择3 加速度计校准4 指南针校准5 遥控器校准6 飞行模式7 紧急断电&无头模式8 基础参数设置9 电流计校准10 电调校准11 起飞前检查&#xff08;每一项都非常重要&#xff09;12 飞行经验四、遇到的问…

ESP8266_Linux环境搭建

工具链设置 适用于 Linux 的 ESP8266 工具链可从 Espressif 网站下载&#xff1a; 对于 64 位 Linux&#xff1a; https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz 对于 32 位 Linux&#xff1a; https://dl.espressif.com/dl/xten…

web自动化使用xpath轴定位

目录 XPath 轴&#xff08;Axes&#xff09; 一、定义&#xff1a;轴可定义相对于当前节点的节点集。 二、语法&#xff1a; 一、ancestor 选取当前节点的所有先辈(父&#xff0c;祖父等) 二、ancestor-or-self&#xff1a; 选取当前节点的所有先辈&#xff08;父、祖父等…

QT(11)- QThread

1 简介 QThread&#xff1a;具有可选事件循环的低级 API QThread是 Qt 中所有线程控制的基础。每个QThread实例表示并控制一个线程。 QThread可以直接实例化&#xff0c;也可以子类化。实例化QThread提供了一个并行事件循环&#xff0c;允许在辅助线程中调用QObject插槽。对 …

Leetcode力扣秋招刷题路-0037

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 37. 解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字…

React hooks之useEffect《类比Vue来记忆》(二)

系列文章目录 下面是正文 文章目录系列文章目录前言一、useEffect的三种形态1.useEffect不传第二个参数代码如下&#xff1a;效果图如下&#xff1a;2.useEffect第二个参数传 []代码如下&#xff1a;效果图如下&#xff1a;3.useEffect第二个参数传 [num]代码如下&#xff1a;效…

java图

1 图基本介 1.1 为什么要有图 前面我们学了线性表和树线性表局限于一个直接前驱和一个直接后继的关系树也只能有一个直接前驱也就是父节点当我们需要表示多对多的关系时&#xff0c; 这里我们就用到了图。 1.2 图的举例说明 图是一种数据结构&#xff0c;其中结点可以具有零…

VL10 使用函数实现数据大小端转换

一、function和task都是为了模块化、结构化设计&#xff0c;主要还是将重复性的功能封装起来方便调用。可以对返回值类型和范围不进行定义&#xff0c;默认值为reg型并且位宽为1变量类型说明 比如integer ifunction(其功能同之前的module模块类似)通常是用来描述组合逻辑&#…

Hi3861编译烧录更快捷

HUAWEI DevEco Device Tool是华为面向智能设备开发者提供的一站式集成开发环境。划重点&#xff0c;DevEco Device Tool 3.1 Beta2又上新技能啦——支持纯Windows环境开发Hi3861&#xff0c;显著提升编译、烧录效率&#xff0c;同时还带来了更多实用的功能及模板&#xff0c;为…

介绍项目前期调研、需求分析阶段的工作

title: 介绍项目前期调研、需求分析阶段的工作 date: 2019-07-07 16:06:00 tags: 需求分析前期调研 categories:架构 立项阶段 所谓立项就是公司内部进行研究、讨论决定要不要做这个事情&#xff0c;通常立项分成两个大类&#xff1a; 项目立项 相对比较简单&#xff0c;需…

欧几里得度量和余弦度量的可取消生物识别方案

欧几里得度量和余弦度量的可取消生物识别方案 便捷的生物识别数据是一把双刃剑&#xff0c;在为生物识别认证系统的繁荣铺平道路的同时&#xff0c;也带来了个人隐私问题。为了缓解这种担忧&#xff0c;提出了各种生物特征模板保护方案来保护生物特征模板免于信息泄露。现有提案…

大道至简 初识springboot

参考文档&#xff1a;springboot官方中文文档 开发工具&#xff1a;IntelliJ IDEA 入门 springboot介绍 Spring Boot帮助你创建可以运行的独立的、基于Spring的生产级应用程序。 我们对Spring平台和第三方库采取了有主见的观点&#xff0c;这样你就能以最少的麻烦开始工作。 …

Service基础使用

Service简介 Service是什么 Service是一个应用组件&#xff0c;它用来在后台完成一个时间跨度比较大的工作&#xff0c;且没有关联任何界面。 Service的生命周期方法在主线程运行。 使用场景 service用于在后台完成用户指定的操作。 访问网络&#xff1b;播放音乐&#xf…

指针的步长及意义(C语言基础)

指针的步长及意义 文章目录指针的步长及意义指针变量1后偏移的字节数不同指针[解引用](https://so.csdn.net/so/search?q解引用&spm1001.2101.3001.7020)时取出的字节数不同其他例子不同类型的指针有何不同的意义指针变量1后跳跃字节数量不同解引用的时候&#xff0c;取出…

虹科方案 | 制药环境中冰箱温度记录的最佳实践——全集成温度监测系统

有效监测冰箱温度是药店、医疗中心和制药实验室的一项重要要求。保持准确的冰箱温度记录对所有储存处方药和疫苗的设施来说是必不可少的&#xff0c;但实现这一目标的最佳方法是什么&#xff1f;● 制药机构需要在特定的温度下储存疫苗和处方药&#xff0c;以保证病人的安全并确…