Spring MVC / Spring Boot全局异常处理实践指南
1. 关键机制速览
机制 | 场景 | 触发顺序 | 说明 |
---|---|---|---|
@ExceptionHandler |
处理特定控制器(或其父类)抛出的异常 | ① | 写在控制器里:局部异常处理 |
@ControllerAdvice / @RestControllerAdvice + @ExceptionHandler |
全局处理所有控制器异常 | ② | 推荐做全局兜底;@RestControllerAdvice 自动 @ResponseBody |
实现 HandlerExceptionResolver / 继承 ResponseEntityExceptionHandler |
需定制高度个性化逻辑或集成第三方框架 | ③ | Spring MVC 内部 SPI;优先级可配置 |
Filter / Aspect 级别捕获 | 想覆盖 Filter→Servlet 链或 AOP 切面 | ④ | 处理更底层、非 MVC 线程抛出的异常 |
Spring Cloud Gateway / Feign Fallback / Hystrix… | 分布式或网关层 | - | 属于微服务弹性保护,不在 MVC 处理链内 |
一般项目仅需
@RestControllerAdvice + @ExceptionHandler
即可满足 80% 以上场景。
2. 快速上手:@RestControllerAdvice
2.1 创建统一响应模型
// 通用响应包装
@Data
@AllArgsConstructor(staticName = "of")
public class ApiResponse<T> {
private Integer code; // 业务码
private String message; // 提示语
private T data; // 数据
}
2.2 定义业务异常
@Getter
public class BizException extends RuntimeException {
private final Integer code;
public BizException(Integer code, String message) {
super(message); this.code = code; }}
2.3 编写全局异常处理器
@Slf4j
@RestControllerAdvice // 等同于 @ControllerAdvice + @ResponseBodypublic class GlobalExceptionHandler {
/** 处理业务异常 */ @ExceptionHandler(BizException.class)
public ResponseEntity<ApiResponse<Void>> handleBiz(BizException ex) {
log.warn("业务异常: {}", ex.getMessage());
return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(ApiResponse.of(ex.getCode(), ex.getMessage(), null)); }
/** 处理校验失败(Bean Validation) */ @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<List<String>>> handleValidation(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(err -> err.getField() + ": " + err.getDefaultMessage()) .toList(); return ResponseEntity .badRequest() .body(ApiResponse.of(4001, "参数校验失败", errors));
}
/** 兜底:所有未处理的异常 */ @ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleDefault(Exception ex) {
log.error("系统异常", ex);
return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.of(5000, "系统繁忙,请稍后再试", null));
}}
小贴士
- 若希望统一返回 HTTP 200,而业务码区分错误,可把
ResponseEntity
改成ApiResponse
并在@ExceptionHandler
上添加@ResponseStatus(HttpStatus.OK)
。@RestControllerAdvice(basePackages="com.xxx.api")
可限制扫描范围,避免拦到 actuator 等端点。
3. 进阶技巧
3.1 自定义错误码枚举
@Getter
@AllArgsConstructor
public enum ErrorCode {
// 通用
OK(0, "成功"),
INVALID_PARAM(4001, "参数错误"),
SYSTEM_ERROR(5000, "系统错误"),
;
private final int code;
private final String msg;
}
在 BizException
中持有 ErrorCode
,便于前后端枚举同步。
3.2 与前端契约统一
-
REST 接口建议 HTTP Status ≈ 协议层错误
-
4xx→ 客户端请求有误(400 参数错 / 401 未登录 / 403 无权限 / 404 资源不存在)
-
5xx→ 服务端错误
-
业务错误用 响应体内的错误码 & message 描述,保持统一 JSON 结构。
3.3 处理异步线程异常
@ExceptionHandler
仅能捕捉 Spring MVC 调用线程 抛出的异常,若使用 @Async
或自定义线程池,可通过:
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // ... executor.setTaskDecorator(r -> () -> { try { r.run(); }
catch (Exception ex) { GlobalAsyncExceptionHandler.handle(ex); } }); return executor; }}
或给线程池设置 setRejectedExecutionHandler
/ setThreadFactory
收拢异常。
3.4 响应式 WebFlux
-
WebFlux 下仍可用
@RestControllerAdvice
;返回类型可以是Mono<ApiResponse<?>>
。 -
若用
RouterFunction
, 则需注册onError
处理器:RouterFunctions.route(). GET("/api/x", handler::foo) .onError(BizException.class, (ex, req) -> ServerResponse .status(HttpStatus.BAD_REQUEST) .bodyValue(ApiResponse.of(ex.getCode(), ex.getMessage(), null)))
3.5 国际化 / 多语言
在 GlobalExceptionHandler
中注入 MessageSource
,通过 LocaleContextHolder.getLocale()
获取当前语言,将错误码映射为多语言消息后返回。
4. 常见陷阱
- 异常被拦在过滤器 / 拦截器
- 如
OncePerRequestFilter
里手动response.getWriter().write(...)
,Spring MVC 将不再进入异常解析链。 - 解决:把异常往上抛或统一在 Filter 里构造 JSON。
- 返回流/文件下载
- Controller 方法直接输出文件流时如果抛异常,可能已写入响应头导致无法再改 HTTP status;需在业务层提前校验,或使用自定义异常提示前端“下载失败”。
- 重复包装异常
- 建议业务层只抛 业务异常 或直接抛原生异常,不要层层 try/catch 再 throw new RuntimeException,会丢失原始堆栈;在全局处理器里统一记录原始 cause。
- 未对接监控告警
- 全局异常处理做好了,但若没有把严重异常接入 APM / Prometheus Alertmanager,会导致后台静默报错无法发现。
- 建议在
@ExceptionHandler(Exception.class)
里增加异常计数埋点或调用告警接口。
5. 小结 Checklist ✅
- ☑ 使用
@RestControllerAdvice
+ 多个@ExceptionHandler
。 - ☑ 设计统一
ApiResponse
、ErrorCode
,前后端对齐。 - ☑ 记录日志:业务异常用
warn
,系统异常用error
。 - ☑ 捕获
MethodArgumentNotValidException
/ConstraintViolationException
解决校验提示。 - ☑ 若有异步 / 线程池,保证异常能上报。
- ☑ 对接监控与告警,让严重错误第一时间可见。
按上面思路落地,全局异常处理即可覆盖 Web、REST、接口校验、业务错误 等大部分场景,在生产中兼顾 一致性、可维护性、可观测性。