Spring MVC 执行流程详解:一次请求的完整旅程

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 这些问题出现时,快速判断是哪个环节没配置好。

相关推荐
增量星球1 小时前
Spring AI 2.0 Tool Calling 到底怎么跑起来的:从 @Tool 到两次模型调用
java·agent·springai·ai coding
步行cgn1 小时前
Spring MVC 九大核心角色详解:请求处理链路中的每一个关键组件
java·spring·mvc
智恒百亿1 小时前
一台 5090 八卡服务器怎么分给一个团队用?多模型多任务的显存分配实战
java·大数据·人工智能
瑞码空间2 小时前
面向接口编程与开闭原则在模拟 JDBC 驱动中的应用
java·jdbc
Félix2512 小时前
Nacos学习笔记(SpringBoot/SpringCloud)
java·spring boot·spring
文丑颜不良啊2 小时前
LeetCode1581~1600题目解析
java·leetcode
香蕉大香蕉(06敬畏市场)2 小时前
MySQL 单点详解(06)
java·数据库·mysql·博客·实习
lqbzlll2 小时前
Spring Boot 开启虚拟线程后接口反而变慢了?4 个排查方向和对应修法
java·spring boot·后端
七夜zippoe2 小时前
LangChain4j vs Spring AI:Java AI 框架怎么选(对比表)
java·人工智能·spring·ai·langchain4j