全局异常处理需遵循四原则:顺序从细到粗、类型精准区分、响应可控友好、日志完整留痕;否则易致500错误、错误码混乱、堆栈泄露及前端接收不到统一json。

Spring Boot 全局异常处理写得“看起来能用”,但上线后常出现 500 页面、错误码乱飞、堆栈泄露、前端收不到统一 JSON——问题不在不会写,而在没避开关键陷阱。核心就四件事:顺序不能错、类型要精准、响应要可控、日志得留痕。
异常处理器的声明顺序必须从细到粗
Spring MVC 按照 @ExceptionHandler 方法的声明顺序匹配异常,父类异常(如 Exception)如果写在前面,会直接拦截所有子类(如 BusinessException、ValidationException),导致更具体的处理逻辑永远不执行。
- ✅ 正确写法:先写子类,再写兜底
@ExceptionHandler(ConstraintViolationException.class)@ExceptionHandler(MethodArgumentNotValidException.class)@ExceptionHandler(BusinessException.class)-
@ExceptionHandler(Exception.class)(放最后) - ❌ 错误写法:把
@ExceptionHandler(Exception.class)写在第一个
区分校验异常和业务异常,别全扔给 Exception 处理
参数校验失败(如 @Valid 报错)抛的是 MethodArgumentNotValidException 或 ConstraintViolationException,不是你自定义的 BusinessException,也不是 RuntimeException。混在一起处理,会导致错误信息丢失、字段定位不准。
- 对 JSR-303 校验,提取
getFieldError().getField()和getDefaultMessage(),返回带字段名的提示,例如:{"username":"用户名不能为空"} - 对业务异常(如“库存不足”),建议继承 RuntimeException 并标注
@ResponseStatus(HttpStatus.BAD_REQUEST),方便语义化识别 - 避免在兜底方法里直接
e.getMessage()返回——空指针时可能为 null,JSON 序列化会报错
生产环境必须屏蔽堆栈,但日志里要完整记录
用户看到 stacktrace 是严重安全风险;运维查不到完整堆栈又没法定位根因。二者必须分离。
- 响应体中只返回友好提示,如
"系统繁忙,请稍后再试"或"订单状态异常,请联系客服" - 日志中必须用
log.error("Global exception caught", e)—— 第二个参数传异常对象,才能打印完整堆栈 - 别写
log.error("error: " + e),这样只打 toString(),丢掉关键上下文
确保全局处理器对所有 Controller 生效,尤其注意 AOP 和异步场景
@RestControllerAdvice 默认只作用于 Spring MVC 的请求处理链路。以下情况它不生效:
- Controller 内部用
@Async调用的方法抛异常 → 需配合AsyncUncaughtExceptionHandler - Filter 或 Interceptor 中抛出的异常 → 需单独配置
ErrorPageRegistrar或自定义 Filter 包裹 - 静态资源或未映射路径(如 /favicon.ico)触发的 404 → 属于容器层,需通过
server.error.whitelabel.enabled=false关闭默认页,并配/error接口 - 确保类在 Spring 扫描路径下,且没有被
@Profile或条件注解意外排除
真正稳定的全局异常处理,不是堆一个 @ExceptionHandler 就完事,而是理清异常来源、分层拦截、精准响应、闭环日志。写对一次,省去线上排查两小时。











