【Spring6源码・MVC】请求处理流程源码解析
上一篇《【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这个方法是主工作线程运行循环。反复从队列中获取任务并执行它们,同时处理许多问题:
- 我们可以从初始任务开始,在这种情况下,我们不需要获取第一个任务。否则,只要池正在运行,我们就从getTask获取任务。如果它返回 null,则工作线程由于池状态或配置参数更改而退出。其他退出是由外部代码中的异常抛出引起的,在这种情况下,complete突然持有,这通常会导致processWorkerExit替换此线程。
- 在运行任何任务之前,获取锁以防止在任务执行时出现其他池中断,然后我们确保除非池停止,否则该线程没有设置中断。
- 每次任务运行之前都会调用 beforeExecute,这可能会引发异常,在这种情况下,我们会导致线程死亡(以 completeA 突然为 true 中断循环)而不处理任务。
- 假设 beforeExecute 正常完成,我们运行任务,收集其抛出的任何异常以发送到 afterExecute。我们分别处理 RuntimeException、Error(规范保证我们捕获这两个错误)和任意 Throwable。因为我们无法在 Runnable.run 中重新抛出 Throwable,所以我们在出路时将它们包装在 Errors 中(到线程的 UncaughtExceptionHandler)。任何抛出的异常也保守地导致线程死亡。
- 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 对象。
其他的暂且先不提了,都比较简单,点点看看就行了。
相关文章:
【Spring6源码・MVC】请求处理流程源码解析
上一篇《【Spring6源码・MVC】初始化registry,完成url和controller的映射关系》我们知道,在IOC容器加载的同时,初始化了registry这个HashMap,这个HashMap中存放了请求路径和对应的方法。当我们请求进来,会通过这个regi…...
elasticsearch term match 查询
1. 准备数据 PUT h1/doc/1 {"name": "rose","gender": "female","age": 18,"tags": ["白", "漂亮", "高"] }PUT h1/doc/2 {"name": "lila","gender&quo…...
canal使用说明:MySQL、Redis实时数据同步
1. canal简介 canal是阿里开源的数据同步工具,基于bin log可以将数据库同步到其他各类数据库中,目标数据库支持mysql,postgresql,oracle,redis,MQ,ES等 canal分成服务端deployer和客户端adapter,我们可以部署多个,同时为了方便管…...
计算机视觉框架OpenMMLab开源学习(三):图像分类实战
前言:本篇主要偏向图像分类实战部分,使用MMclassification工具进行代码应用,最后对水果分类进行实战演示,本次环境和代码配置部分省略,具体内容建议参考前一篇文章:计算机视觉框架OpenMMLab开源学习&#x…...
awk命令
一.介绍 awk是专门为文本处理设计的编程语言,是一门数据驱动的编程语言。与sed类似,都是以数据驱动的行处理软件,主要用于数据扫描,过滤和汇总。数据可以来自于标准输入,管道或者文件。 二.语法 awk是一种处理文本文件…...
LocalDateTime获取时间的年、月、日、时、分、秒、纳秒
如何把String/Date转成LocalDateTime参考String、Date与LocalDate、LocalTime、LocalDateTime之间互转 String、Date、LocalDateTime、Calendar与时间戳之间互相转化参考String、Date、LocalDateTime、Calendar与时间戳之间互相转化 方法介绍 getYear() 获取日期的年 getMon…...
MoveIT Rviz和Gazebo联合仿真
文章目录环境安装概述ros_control框架ros_control数据流文件配置附加工具故障问题解决参考接前两篇:ROS MoveIT1(Noetic)安装总结 Solidworks导出为URDF用于MoveIT总结(带prismatic) MoveIT1 Assistant 总结 环境 Ubu…...
ESP32S2(12K)-DS18B20数码管显示温度
一、物料清单: NODEMCU-32-S2 (ESP32-12K)四段数码管(共阴)DS18B20(VCC/DQ/GND)Arduino-IDE 2.0.3二、实现方法及效果图: 2.1 引用库 // #include <OneWire.h> //可以不引入,因为DallasTemperature.h中已经引入了OneWire.h #include <DallasTemperature.h>#…...
linux栈溢出定位
一、编译选项定位堆栈溢出 来源:堆栈溢出检测机制 - SkrSky - 博客园 1、栈溢出可能打印 unhandled level 1 translation fault (11) at 0x7f8d0347, esr 0x92000005 2、栈溢出保护机制 gcc提供了栈保护机制stack-protector(编译选项-fstack-protec…...
CSS基础:选择器和声明样式
CSS概念 CSS(Cascading Style Sheets)层叠样式表,又叫级联样式表,简称样式表 CSS用于HTML文档中元素样式的定义 使用css让网页具有美观一致的页面 语法 CSS 规则由两个主要的部分构成:选择器和声明样式 选择器通常…...
VS中安装gismo库
文章目录前言一、下载安装paraview直接下载压缩包安装就可以了解压后按步骤安装即可二、gismo库的安装gismo库网址第一种方法:第二种方法第三种方法:用Cmake软件直接安装首先下载cmake软件[网址](https://cmake.org/download/)安装gismo库三、gismo库的使…...
元学习方法解决CDFSL以及两篇SOTA论文讲解
来源:投稿 作者:橡皮 编辑:学姐 带你学习跨域小样本系列1-简介篇 跨域小样本系列2-常用数据集与任务设定详解 跨域小样本系列3:元学习方法解决CDFSL以及两篇SOTA论文讲解(本篇) 跨域小样本系列4…...
大数据之------------数据中台
一、什么是数据中台 **数据中台是指通过数据技术,对海量数据进行采集、计算、存储、加工,同时统一标准和口径。**数据中台的目标是让数据持续用起来,通过数据中台提供的工具、方法和运行机制,把数据变为一种服务能力,…...
Python 中 字符串是什么?
字符串是 Python 中最常用的数据类型。我们可以使用引号 ( ’ 或 " ) 来创建字符串。 创建字符串很简单,只要为变量分配一个值即可。例如: var1 ‘Hello World!’ var2 “Python Runoob” Python 访问字符串中的值 Python 不支持单字符类型&…...
OJ刷题Day1 · 一维数组的动态和 · 将数字变成 0 的操作次数 · 最富有的客户资产总量 · Fizz Buzz · 链表的中间结点 · 赎金信
一、一维数组的动态和二、将数字变成 0 的操作次数三、最富有的客户资产总量四、Fizz Buzz五、链表的中间结点六、赎金信一、一维数组的动态和 给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] sum(nums[0]…nums[i]) 。 请返回 nums 的动态和。 示…...
【数据结构】栈——必做题
逆波兰表达式后缀表达式的出现是为了方便计算机处理,它的运算符是按照一定的顺序出现,所以求值过程中并不需要使用括号来指定运算顺序,也不需要考虑运算符号(比如加减乘除)的优先级。先介绍中简单的人工转化方法&#…...
LearnOpenGL 笔记 - 入门 04 你好,三角形
系列文章目录 LearnOpenGL 笔记 - 入门 01 OpenGLLearnOpenGL 笔记 - 入门 02 创建窗口LearnOpenGL 笔记 - 入门 03 你好,窗口 文章目录系列文章目录前言你好,三角形顶点输入顶点着色器(Vertex Shader)编译着色器片段着色器&…...
keepalived+mysql高可用
一.设置mysql同步信息两节点安装msyql略#配置节点11.配置权限允许远程访问mysql -u root -p grant all on *.* to root% identified by Root1212# with grant option; flush privileges;2.修改my.cnf#作为主节点配置(节点1)#作为主节点配置 server-id 1 …...
JAVA工具篇--1 Idea中 Gradle的使用
前言: 既然我们已经使用Maven 来完成对项目的构建,为什么还要使用Gradle 进行项目的构建;gradle和maven都可以作为java程序的构建工具,但两者还是有很大的不同之处的:1.可扩展性,gradle比较灵活,…...
弄懂自定义 Hooks 不难,改变开发认知有点不习惯
前言 我之前总结逻辑重用的时候,就一直在思考一个问题。 对于逻辑复用,render props 和 高阶组件都可以实现,同样官方说 Hooks 也可以实现,且还是在不增加额外的组件的情况下。 但是我在项目代码中,没有找到自定义 …...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
