心跳接口已经能够接收请求并返回 JSON,但请求为什么会找到某个 Java 方法、参数从哪里得到、返回对象又怎样变成响应,还需要一条完整解释链路。这些工作由 Spring Web MVC 协调完成。
当前项目使用 spring-boot-starter-webmvc,运行的是基于 Servlet 的 Spring MVC。本章只观察框架处理过程;下一篇再集中编写 HealthController 的映射、参数和响应代码。
1. MVC 与 Servlet
MVC 是 Model、View、Controller 的缩写。传统服务器页面把数据放入 Model,由 View 模板渲染 HTML;当前心跳项目是 REST 接口,Controller 返回数据对象,消息转换器将它写成 JSON,不会进入模板渲染流程。
需要区分三个层次:
| 层次 | 当前实现 | 职责 |
|---|---|---|
| Servlet 容器 | 内嵌 Tomcat | 监听端口,运行 Servlet 和 Filter |
| Spring MVC | DispatcherServlet 及其协作组件 | 把请求分发到处理方法并组织响应 |
| 应用代码 | Controller、Service | 处理 HTTP 边界和业务规则 |
HealthController 不是 Servlet,它是由容器管理、再由 Spring MVC 调用的 Bean。DispatcherServlet 才是运行在 Tomcat 中的中心 Servlet。
项目通过 Starter 引入 MVC:
1<dependency>2<groupId>org.springframework.boot</groupId>3<artifactId>spring-boot-starter-webmvc</artifactId>4</dependency>
Starter 提供 Spring MVC、JSON 转换和默认内嵌 Tomcat 所需依赖。Spring Boot 根据 classpath 启用自动配置,因此项目不需要手动创建 Tomcat、注册 DispatcherServlet 或维护 web.xml。
2. 请求管线
Spring MVC 使用前端控制器模式:进入 MVC 的请求先到 DispatcherServlet,再由它协调其他组件。
01客户端02-> Tomcat 接收 HTTP 请求03-> Servlet Filter 链04-> DispatcherServlet05-> HandlerMapping 查找处理方法06-> HandlerInterceptor.preHandle07-> HandlerAdapter08-> HandlerMethodArgumentResolver 准备方法参数09-> 调用 Controller 方法10-> HandlerMethodReturnValueHandler 处理返回值11-> HttpMessageConverter 读写请求体或响应体12-> HandlerInterceptor.postHandle13-> HandlerInterceptor.afterCompletion14-> Filter 链返回15-> Tomcat 发送 HTTP 响应
这条管线不是每个请求都重新扫描所有类。应用启动时,MVC 已经发现控制器 Bean 并登记处理方法;请求到来后,再根据登记结果匹配。
正常返回和抛出异常会走不同分支。正常结果进入返回值处理,异常则交给 HandlerExceptionResolver 尝试转换成 HTTP 响应。只有没有机制能够处理时,异常才继续向 Servlet 容器传播。
3. 查找与调用 Handler
在 Spring MVC 中,Handler 通常是某个控制器方法,而不只是控制器类。框架会把 Bean、Java 方法和映射条件组合成可调用的处理器。
HandlerMapping 根据路径、HTTP 方法、查询条件、请求头和媒体类型等信息寻找匹配项。它只负责回答「应该处理哪个方法」,不负责直接调用。
HandlerAdapter 负责以当前 Handler 所需的方式完成调用。对注解控制器,它会进一步使用参数解析器准备 Java 参数,再调用反射得到的方法。
1GET /api/v1/health2-> 路径匹配 /api/v1/health3-> HTTP 方法匹配 GET4-> consumes / produces 等条件匹配5-> 确定 HealthController.health()6-> HandlerAdapter 调用该方法
如果没有路径匹配,通常得到 404 Not Found;路径存在但 HTTP 方法不匹配,通常是 405 Method Not Allowed;请求或响应媒体类型不满足条件,则可能得到 415 Unsupported Media Type 或 406 Not Acceptable。
映射冲突通常在应用启动时暴露。例如两个方法声明了无法区分的相同路径和 HTTP 方法,MVC 无法建立唯一映射,不应依赖声明顺序碰运气。
4. 参数与消息转换
Controller 方法只声明 Java 参数,不会自己解析原始 HTTP 字节。HandlerMethodArgumentResolver 根据参数类型和注解选择解析方式:
| HTTP 数据 | 解析结果 |
|---|---|
| 路径片段 | 路径变量参数 |
| 查询字符串 | 查询参数 |
| 请求头 | 请求头参数 |
| JSON 请求体 | Java 请求对象 |
| Servlet 请求上下文 | HttpServletRequest 等基础设施类型 |
路径和查询参数最初通常是字符串,转换服务会把它们转换为 int、枚举、时间等目标类型。转换失败时,控制器方法不会收到一个勉强生成的值,而是进入请求错误处理流程。
请求体需要消息转换器参与。它结合 Content-Type 和目标 Java 类型选择读取方式:
1Content-Type: application/json2JSON 字节3-> Jackson 消息转换器4-> HealthCheckRequest5-> Bean Validation6-> Controller 方法参数
消息转换与参数校验是两个阶段。转换负责建立对象,Bean Validation 负责检查对象是否满足约束;JSON 语法错误与字段违反 @NotBlank 也会产生不同异常。
5. 返回值与异常
Controller 方法执行结束后,HandlerMethodReturnValueHandler 根据返回类型和注解决定下一步:
| 返回形式 | MVC 行为 |
|---|---|
| REST Controller 返回普通对象 | 使用消息转换器写入响应体 |
ResponseEntity<T> | 使用其中的状态码、响应头和响应体 |
| 普通 Controller 返回字符串 | 通常作为视图名称 |
ModelAndView | 使用模型数据和视图信息渲染页面 |
REST 响应转换方向与请求相反:
1HealthResponse2-> 返回值处理器3-> 根据 Accept 和 produces 选择表示形式4-> Jackson 消息转换器5-> JSON 响应体
客户端的 Accept、映射声明的 produces、可用消息转换器和返回类型共同参与内容协商。接口只支持 JSON,而客户端只接受 HTML 时,框架不能凭空生成 HTML。
处理过程中抛出的异常会交给异常解析器链。控制器局部的 @ExceptionHandler、全局 @ControllerAdvice、ResponseStatusException 和框架默认异常处理都可能参与。后面的「统一异常处理」会集中实现业务异常、校验错误与 ProblemDetail 的转换。
异常处理方法返回的对象仍然经过返回值处理器和消息转换器。错误响应不是绕开 MVC 的另一套机制,而是同一管线中的失败分支。
6. Filter、Interceptor 与配置
Filter 和 HandlerInterceptor 都能在 Controller 前后执行,但所在层次不同:
| 对比项 | Servlet Filter | HandlerInterceptor |
|---|---|---|
| 所在位置 | Servlet 容器层 | Spring MVC 层 |
| 执行时机 | DispatcherServlet 前后 | Handler 已匹配之后、方法调用前后 |
| 是否知道控制器方法 | 通常不知道 | 可以取得 Handler 信息 |
| 常见用途 | 请求包装、编码、安全过滤链、底层日志 | MVC 耗时、区域设置、处理器相关检查 |
身份认证与授权更适合 Spring Security 的 Filter 链,而不是只靠 MVC Interceptor。安全检查需要尽早覆盖进入应用的请求,也要处理那些尚未进入 Controller 的分支。
Spring Boot 已经配置好常用映射处理器、参数解析器、消息转换器和静态资源处理。需要增加 CORS、格式转换、Interceptor 或资源规则时,创建 WebMvcConfigurer 配置类:
01package cn.usehook.spring.config;0203import org.springframework.context.annotation.Configuration;04import org.springframework.web.servlet.config.annotation.CorsRegistry;05import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;0607@Configuration08public class WebConfiguration implements WebMvcConfigurer {0910@Override11public void addCorsMappings(CorsRegistry registry) {12registry.addMapping("/api/**")13.allowedOrigins("https://springboot.usehook.cn")14.allowedMethods("GET", "POST");15}16}
这种方式会在保留 Spring Boot 自动配置的基础上增加规则。不要为了添加一个扩展点就同时使用 @EnableWebMvc;它表示应用准备接管完整 MVC 配置,会改变自动配置参与方式。
现在一次请求的框架链路已经完整:Tomcat 提供 Servlet 运行环境,Filter 处理容器层逻辑,DispatcherServlet 协调 MVC,HandlerMapping 和 HandlerAdapter 找到并调用方法,参数解析器与消息转换器负责输入输出,异常解析器处理失败。下一篇只需要站在这条管线的应用边界上,专注编写 Controller。