Spring Boot 从“会用”到“精通”:SpringBoot MVC 请求处理全流程

Spring Boot MVC 请求处理全流程

前言

前面我们学了自动装配、请求映射、参数解析、返回值处理、内容协商......每一个都是独立的知识点。但如果不能把它们串成一条线,这些知识就像是散落一地的精密零件------你知道每个零件是干什么的,却拼不出一台运转的发动机。

这一篇的目标就是:把散落的知识点串联成一条完整的流水线

Spring Boot 的底层虽然做了大量的自动装配,但其 Web 核心依然是 Spring MVC。我们可以把整个 HTTP 请求的处理过程,分为 五个核心阶段:从容器启动时的"基础设施建设",到请求到达时的"精准路由",再到核心的"参数解析"与"执行响应"。


第一阶段:项目启动与基础设施初始化

在应用启动时,Spring Boot 并非静止等待,而是预先搭建好了整个处理引擎。

1. 自动装配介入

@SpringBootApplication 触发 WebMvcAutoConfiguration。它会自动向 IoC 容器中注入 DispatcherServlet------这是整个 Spring MVC 的"总调度中心"。

2. 构建映射路由表(HandlerMapping)

Spring 会扫描所有带有 @Controller@RestController 的 Bean,解析其中的 @RequestMapping(及其变体)注解,将 URL 路径具体的目标方法(HandlerMethod) 一一绑定,注册到 RequestMappingHandlerMapping 中。这就好比编译器在处理代码前先构建好了一张精确的符号表------请求还没来,路由表已经就绪。

3. 准备解析器兵工厂(HandlerAdapter)

