Appearance
Spring MVC
Spring MVC 负责把 HTTP 世界和 Java 方法连接起来。客户端看到的是 URL、Method、Header、Query、Body;业务代码希望拿到的是 Java 对象、业务命令和明确的返回结果。MVC 的工作就是完成这层翻译和调度。
详细位置
- 本页说明 MVC 请求链、Controller 职责、参数绑定、校验、异常处理和排查方式。
- Controller、Service 等对象如何注入见 IoC 容器。
- Service 方法上的事务和日志增强见 AOP。
- Boot Web 默认配置见 Spring Boot。
请求链路
一次接口请求可以按这条链理解:
- 请求先到 Servlet 容器。
- Filter 处理更底层的通用逻辑,例如编码、安全链、链路追踪。
DispatcherServlet接管请求,成为 MVC 的统一入口。- HandlerMapping 找到哪个控制器方法能处理这个请求。
- HandlerAdapter 完成参数绑定、类型转换和校验。
- Controller 调用 Service。
- 返回值处理器把 Java 对象写成 JSON 或视图。
- 如果出现异常,异常处理器把异常转换成响应。
DispatcherServlet 可以理解为前台调度员:它不亲自处理业务,而是找到合适窗口、准备好材料、交给对应处理人,再把处理结果按协议返回。
分层职责
| 层级 | 负责 | 不负责 |
|---|---|---|
| Controller | HTTP 协议适配、参数校验、响应组织 | 复杂业务决策、事务细节、数据库访问 |
| Request / Response | 接口入参和出参模型 | 持久化细节 |
| Service | 业务编排、事务边界 | HTTP Header、URL、状态码细节 |
| Repository / Client | 数据库、缓存、外部接口访问 | 业务流程编排 |
| Advice / Interceptor | 通用异常、上下文、拦截逻辑 | 核心业务分支 |
Controller 像海关窗口,负责检查表单、核对格式、把材料转交给业务部门。它不应该在窗口现场决定库存、支付、审批、结算等复杂规则。
请求映射
请求映射回答三个问题:
- 这个请求的路径是什么。
- 这个请求使用什么 HTTP Method。
- 这个请求需要什么附加条件,例如参数、Header、内容类型。
常见匹配关系:
| 请求数据 | MVC 注解 | 适合场景 |
|---|---|---|
| 路径片段 | @PathVariable | /orders/1001 中的 1001 |
| 查询参数 | @RequestParam | 分页、筛选、搜索关键词 |
| 请求体 JSON | @RequestBody | 创建、修改、复杂查询条件 |
| 请求头 | @RequestHeader | 请求 ID、租户、客户端版本 |
| Cookie | @CookieValue | 会话标识、灰度标记 |
不清楚用哪个时,可以按数据位置判断:在路径里就是 PathVariable,在问号后就是 RequestParam,在 JSON Body 里就是 RequestBody。
参数绑定与校验
参数绑定是把字符串、JSON、文件等 HTTP 输入转换成 Java 类型。校验是在进入业务前检查输入是否满足基础格式。
适合放在校验层的规则:
- 字段不能为空。
- 数字范围是否合法。
- 字符串长度是否合法。
- 集合是否为空。
- 邮箱、手机号等基础格式。
不适合放在校验层的规则:
- 库存是否足够。
- 用户是否可以取消订单。
- 支付状态是否允许退款。
- 优惠券是否满足业务条件。
后一类属于业务规则,应放在 Service 或领域对象里。校验层负责“材料格式是否齐”,业务层负责“这件事能不能办”。
统一异常
统一异常处理的目标是让接口错误稳定、可读、可排查。
一个合理的错误响应通常包含:
- 业务错误码。
- 面向调用方的错误信息。
- 请求追踪 ID。
- 必要时包含字段级校验错误。
不应返回给外部的信息:
- Java 堆栈。
- 数据库 SQL 细节。
- 内部文件路径。
- 密钥、Token、连接串。
HTTP 状态码和业务错误码不冲突。参数错误可以是 400,同时业务错误码是 PARAM_INVALID;系统异常可以是 500,同时业务错误码是 SYSTEM_ERROR。
拦截器、过滤器、AOP 的区别
| 机制 | 所在位置 | 更适合做什么 |
|---|---|---|
| Filter | Servlet 容器层,MVC 之前 | 安全链、编码、跨域、链路 ID |
| Interceptor | MVC 层,知道 Handler 信息 | 登录态、租户上下文、接口审计 |
| AOP | Bean 方法调用层 | 事务、方法级日志、业务注解增强 |
选择时看你需要的信息在哪一层。如果需要原始请求和响应,用 Filter;如果需要知道目标控制器方法,用 Interceptor;如果关注 Service 方法调用,用 AOP。
CORS
CORS 是浏览器安全策略下的跨域访问控制,不是后端接口本身的业务权限。排查跨域时要同时看四处:
- 浏览器控制台报错。
- 预检请求
OPTIONS是否到达服务。 - 网关或 Nginx 是否已经处理 CORS。
- Spring MVC 或 Spring Security 是否重复或覆盖了响应头。
生产环境应明确允许的 Origin、Method 和 Header。长期使用任意 Origin 会扩大风险,尤其是携带 Cookie 或认证信息时。
文件上传
文件上传比普通 JSON 请求更容易出问题,原因是它涉及请求大小、临时文件、文件名、内容类型、存储路径和后续处理。
基本判断:
- 大小限制应在网关和应用两层都配置。
- 文件名不要直接使用客户端传入值作为存储名。
- 扩展名不能作为唯一校验依据。
- 上传接收和后续导入处理最好解耦,避免长时间占用请求线程。
静态资源
Spring Boot 可以直接提供 classpath 下的静态资源,也可以通过 MVC 配置映射外部目录。用户上传文件如果要提供访问,需要额外考虑:
- 是否需要鉴权。
- 是否可能目录穿越。
- 是否应该使用对象存储或 CDN。
- 缓存策略如何设置。
- 删除或替换文件后访问结果是否一致。
静态资源不是“能访问就结束”,生产环境更关注安全边界和缓存行为。
测试选择
| 测试目标 | 推荐方式 | 原因 |
|---|---|---|
| 只验证 Controller 映射、参数、响应 | @WebMvcTest | 启动范围小,定位 Web 层问题快 |
| 验证完整接口链路 | @SpringBootTest + 随机端口 | 能覆盖真实容器和完整配置 |
| 只验证业务规则 | 普通单元测试 | 不需要启动 MVC |
| 验证异常响应格式 | Web 层测试 | 能确认状态码和错误结构 |
不要把所有测试都写成完整上下文测试。MVC 层问题和业务规则问题应分开验证。
排查路径
404
优先检查:
- URL 和服务上下文路径是否正确。
- HTTP Method 是否匹配。
- Controller 是否被扫描成 Bean。
- 类级路径和方法级路径组合后是否符合预期。
- 网关或反向代理是否改写路径。
400
常见原因:
- JSON 格式错误。
Content-Type不匹配。- 参数类型转换失败。
- 校验规则不通过。
- 必填参数缺失。
415
说明服务端不支持当前请求体类型。JSON 请求通常应使用 application/json,文件上传通常应使用 multipart/form-data。
500
优先看服务端日志中的第一处业务异常。全局异常处理应记录原始异常,响应中只保留调用方需要知道的信息。
总结
Spring MVC 是 HTTP 协议和业务方法之间的调度与翻译层。读 MVC 代码时先看请求链,再看 Controller 是否只做协议适配,最后检查校验、异常、拦截器和返回值处理是否集中。排错时按“路由 -> 参数 -> 校验 -> 拦截链 -> 业务异常 -> 响应转换”的顺序推进。
