Spring Boot 全局异常处理需三步:①自定义异常继承 RuntimeException 并含 code/message 字段;②@ControllerAdvice 类须被 Spring 扫描到,推荐放启动类同包或子包;③@ExceptionHandler 方法必须是非静态实例方法,返回统一响应结构并记录日志。

Spring Boot 中捕获并处理自定义异常,核心是用 @ControllerAdvice + @ExceptionHandler 搭配自定义异常类,实现全局、统一、可维护的错误响应。关键不在“能不能写”,而在于“Spring 能不能扫到、能不能调到”。下面分三步说清楚实际落地要点。
自定义异常类要继承 RuntimeException
这是最常见也最容易踩坑的一点。Spring MVC 默认只对非受检异常(即继承 RuntimeException)做自动传播和拦截;如果继承 Exception,必须显式 throws,且 @ExceptionHandler 可能静默失效。
- ✅ 推荐写法:让自定义异常直接继承
RuntimeException - ✅ 建议带状态码和消息字段,方便前端识别,例如:
public class BizException extends RuntimeException {
private final int code;
private final String message;
public BizException(int code, String message) {
super(message);
this.code = code;
this.message = message;
}
// getter 省略
@ControllerAdvice 类必须被 Spring 扫描到
哪怕代码写得完全正确,如果这个类没被 Spring 加载成 Bean,整个异常处理器就形同虚设——抛异常时不会进你的 @ExceptionHandler 方法,而是直接返回 500 或白页。
- ✅ 最稳妥做法:把
@ControllerAdvice类放在主启动类所在包或其子包下(如启动类在com.example.app,异常处理器放com.example.app.advice) - ✅ 若必须跨包,需在启动类上显式声明扫描路径:
@SpringBootApplication
@ComponentScan(basePackages = {"com.example.app", "com.example.exception"}) - ❌ 不要放在完全无关的包里,也不依赖 IDE 自动补全的包路径(容易错)
@ExceptionHandler 方法必须是非静态实例方法
Spring 通过代理机制调用异常处理器,static 方法无法被 AOP 拦截,会导致该 handler 完全不生效,且无任何报错提示。
- ✅ 正确写法:
@ExceptionHandler(BizException.class)
public ResponseEntityhandleBizException(BizException e) {
return ResponseEntity.status(200).body(Result.fail(e.getCode(), e.getMessage()));
} - ❌ 错误写法:
public static ResponseEntity<...> handle...</...>—— 即使编译通过,运行时也无效 - ⚠️ 补充:方法参数可以是异常本身、
HttpServletRequest、HttpServletResponse等,但不要加static
额外建议:搭配统一响应结构和日志记录
光捕获不够,还要让结果对前端友好、对后端可追溯。
- 返回值建议封装为统一格式(如
Result<t></t>),包含code、message、data字段 - 在
@ExceptionHandler方法中加一行日志,例如:
log.error("BizException occurred: {}, code={}", e.getMessage(), e.getCode(), e); - 可同时处理多种异常(如参数校验异常
MethodArgumentNotValidException),共用一个响应结构


