初始化 RequestMappingHandlerAdapter。这个适配器内部装载了几十个内置的 参数解析器(HandlerMethodArgumentResolver返回值处理器(HandlerMethodReturnValueHandler 。同时,还会初始化一系列的 HttpMessageConverter(比如负责 JSON 转换的 Jackson 转换器),为后续的内容协商做准备。

关键认知:此时还没有任何请求进来。Spring Boot 只是把"武器库"准备好了------路由表、适配器、解析器、转换器全部就位,就等请求到达。


第二阶段:请求到达与精确路由

假设前端发来了一个 POST 请求,携带了 JSON 数据。

1. 进入 DispatcherServlet

请求穿过 Tomcat 的 Filter 链,进入 Spring MVC 的大门------DispatcherServlet.doDispatch() 方法。这是所有流程的"总指挥"。

你可能记得原生的 Servlet 是 doGet()doPost(),但在 Spring 的 FrameworkServlet 中,它们都被重写并统一收口到 processRequest()doService()doDispatch()

2. 查找执行链(getHandler)

DispatcherServlet 拿着请求的 URL,去 RequestMappingHandlerMapping 中匹配。匹配成功后,返回的不仅仅是那个目标方法,而是一个 HandlerExecutionChain(处理器执行链)。这个链条里包含了:

  • 目标 HandlerMethod(你要执行的 Controller 方法)
  • 所有配置好的拦截器(HandlerInterceptor

拦截器的 preHandle 方法会在这里率先执行------如果任何一个拦截器返回 false,请求直接终止。

3. 获取适配器(getHandlerAdapter)

有了处理器,还需要一个能"执行"它的适配器。DispatcherServlet 遍历所有已注册的 HandlerAdapter,调用 adapter.supports(handler) 逐个询问。对于 @RequestMapping 标注的方法,命中的始终是 RequestMappingHandlerAdapter

设计模式:这一步也是遍历匹配------和找 HandlerMapping、找 ArgumentResolver 完全一样的套路。Spring 把"遍历查找最匹配的那个"这个模式用到了极致。


第三阶段:参数解析与绑定

这是整个流程中最精妙的部分。你的 Controller 方法可能声明了各种各样的参数(对象、字符串、上传的文件),Spring 是如何精准赋值的?

1. 文件上传预处理(如果有)

如果请求是 multipart/form-dataDispatcherServlet 会在最开始调用 checkMultipart(),通过 MultipartResolver 将原始的 HttpServletRequest 包装成 MultipartHttpServletRequest,方便后续提取文件流。如果不是文件上传,这一步直接跳过。

2. 遍历参数解析器(策略模式)

Spring 反射获取目标方法的所有参数签名。对于每一个参数 ,都会去遍历那庞大的 HandlerMethodArgumentResolver 列表:

  • 步骤 A:supportsParameter(MethodParameter)

    • 解析器依次被询问:"你能处理这个参数吗?"
    • 当遇到带有 @RequestBody 的参数时,RequestResponseBodyMethodProcessor 检查该参数是否有这个注解 → 有 → 返回 true → 匹配成功,停止遍历。
    • 如果是普通的查询参数,则匹配到 RequestParamMethodArgumentResolver
  • 步骤 B:resolveArgument(...)

    • 确定了解析器后,调用此方法真正开始解析。

3. 消息转换与数据绑定

这是两条截然不同的路径,也是整个 Spring MVC 中最容易混淆的地方:

参数类型 谁来解析 谁来转换
@RequestBody(JSON 流) RequestResponseBodyMethodProcessor HttpMessageConverter (如 Jackson),读取 InputStream,反序列化整个 JSON → Java 对象
@RequestParam / @PathVariable / 表单 POJO 对应的参数解析器 WebDataBinder → 调用 ConversionService 中的 Converter<S, T> 进行单字段 String → 目标类型的转换

一句话区分 :处理"一整坨 JSON"用 HttpMessageConverter (流处理阵营);处理"散装 K-V 参数"用 WebDataBinder + Converter<S, T> (KV 参数阵营)。它们井水不犯河水,互不调用。

对于 @RequestBody,解析出来的值(已经被 Jackson 反序列化好的 Java 对象)最终被放入参数数组。对于 @RequestParam / 表单 POJO,WebDataBinder 逐字段完成类型转换和绑定后,同样放入参数数组。


第四阶段:业务执行与返回值处理

1. 反射调用

参数全部就绪后,适配器通过反射 method.invoke(bean, args) 正式执行你编写的 Controller 业务逻辑。到这一步,你的代码才开始运行。

2. 寻找返回值处理器

业务逻辑执行完毕,返回了一个结果。与参数解析的套路完全一样------Spring 再次遍历 HandlerMethodReturnValueHandler 列表,调用 supportsReturnType 逐个询问:

  • 返回了 String 且没有 @ResponseBody?→ ViewNameMethodReturnValueHandler,把返回值当视图名
  • 返回了 ModelAndView?→ ModelAndViewMethodReturnValueHandler
  • 类上有 @RestController 或方法上有 @ResponseBody?→ RequestResponseBodyMethodProcessor

第五阶段:内容协商与响应写入

1. 两条路的分岔口

复制代码
Controller 返回结果
    ├── 走视图(无 @ResponseBody)→ ViewResolver 解析视图名 → View.render() 渲染 HTML → 响应
    └── 走数据(有 @ResponseBody)→ 内容协商 → HttpMessageConverter 序列化 → 直接写响应流

2. 走数据路径:内容协商

RequestResponseBodyMethodProcessor 调用了 handleReturnValue(),这个方法内部一气呵成完成了三件事:

  • 动作 A(内容协商) :读取请求头的 Accept(或 URL 参数 ?format=json),决定输出 application/json 还是 application/xml
  • 动作 B(寻找 Converter) :遍历 HttpMessageConverter,问谁能把返回值写成 application/json
  • 动作 C(消息转换与写入) :Jackson 的 Converter 说"我可以",立刻调用 converter.write(),把 Java 对象序列化为 JSON,写入 HttpServletResponseOutputStream

重要澄清 :这不是"两次转换"------返回值处理 + 内容协商 + 消息转换,是融为一体的同一个动作handleReturnValue() 内部直接完成了协商和写出,HTTP 响应在这一个方法调用中就已经发出。

3. 收尾工作

流程退回到 DispatcherServlet

  • 倒序执行拦截器的 postHandle(Controller 执行后、视图渲染前)
  • 视图渲染(如果走的是视图路径)
  • 倒序执行拦截器的 afterCompletion(视图渲染后)
  • 请求处理彻底结束

全景流程图

复制代码
┌──────────────────────────────────────────────────────────────┐
│ 第一阶段:项目启动(SpringApplication.run)                    │
│   WebMvcAutoConfiguration → 注入 DispatcherServlet             │
│   → 构建 HandlerMapping(路由表)                              │
│   → 初始化 HandlerAdapter(含 27 个参数解析器 + 15 个返回值处理器)│
│   → 初始化 HttpMessageConverter 列表                           │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 第二阶段:请求路由(DispatcherServlet.doDispatch)             │
│   请求过 Filter → doDispatch()                                │
│   → getHandler() 遍历 HandlerMapping → HandlerExecutionChain  │
│   → getHandlerAdapter() 遍历 HandlerAdapter → 找到适配器       │
│   → 执行拦截器 preHandle                                      │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 第三阶段:参数解析(getMethodArgumentValues)                  │
│   → checkMultipart() 文件上传预处理                            │
│   → 遍历参数 → supportsParameter() 逐个匹配解析器               │
│   → resolveArgument() 解析参数值                               │
│       ├ @RequestBody → HttpMessageConverter.read()(流处理)    │
│       └ @RequestParam/POJO → WebDataBinder + Converter(KV处理)│
│   → args[] 参数数组组装完成                                    │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 第四阶段:业务执行 + 返回值处理                                │
│   → method.invoke(bean, args) 反射执行 Controller             │
│   → 遍历 ReturnValueHandler → supportsReturnType() 匹配       │
│   → handleReturnValue()                                       │
└──────────────────────────────────────────────────────────────┘
                            ↓
┌──────────────────────────────────────────────────────────────┐
│ 第五阶段:内容协商与响应写入                                   │
│   ├ 走视图:ViewResolver → View.render() → HTML               │
│   └ 走数据:内容协商(选MediaType) → HttpMessageConverter.write()│
│           → JSON/XML 写入 OutputStream                         │
│   → 拦截器 postHandle → 视图渲染 → afterCompletion             │
└──────────────────────────────────────────────────────────────┘

设计哲学

如果我们把这套流程抽象一下,它本质上是一个 分发路由 → 策略模式匹配 → 核心逻辑执行 → 策略模式输出 的闭环。

@RequestBody@ResponseBody 只是一个标记 ,真正干活的是底层的 MethodProcessorHttpMessageConverter。当你理解了这种"遍历匹配,各司其职"的设计哲学,以后无论遇到多么复杂的注解或参数类型,都可以顺着这个执行链条,准确定位到它的解析器去排查问题。

记住两条金线:

处理"一整坨"(JSON/XML 流)→ HttpMessageConverter (流处理阵营)

处理"散装的"(URL参数/表单)→ WebDataBinder + Converter<S, T>(KV 参数阵营)

这两条线井水不犯河水,互不调用。分清了它们,Spring MVC 的任何参数绑定在你眼里就不再是黑盒。

相关推荐
我登哥MVP2 小时前
Spring Boot 从“会用”到“精通”:ReturnValueHandler原理
java·spring boot·后端·spring·java-ee·maven·intellij-idea
snow@li2 小时前
数据库:MySQL vs PostgreSQL 详尽对比(2026版)
java·mysql·postgresql
丑过三八线2 小时前
Runc 深度解析:从原理到实操
java·linux·开发语言·docker·容器·rpc
伊布拉西莫2 小时前
Flask 请求生命周期
后端·python·flask
STDD2 小时前
ntfy 自托管推送通知服务搭建:一条 curl 命令向手机发送通知
java·开发语言·智能手机
英豪1632 小时前
@Target + @Retention + isAnnotationPresent + getAnnotation
后端
黄同学real2 小时前
HJL WebAPI 项目日志入库实战:从建表到自动清理
后端