Spring MVC 与 Web
Java 中级 · Spring 生态
Spring MVC 是 Spring 的 Web 层框架,核心是一个 DispatcherServlet(前端控制器) 和一套「组件协作」的请求处理流程。理解这九个组件如何串起来,你就能解释:为什么 @RequestMapping 能被识别、参数是怎么绑定的、拦截器和过滤器有什么区别、全局异常怎么统一处理。
一、请求处理全流程
1.1 九大组件
| 组件 | 职责 |
|---|---|
HandlerMapping | 根据 URL 找 Handler 和拦截器 |
HandlerAdapter | 适配并执行 Handler |
HandlerExceptionResolver | 异常解析(全局异常处理) |
ViewResolver | 视图解析(JSP/Thymeleaf) |
HandlerMethodArgumentResolver | 参数解析(@RequestParam 等) |
HandlerMethodReturnValueHandler | 返回值处理 |
RequestToViewNameTranslator | URL 推导视图名 |
LocaleResolver | 国际化 |
MultipartResolver | 文件上传 |
二、请求映射与 RESTful
java
@RestController // = @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}") // GET /api/users/1
public User get(@PathVariable Long id) { return userService.get(id); }
@GetMapping // GET /api/users?status=1
public List<User> list(@RequestParam(required = false) Integer status) { /* ... */ }
@PostMapping // POST /api/users
public User create(@RequestBody @Valid User user) { /* ... */ }
@PutMapping("/{id}")
public void update(@PathVariable Long id, @RequestBody User user) { /* ... */ }
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { /* ... */ }
}| 注解 | 作用 |
|---|---|
@PathVariable | 取 URL 路径变量 /users/{id} |
@RequestParam | 取查询参数 ?name=x |
@RequestBody | 请求体 JSON → 对象 |
@RequestHeader | 请求头 |
@CookieValue | Cookie |
@ModelAttribute | 表单绑定 |
参数绑定的本质
每个注解背后都有一个 HandlerMethodArgumentResolver。方法执行前,DispatcherServlet 会为每个参数找匹配的解析器,把 HTTP 请求里的数据「解析」成方法参数——这就是为什么参数能自动绑定。
三、参数校验
java
public class User {
@NotBlank(message = "用户名不能为空")
private String name;
@Min(value = 18, message = "年龄不能小于18")
private Integer age;
@Email(message = "邮箱格式错误")
private String email;
}
@PostMapping
public User create(@RequestBody @Valid User user, BindingResult result) {
if (result.hasErrors()) {
throw new IllegalArgumentException(result.getFieldError().getDefaultMessage());
}
return userService.save(user);
}四、统一异常处理
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException e) {
return Result.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<Void> handleValid(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.fail(400, msg);
}
@ExceptionHandler(Exception.class) // 兜底
public Result<Void> handleAll(Exception e) {
log.error("系统异常", e);
return Result.fail(500, "系统繁忙,请稍后再试");
}
}异常处理要分层
- 业务异常(如「库存不足」)返回明确提示,HTTP 200 + 业务码 或 4xx。
- 系统异常(NPE、超时)返回通用提示,不能把堆栈暴露给前端,但必须完整记录日志。
- 兜底的
Exception处理器一定要有,否则异常会以默认错误页返回,体验差且可能泄露信息。
五、拦截器 vs 过滤器
| 对比 | Filter | Interceptor |
|---|---|---|
| 规范 | Servlet | Spring MVC |
| 作用范围 | 所有请求(含静态资源) | 只拦 Controller 请求 |
| 能拿到什么 | ServletRequest | HandlerMethod(知道调用哪个方法/注解) |
| 典型用途 | 编码、跨域、XSS、日志埋点 | 登录校验、权限、限流 |
java
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) {
if (req.getHeader("token") == null) {
resp.setStatus(401);
return false; // 返回 false 中断请求
}
return true;
}
}
// 注册
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login");
}
}六、跨域(CORS)
java
// 方式一:全局配置(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("*") // 生产要写具体域名!
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true)
.maxAge(3600);
}
}生产别用 allowedOrigins("*") + allowCredentials(true)
二者不能同时生效(浏览器会拒绝)。且 * 允许任意站点携带凭证请求,存在 CSRF 风险。生产必须白名单具体域名。
七、最佳实践
- Controller 只做参数校验和编排,业务逻辑放 Service。
- 统一返回结构(
Result<T>),前端处理一致。 - 全局异常处理器兜底,别让异常裸奔到前端。
- 登录校验用拦截器、编码/跨域用过滤器,各司其职。
- CORS 白名单化,别图省事用
*。 - RESTful 语义规范:GET 查询、POST 新增、PUT 全量改、PATCH 局部改、DELETE 删除。
📚 官方文档与参考资料
✏️ 练习与自测
- 画出
DispatcherServlet处理一次请求的完整流程,指出拦截器三个方法的调用时机。 @PathVariable、@RequestParam、@RequestBody分别从请求的哪个位置取值?- Filter 和 Interceptor 有什么区别?登录校验、跨域分别该用哪个?
- 写一个全局异常处理器,分别处理业务异常、参数校验异常和系统异常。
本节小结
Spring MVC 的核心是 DispatcherServlet + 九大组件:HandlerMapping 找方法、HandlerAdapter 执行、ArgumentResolver 绑参数、HandlerExceptionResolver 统一处理异常。拦截器能感知 Handler、过滤器不能;异常处理要分层,兜底处理器不能少;CORS 生产必须白名单。