以下是一份极致详尽、系统化、可作为企业培训教材的《Spring MVC 核心架构与请求生命周期深度解析》文档,涵盖您要求的全部内容:核心组件职责、完整请求流程、可视化流程图、学习建议、规范与注意事项,并基于 Spring Framework 6.x 最新标准撰写。
📜 Spring MVC 核心架构与请求生命周期深度解析
目标:彻底掌握 Spring MVC 的架构设计哲学与请求处理全链路机制,实现从“会用”到“懂原理、能调优、可排错”的质变
✅ 一、Spring MVC 核心架构:五大核心组件职责详解
Spring MVC 的核心是前端控制器(Front Controller)模式,其架构由五个核心组件构成,各司其职,协同工作。
| 组件 | 类名 | 职责 | 关键特性 | 协作关系 |
|---|---|---|---|---|
| DispatcherServlet | org.springframework.web.servlet.DispatcherServlet | 前端控制器(总入口) 接收所有 HTTP 请求,协调其他组件工作 | - 唯一入口点 - 由 Servlet 容器(Tomcat)初始化 - 配置在 web.xml 或 ServletRegistrationBean 中 | 接收请求 → 查询 HandlerMapping → 获取 Handler → 调用 HandlerAdapter → 执行 Controller → 获取 ModelAndView → 解析 View → 渲染响应 |
| HandlerMapping | RequestMappingHandlerMapping(默认) | 请求映射器 根据请求 URL、Method、Header、Params 等信息,找到匹配的 Controller 方法(Handler) | - 基于注解 @RequestMapping 等- 支持路径变量、通配符、正则 - 支持多级匹配(最精确优先) | 被 DispatcherServlet 调用 → 返回 HandlerExecutionChain(含 Handler + 拦截器列表) |
| HandlerAdapter | RequestMappingHandlerAdapter(默认) | 处理器适配器 调用 Handler(Controller 方法) 负责参数绑定、类型转换、数据校验、返回值处理 | - 支持 @RequestMapping, @GetMapping 等- 支持 @RequestBody, @ModelAttribute- 支持异步返回 DeferredResult- 支持 @ResponseBody 转 JSON | 被 DispatcherServlet 调用 → 调用 Controller 方法 → 返回 ModelAndView 或 ResponseEntity |
| Controller | 任意 @Controller / @RestController 类 | 业务控制器 处理具体业务逻辑,返回模型数据和视图名(或直接返回响应体) | - 方法可返回 String(视图名)、ModelAndView、ResponseEntity<T>、void、Mono<T>(WebFlux)等- 方法参数支持 @RequestParam, @PathVariable, @RequestBody 等 | 被 HandlerAdapter 调用 → 执行业务 → 返回数据结构 |
| ViewResolver | ThymeleafViewResolver, ContentNegotiatingViewResolver | 视图解析器 将 Controller 返回的逻辑视图名(如 "user/list")解析为物理视图对象(如 Thymeleaf 模板文件) | - 支持多种模板引擎(Thymeleaf, FreeMarker, JSP) - 支持内容协商(Content Negotiation) - 可配置优先级 | 被 DispatcherServlet 调用 → 根据视图名 + 请求 Accept 头 → 返回 View 对象 |
| View | ThymeleafTemplate, MappingJackson2JsonView | 视图渲染器 将模型数据(Model)渲染成最终的响应内容(HTML、JSON、XML) | - 模板引擎渲染 HTML - JSON 序列化器(Jackson)生成 JSON - 可自定义 View 实现 | 被 DispatcherServlet 调用 → render() 方法执行 → 输出响应流 |
✅ 架构图(文字版):
HTTP Request ↓ ┌──────────────────────┐ │ DispatcherServlet │ ← 1. 接收请求 └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ HandlerMapping │ ← 2. 找到匹配的 Controller 方法 └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ HandlerExecutionChain│ ← 3. 返回 Handler + 拦截器列表 └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ HandlerAdapter │ ← 4. 调用 Controller 方法 └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ Controller │ ← 5. 执行业务逻辑,返回 ModelAndView └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ ViewResolver │ ← 6. 解析逻辑视图名 → 物理视图对象 └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ View │ ← 7. 渲染模型数据 → HTML/JSON └──────────┬───────────┘ ↓ ┌──────────────────────┐ │ HTTP Response │ ← 8. 返回客户端 └──────────────────────┘
✅ 关键设计哲学:
- 单一职责:每个组件只做一件事
- 松耦合:组件间通过接口通信,可替换(如换 Jackson 为 Gson)
- 可扩展:可自定义
HandlerMapping、ViewResolver、HandlerAdapter- 统一入口:所有请求都经过
DispatcherServlet,便于统一处理(如日志、安全)
✅ 二、请求处理完整生命周期:从 HTTP 请求到响应返回(8步详解)
我们以一个标准的 REST API 请求为例:
GET /api/users/1001 → 返回 JSON
🔹 步骤 1:请求到达 DispatcherServlet
- 触发者:Servlet 容器(Tomcat)接收到 HTTP 请求
- 执行者:
DispatcherServlet - 动作:
- 初始化(若为首次请求)
- 获取请求 URI:
/api/users/1001 - 获取 HTTP Method:
GET - 获取请求头(Headers)、参数(Query)、Body
- 关键代码入口:
// DispatcherServlet.doDispatch() protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // ... }
🔹 步骤 2:HandlerMapping 匹配 Controller 方法
- 触发者:
DispatcherServlet调用HandlerMapping.getHandler() - 执行者:
RequestMappingHandlerMapping - 动作:
- 遍历所有注册的
@Controller类及其方法 - 匹配规则(按优先级):
- 精确路径匹配:
/api/users/1001 - 路径变量匹配:
/api/users/{id} - 通配符匹配:
/api/users/** - HTTP 方法匹配:
GETvsPOST - 请求头匹配:
Accept: application/json - 参数匹配:
params="type=active"
- 精确路径匹配:
- 找到匹配方法:
UserController.getUser(@PathVariable("id") Long id)
- 遍历所有注册的
- 输出:
HandlerExecutionChain- 包含:目标方法(
HandlerMethod对象) - 包含:拦截器列表(
HandlerInterceptor[],按注册顺序)
- 包含:目标方法(
✅ 路径匹配规则优先级(官方文档):
1. 精确路径(无变量) > 2. 路径变量 > 3. 通配符 > 4. 默认路径
🔹 步骤 3:执行 HandlerExecutionChain 中的拦截器 preHandle()
- 触发者:
DispatcherServlet - 执行者:所有注册的
HandlerInterceptor - 动作:
- 按注册顺序,依次调用每个拦截器的
preHandle(request, response, handler) - 若任一拦截器返回
false→ 请求被中断,跳过 Controller,直接调用afterCompletion - 若全部返回
true→ 继续执行
- 按注册顺序,依次调用每个拦截器的
- 典型用途:
- 登录校验(
if (token == null) return false;) - 请求日志记录(记录 startTime)
- 性能监控(记录 startTime)
- 跨域处理(设置 CORS 头)
- 登录校验(
✅ 拦截器执行顺序:
Interceptor1.preHandle → Interceptor2.preHandle → ... → Controller → ... → Interceptor2.postHandle → Interceptor1.postHandle → ... → Interceptor1.afterCompletion → Interceptor2.afterCompletion
🔹 步骤 4:HandlerAdapter 调用 Controller 方法
- 触发者:
DispatcherServlet - 执行者:
RequestMappingHandlerAdapter - 动作:
- 解析方法签名:获取参数列表(
@PathVariable,@RequestParam,@RequestBody等) - 参数解析器(ArgumentResolvers):
PathVariableMethodArgumentResolver→ 提取{id}→ 转为LongRequestResponseBodyMethodProcessor→ 读取 JSON Body → 反序列化为UserRequestParamMethodArgumentResolver→ 读取 query stringModelMethodProcessor→ 注入ModelServletWebRequestMethodArgumentResolver→ 注入HttpServletRequest
- 数据校验:
- 若参数有
@Valid→ 调用Validator(Hibernate Validator) - 校验失败 → 抛出
MethodArgumentNotValidException
- 若参数有
- 类型转换:
String "2025-01-01"→LocalDateString "admin"→Role.ADMIN
- 调用目标方法:
// 伪代码 Object result = method.invoke(controllerInstance, args); - 返回值处理器(ReturnValueHandlers):
- 若返回
String→ 包装为ModelAndView - 若返回
ResponseEntity<T>→ 直接使用 - 若返回
@ResponseBody对象 → 用HttpMessageConverter转为 JSON - 若返回
void→ 无响应体
- 若返回
- 解析方法签名:获取参数列表(
✅ 核心机制:
参数绑定 + 类型转换 + 数据校验 + 返回值序列化 全部由HandlerAdapter通过策略模式动态选择处理器完成。
🔹 步骤 5:Controller 执行业务逻辑
- 执行者:开发者编写的
@Controller类中的方法 - 动作:
- 调用 Service 层(
userService.findById(id)) - 处理业务逻辑(权限校验、状态变更)
- 构造返回数据:
@GetMapping("/users/{id}") public ResponseEntity<User> getUser(@PathVariable Long id) { User user = userService.findById(id); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); // ✅ 返回 ResponseEntity }
- 调用 Service 层(
- 返回值类型:
返回类型 含义 是否需要 @ResponseBodyString逻辑视图名(如 "user/list")❌ ModelAndView包含 Model + ViewName ❌ ResponseEntity<T>完整 HTTP 响应(状态码、头、体) ✅(隐式) T+@ResponseBody直接序列化为 JSON/XML ✅ void无响应体 ✅(通常用于异步) Mono<T>/Flux<T>响应式编程 ✅(需 WebFlux)
✅ 最佳实践:REST API 优先使用
ResponseEntity<T>,控制力最强。
🔹 步骤 6:ViewResolver 解析视图名
- 触发者:
DispatcherServlet - 执行者:
ViewResolver(默认ContentNegotiatingViewResolver) - 动作:
- 获取 Controller 返回的 逻辑视图名(如
"user/list") - 获取客户端请求头:
Accept: application/json/text/html - 内容协商(Content Negotiation):
- 若
Accept: application/json→ 选择MappingJackson2JsonView - 若
Accept: text/html→ 选择ThymeleafViewResolver
- 若
- 根据视图名 + 内容类型,查找物理视图:
- Thymeleaf:
/WEB-INF/views/user/list.html - JSON:无物理文件,直接使用
HttpMessageConverter
- Thymeleaf:
- 返回
View实例(如JsonView或ThymeleafTemplate)
- 获取 Controller 返回的 逻辑视图名(如
✅ 内容协商优先级(默认):
- URL 扩展(如
/user/1001.json)- 请求头
Accept- 请求参数
?format=json
🔹 步骤 7:View 渲染响应内容
- 触发者:
DispatcherServlet - 执行者:
View对象(如MappingJackson2JsonView) - 动作:
- 获取 Controller 返回的 Model 数据(如
User user = new User(...)) - 调用
render(model, request, response) - 序列化:
- JSON:使用
ObjectMapper(Jackson) →user→{"id":1001,"name":"张三"} - HTML:使用 Thymeleaf 引擎 → 渲染模板 → 生成完整 HTML 字符串
- JSON:使用
- 写入
HttpServletResponse的输出流(OutputStream)
- 获取 Controller 返回的 Model 数据(如
- 关键点:
- Model 数据 是一个
Map<String, Object>,键是变量名,值是对象 - 渲染过程不涉及业务逻辑,仅是数据展示
- Model 数据 是一个
🔹 步骤 8:执行拦截器 postHandle() 和 afterCompletion()
✅ 8.1 postHandle() —— 渲染前调用
- 触发时机:Controller 执行完,View 渲染前
- 执行者:所有拦截器,逆序执行(与 preHandle 顺序相反)
- 用途:
- 修改 Model 数据(如添加通用变量)
- 添加响应头(如
X-Powered-By) - 记录处理时间(需在 preHandle 记录 startTime)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
modelAndView.addObject("appVersion", "v2.1.0"); // ✅ 添加全局变量
}
}
✅ 8.2 afterCompletion() —— 响应完成后调用
- 触发时机:无论成功或异常,响应完全写入客户端后
- 执行者:所有拦截器,逆序执行
- 用途:
- 记录完整耗时(
System.currentTimeMillis() - startTime) - 记录访问日志(URL、IP、状态码、耗时)
- 清理线程本地变量(如
RequestContextHolder) - 发送监控指标(Prometheus)
- 记录完整耗时(
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Long startTime = (Long) request.getAttribute("startTime");
if (startTime != null) {
long duration = System.currentTimeMillis() - startTime;
log.info("✅ [{}] {} {} | {}ms | Status: {}",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration,
request.getAttribute("requestId"));
}
}
✅ 为什么必须逆序?
拦截器是栈结构:preHandle入栈,afterCompletion出栈。
保证“先入后出”,如:日志记录先开始,后结束。
✅ 三、完整请求生命周期可视化流程图(Mermaid)
✅ 图中关键点:
- 所有组件均由
DispatcherServlet调用- 拦截器在
preHandle和afterCompletion中执行postHandle在View渲染前执行- 无 View 的请求(REST API)直接由
HttpMessageConverter输出
✅ 四、学习建议:如何真正掌握 Spring MVC?
| 阶段 | 建议行动 | 目标 |
|---|---|---|
| 1. 理解架构 | 画出上述 5 组件图,手写每个组件的职责 | 能口述“请求如何流转” |
| 2. 源码追踪 | 在 IDEA 中启动 Spring Boot,Debug DispatcherServlet.doDispatch() | 看清每一步调用栈 |
| 3. 模拟实现 | 自己写一个简易版 MyDispatcherServlet,只支持 @RequestMapping + @ResponseBody | 理解核心逻辑 |
| 4. 调试拦截器 | 写一个拦截器,打印 request.getRequestURI()、response.getStatus() | 理解拦截器生命周期 |
| 5. 分析异常 | 故意传非法参数(如 age=abc),看 MethodArgumentNotValidException 如何被处理 | 理解异常传播链 |
| 6. 配置自定义 | 替换 Jackson 为 Gson,自定义 ViewResolver | 理解可扩展性 |
| 7. 面试准备 | 准备“请描述一个请求从浏览器到后端的完整流程”标准答案 | 能讲 5 分钟不卡顿 |
✅ 推荐工具:
- IDEA:开启
Debug模式,断点在DispatcherServlet.doDispatch()- Postman / Apifox:测试不同
Accept头、路径变量、JSON Body- Spring Boot Actuator:
/actuator/mappings查看所有 URL 映射
✅ 五、使用 Spring MVC 的推荐规范与注意事项
✅ 推荐规范(企业级最佳实践)
| 类别 | 规范 | 说明 |
|---|---|---|
| Controller 设计 | 使用 @RestController | REST API 统一用 @RestController,避免 @Controller + @ResponseBody 混用 |
| 请求映射 | 优先使用 @GetMapping, @PostMapping | 比 @RequestMapping(method = GET) 更语义化 |
| 路径设计 | 使用复数名词,如 /users,而非 /user | RESTful 风格,符合惯例 |
| 参数绑定 | 优先使用 @PathVariable、@RequestParam、@RequestBody | 禁止直接使用 request.getParameter() |
| 数据校验 | 使用 @Valid + JSR-380 注解 | 所有 DTO 必须校验,禁止在 Controller 中手动校验 |
| 异常处理 | 使用 @RestControllerAdvice 统一处理 | 禁止每个 Controller 写 try-catch |
| 响应格式 | 统一返回 ResponseEntity<T> 或 CommonResult<T> | 前后端契约清晰,避免 null 混淆 |
| 文件上传 | 使用 MultipartFile,校验大小、类型 | 生产环境使用 MinIO/OSS,禁止直接存本地磁盘 |
| 拦截器 | 用于登录、日志、性能监控 | 禁止在拦截器中执行数据库查询(影响性能) |
| 线程安全 | Controller 是单例,不要存储成员变量 | 所有数据通过方法参数传递 |
| 性能优化 | 开启 @EnableAsync + 异步写日志 | 避免 I/O 阻塞主线程 |
| 内容协商 | 明确指定 produces = "application/json" | 避免客户端误解析为 HTML |
⚠️ 常见错误与避坑指南
| 错误 | 原因 | 正确做法 |
|---|---|---|
❌ @Autowired 注入 HttpServletRequest | 虽然能用,但违反设计 | 改用 @RequestParam, @RequestHeader |
❌ 在 Controller 中调用 response.sendRedirect() | 跳过 View,破坏流程 | 使用 return "redirect:/users" |
❌ @PathVariable 未校验 | 传入 abc 导致 NumberFormatException | 加 @Min(1) 或 @Pattern |
❌ @RequestBody 接收 null | 客户端没传 Body | 加 @NotNull 或 @Valid |
❌ 拦截器中 request.setAttribute() 传数据给 Controller | 不推荐,耦合高 | 改用 @ModelAttribute 或 @RequestAttribute |
❌ 使用 @RequestMapping 没写 method | 可能被 POST 请求触发 | 必须指定 method = GET |
❌ 没有配置 ContentNegotiatingViewResolver | JSON 返回乱码 | 在 WebMvcConfigurer 中配置 |
❌ 使用 @Controller 返回 JSON 但没加 @ResponseBody | 返回视图名,不是 JSON | 改用 @RestController 或加注解 |
❌ 拦截器中调用 response.getWriter().write() | 会跳过后续组件 | 只在 preHandle 中 return false 中断 |
✅ 高级建议(进阶)
| 建议 | 说明 |
|---|---|
✅ 使用 @ControllerAdvice + @ExceptionHandler 统一处理异常 | 避免重复代码,提升可维护性 |
✅ 自定义 HttpMessageConverter | 如支持 Protobuf、YAML 格式 |
✅ 启用 @EnableWebMvc 时谨慎 | 会覆盖 Spring Boot 自动配置,仅在需要完全控制时使用 |
✅ 使用 @RequestScope 管理请求级状态 | 如 UserContext 存储当前登录用户 |
| ✅ 集成 Springdoc OpenAPI | 自动生成 Swagger UI 文档 |
✅ 使用 @CrossOrigin 或全局 CORS 配置 | 解决前后端分离的跨域问题 |
✅ 六、总结:Spring MVC 的设计哲学
| 哲学 | 体现 |
|---|---|
| 约定优于配置 | @RestController 默认返回 JSON,无需额外配置 |
| 松耦合 | 组件间通过接口通信,可替换(如换 Jackson 为 Gson) |
| 可扩展 | 自定义 HandlerMapping、ViewResolver、HandlerAdapter |
| 统一入口 | 所有请求走 DispatcherServlet,便于统一处理(安全、日志、监控) |
| 声明式编程 | 用注解声明“我要什么”,而不是“我怎么做” |
| 关注点分离 | Controller 只管业务,校验归校验,视图归视图,异常归异常 |
✅ 终极目标:
让开发者只写业务逻辑,其余交给框架。
✅ 七、学习路线图(进阶方向)
| 阶段 | 学习内容 |
|---|---|
| 基础 | 理解上述 5 组件 + 请求流程 |
| 进阶 | 自定义 HandlerInterceptor、ViewResolver、ArgumentResolver |
| 深入 | 阅读 DispatcherServlet.java、RequestMappingHandlerAdapter.java 源码 |
| 实战 | 搭建一个带权限校验、日志、上传下载、统一响应的完整项目 |
| 扩展 | 学习 Spring WebFlux(响应式)、Spring GraphQL、Spring Cloud Gateway |
Spring MVC 请求生命周期解析
1000

被折叠的 条评论
为什么被折叠?



