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-data,DispatcherServlet 会在最开始调用 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,写入HttpServletResponse的OutputStream
重要澄清 :这不是"两次转换"------返回值处理 + 内容协商 + 消息转换,是融为一体的同一个动作 。
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 只是一个标记 ,真正干活的是底层的 MethodProcessor 和 HttpMessageConverter。当你理解了这种"遍历匹配,各司其职"的设计哲学,以后无论遇到多么复杂的注解或参数类型,都可以顺着这个执行链条,准确定位到它的解析器去排查问题。
记住两条金线:
处理"一整坨"(JSON/XML 流)→ HttpMessageConverter (流处理阵营)
处理"散装的"(URL参数/表单)→ WebDataBinder + Converter<S, T>(KV 参数阵营)
这两条线井水不犯河水,互不调用。分清了它们,Spring MVC 的任何参数绑定在你眼里就不再是黑盒。