Spring MVC 执行流程详解:一次请求的完整旅程
一、为什么需要理解执行流程
Spring MVC 的组件很多:DispatcherServlet、HandlerMapping、HandlerAdapter、ViewResolver、HttpMessageConverter、HandlerInterceptor、HandlerExceptionResolver......
单独看每个组件都知道它是干什么的,但一遇到问题就不知道从哪查:
- 为什么请求 404?是路由没找到,还是 Controller 没扫描到?
- 为什么返回对象变成了 HTML 页面?是视图解析器接管了,还是
@ResponseBody没生效? - 为什么拦截器没执行?是路径没匹配,还是执行顺序不对?
理解执行流程,就能知道一次请求在每个阶段经过了哪些组件,出了问题该看哪一段。
二、核心组件速览
在进入流程之前,先认识流程中的主要角色。
| 组件 | 作用 |
|---|---|
DispatcherServlet |
前端控制器,整个流程的调度中心 |
HandlerMapping |
根据请求路径找到对应的 Handler(Controller 方法) |
HandlerAdapter |
调用 Handler,负责参数绑定、返回值处理 |
Handler |
开发者写的 Controller 方法 |
HandlerInterceptor |
拦截器,在 Handler 前后插入逻辑 |
HandlerExceptionResolver |
异常解析器,处理执行过程中的异常 |
ViewResolver |
视图解析器,把视图名解析成 View |
View |
视图,负责渲染页面 |
HttpMessageConverter |
消息转换器,负责对象与请求/响应体之间的转换 |
三、完整执行流程
1. 流程总览
用户发起请求
→ DispatcherServlet
→ HandlerMapping 查找 Handler
→ 得到 HandlerExecutionChain(Handler + 拦截器)
→ 执行拦截器 preHandle
→ HandlerAdapter 调用 Handler
→ 参数绑定
→ 执行 Controller 方法
→ 返回值处理
→ 执行拦截器 postHandle
→ 视图渲染
→ 执行拦截器 afterCompletion
→ 响应返回用户
2. 逐步拆解
第一步:请求到达 DispatcherServlet
用户发送 HTTP 请求,Tomcat 接收后交给 DispatcherServlet。
DispatcherServlet 是 web.xml 中注册的 Servlet,映射路径通常是 /,拦截所有请求。它是整个流程的入口和调度中心。
xml
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
第二步:HandlerMapping 查找 Handler
DispatcherServlet 拿到请求后,第一件事是问 HandlerMapping:
"这个请求路径,交给哪个 Controller 方法处理?"
HandlerMapping 内部维护一张映射表,记录着每个 路径 + 请求方法 对应的处理方法。它遍历所有注册的映射,找到匹配的那一个,返回 HandlerExecutionChain。
HandlerExecutionChain 包含两部分:
Handler:通常是HandlerMethod,封装了 Controller 类、方法、参数信息。HandlerInterceptor列表:当前请求匹配到的拦截器。
找不到 Handler 时,返回 404。
第三步:执行拦截器的 preHandle
DispatcherServlet 拿到 HandlerExecutionChain 后,先按顺序执行所有拦截器的 preHandle。
InterceptorA.preHandle → InterceptorB.preHandle
- 返回
true:继续下一个拦截器或进入 Handler。 - 返回
false:中断流程,Controller 不执行,但已经放行的拦截器的afterCompletion仍会执行。
第四步:HandlerAdapter 调用 Handler
DispatcherServlet 不直接调用 Controller 方法,而是交给 HandlerAdapter。
为什么不直接调用?因为 Handler 的类型可能有很多种:
@RequestMapping标注的方法。- 实现
Controller接口的类。 HttpRequestHandler接口的实现。
HandlerAdapter 负责适配不同类型的 Handler,找到合适的适配器来执行。
以 RequestMappingHandlerAdapter 为例,它做三件事:
1. 参数绑定
把请求中的参数解析成 Controller 方法的参数:
@RequestParam从查询参数或表单取值。@PathVariable从路径变量取值。@RequestBody从请求体读取,交给HttpMessageConverter反序列化。@RequestHeader从请求头取值。- 普通对象参数由
WebDataBinder绑定。
2. 执行 Controller 方法
通过反射调用 Controller 方法,得到返回值。
3. 处理返回值
根据返回值类型决定怎么处理:
| 返回值类型 | 处理方式 |
|---|---|
String + 视图解析 |
当作视图名,交给 ViewResolver |
String + @ResponseBody |
当作响应体,交给 StringHttpMessageConverter |
普通对象 + @ResponseBody |
交给 Jackson 序列化成 JSON |
ModelAndView |
包含视图名和数据,交给 ViewResolver |
ResponseEntity |
包含状态码、响应头、响应体,由 HttpEntityMethodProcessor 处理 |
void |
不返回内容,或由方法自己写响应 |
第五步:执行拦截器的 postHandle
Controller 正常执行后,按逆序执行拦截器的 postHandle。
InterceptorB.postHandle → InterceptorA.postHandle
postHandle 可以拿到 ModelAndView,适合往模型里补充公共数据。
Controller 抛出异常时,postHandle 不执行,直接进入异常处理。
第六步:视图渲染
如果返回值需要渲染视图,DispatcherServlet 调用 ViewResolver 把逻辑视图名解析成 View:
java
View view = viewResolver.resolveViewName("user", locale);
ViewResolver 根据配置的前缀后缀拼接模板路径:
/WEB-INF/templates/ + user + .html
然后调用 View.render(model, request, response),由模板引擎生成 HTML,写入响应。
如果返回值走 @ResponseBody,不经过这一步,直接由 HttpMessageConverter 写入响应体。
第七步:执行拦截器的 afterCompletion
请求完成后,按逆序执行拦截器的 afterCompletion。
InterceptorB.afterCompletion → InterceptorA.afterCompletion
无论 Controller 是否抛异常,只要 preHandle 返回过 true,afterCompletion 都会执行。适合清理 ThreadLocal、关闭资源、记录耗时。
第八步:响应返回用户
DispatcherServlet 把响应交给 Tomcat,Tomcat 写回给客户端。
四、异常处理在流程中的位置
Controller 执行过程中抛出异常时,DispatcherServlet 会进入异常处理流程:
Controller 抛出异常
→ postHandle 跳过
→ HandlerExceptionResolver 处理异常
→ 找到 @ExceptionHandler 方法
→ 执行异常处理方法,生成响应
→ afterCompletion 执行
→ 响应返回
如果所有异常解析器都返回 null,异常抛给 Tomcat,用户看到默认错误页。
五、@ResponseBody 时的流程差异
现代前后端分离项目中,大部分接口都返回 JSON,流程和视图渲染有差异:
| 阶段 | 视图渲染流程 | @ResponseBody 流程 |
|---|---|---|
| 返回值 | 视图名 | 对象 |
| ViewResolver | 参与 | 不参与 |
| View.render | 参与 | 不参与 |
| HttpMessageConverter | 不参与 | 参与 |
| 响应体 | 模板渲染后的 HTML | JSON 或字符串 |
@ResponseBody 时,RequestResponseBodyMethodProcessor 遍历 HttpMessageConverter,找到能处理返回类型的转换器,直接写入响应体。
六、一次请求经过的组件图
客户端
│
▼
Tomcat
│
▼
Filter 链(编码、跨域等)
│
▼
DispatcherServlet
│
├── HandlerMapping → 找到 Handler + 拦截器
│
├── Interceptor.preHandle
│
├── HandlerAdapter
│ ├── 参数绑定(WebDataBinder / HttpMessageConverter)
│ ├── 调用 Controller
│ └── 处理返回值
│
├── Interceptor.postHandle
│
├── ViewResolver → View.render(视图渲染时)
│ 或 HttpMessageConverter.write(@ResponseBody 时)
│
├── Interceptor.afterCompletion
│
▼
响应返回客户端
七、为什么需要 HandlerMapping 和 HandlerAdapter 两个组件
这是面试常问的问题。
HandlerMapping 负责"找":根据请求路径找到对应的处理方法。
HandlerAdapter 负责"调":根据 Handler 的类型,用合适的方式调用它。
为什么不合并成一个?
因为 Handler 的类型很多。@RequestMapping 方法、Controller 接口实现、HttpRequestHandler 实现,调用方式各不相同。如果合并,DispatcherServlet 里就会充斥大量 if-else 判断 Handler 类型。
拆成两个组件后:
HandlerMapping只负责映射,不关心怎么调用。HandlerAdapter只负责调用,不关心怎么找到。
新增一种 Handler 类型时,只需要增加一个 HandlerAdapter,不需要改 DispatcherServlet。这符合开闭原则。
八、记忆方法
1. 核心链路口诀
DispatcherServlet 接请求,
HandlerMapping 找方法,
HandlerAdapter 调方法,
ViewResolver 解视图,
View 渲染写响应。
简化为:
接 → 找 → 调 → 解 → 渲
2. 拦截器三个时机
preHandle → 调 Controller 之前
postHandle → 调 Controller 之后、渲染之前
afterCompletion → 渲染之后、请求结束
3. 返回值两条路
返回视图名 → ViewResolver + View 渲染
@ResponseBody → HttpMessageConverter 写响应体
4. 组件分工口诀
HandlerMapping 管"找谁",
HandlerAdapter 管"怎么调",
ViewResolver 管"哪个页面",
View 管"怎么渲染",
HttpMessageConverter 管"怎么转"。
5. 顺序口诀
preHandle 正序,
postHandle 和 afterCompletion 逆序,
异常时 postHandle 跳过,afterCompletion 照常。
6. 排查口诀
404 → 查 HandlerMapping
参数绑定失败 → 查 HandlerAdapter 的参数解析
返回 HTML 而不是 JSON → 查 @ResponseBody 和 HttpMessageConverter
页面找不到 → 查 ViewResolver 和模板路径
拦截器没执行 → 查注册路径和 preHandle 顺序
九、总结
| 阶段 | 核心组件 | 作用 |
|---|---|---|
| 接收请求 | DispatcherServlet |
调度中心 |
| 查找 Handler | HandlerMapping |
根据路径找到 Controller 方法和拦截器 |
| 前置拦截 | HandlerInterceptor.preHandle |
Controller 执行前处理 |
| 调用 Handler | HandlerAdapter |
参数绑定、反射调用、返回值处理 |
| 后置拦截 | HandlerInterceptor.postHandle |
Controller 后、渲染前处理 |
| 视图解析 | ViewResolver |
视图名解析成 View |
| 视图渲染 | View |
生成 HTML 写入响应 |
| 消息转换 | HttpMessageConverter |
对象与请求/响应体互转 |
| 异常处理 | HandlerExceptionResolver |
处理执行过程中的异常 |
| 完成拦截 | HandlerInterceptor.afterCompletion |
请求结束后清理资源 |
Spring MVC 的执行流程可以概括为"接、找、调、解、渲 "五个字:DispatcherServlet 接请求,HandlerMapping 找方法,HandlerAdapter 调方法,ViewResolver 解视图,View 渲染响应。拦截器在方法前后插入逻辑,异常处理器在出错时兜底,HttpMessageConverter 在需要读写请求响应体时负责序列化。理解这条链路,就能在 404、415、406、500 这些问题出现时,快速判断是哪个环节没配置好。