Spring MVC 中如何处理异常?
- 标签:后端、Spring
🏛 业务架构师
Spring MVC 的异常处理有四个层次,从局部到全局:
- @ExceptionHandler(局部):写在单个 Controller 里,处理该类请求的异常——只适合特定控制器的定制逻辑;
- @ControllerAdvice / @RestControllerAdvice(全局,主流方案):集中定义异常到响应的映射,全应用生效;
- HandlerExceptionResolver 接口:更底层的扩展点(@ControllerAdvice 就由其支撑),框架或自研组件需要编程式接管时用;
- 容器 ErrorPage / /error 端点:兜底,漏网的异常最终由 Servlet 容器错误页转成 500。
架构师视角,异常处理的本质是错误码体系与告警分级的企业级治理,我的标准设计:
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public Result<Void> biz(BizException e) {
return Result.fail(e.getCode(), e.getMessage()); // 业务异常:正常分支,info 级
}
@ExceptionHandler(Exception.class)
public Result<Void> unknown(Exception e) {
log.error("system error", e);
metrics.increment("sys.error"); // 系统异常:告警+计数
return Result.fail("SYSTEM_BUSY");
}
}三条纪律:业务异常与系统异常严格分级(决定告警信噪比与值班负担);错误信息不回传堆栈细节(防信息泄露,细节进日志用 traceId 关联);所有异常路径返回结构一致的响应体(前端统一处理)。
🏗 系统架构师
四级体系:
@ExceptionHandler:Controller 内局部;@ControllerAdvice/@RestControllerAdvice:全局收口(主力);ResponseStatusException:直接抛携带状态码的异常,一次性场景;- DefaultHandlerExceptionResolver:框架异常翻译(参数校验失败转 400 等)。
我的全局设计标准:
- 统一错误契约:code/message/traceId 三件套;业务异常(自定义异常加错误码表)转 4xx,系统异常转 5xx 且不外泄细节;
- 告警分流:4xx 计数不动告警(调用方问题),5xx 触发告警并带 traceId 直达链路现场——异常体系直接决定 MTTR;
- 匹配顺序:先精确类型后父类,Advice 里的兜底 Exception 处理器是最后防线;
- Spring Boot 另有
/error端点兜底 Filter 与异步超时等 MVC 链路之外的异常,自定义 ErrorController 保持契约一致。
💻 全栈开发专家
四层手段,按使用频率排:
1. @ExceptionHandler + @RestControllerAdvice(主力):
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<Void> invalid(MethodArgumentNotValidException e) {
return Result.fail(400, buildFieldMsg(e));
}
@ExceptionHandler(Exception.class) // 兜底
public ResponseEntity<Result<Void>> unknown(Exception e) {
log.error("unhandled", e); // 必须带堆栈
return ResponseEntity.status(500).body(Result.fail("SYSTEM_ERROR"));
}
}2. @ResponseStatus:标异常类上指定状态码,简单场景免写 handler。
3. SimpleMappingExceptionResolver:配置式映射异常到视图,老模板项目用。
4. ErrorController(/error):最容易被忽略的兜底层——Filter 里抛的异常、静态资源 404 这类没进到 handler 的错误,@ControllerAdvice 接不住,会转发到 /error(Boot 内置 BasicErrorController),需要时自定义接管。
生产细节:错误码分级(参数错/业务错/系统错)方便前端和告警分流;兜底日志必须 error 带堆栈(只打 message 丢根因的惨案见过);Boot 3 可用 ProblemDetail 输出 RFC 7807 标准错误体。