Spring 全局异常处理机制:多个 @ControllerAdvice 与重复 @ExceptionHandler
Spring 的异常处理机制允许你通过多个 @ControllerAdvice
类来组织异常处理逻辑。当存在多个处理器处理相同异常时,Spring 会按照特定规则决定使用哪个处理器。
处理规则
-
优先级顺序:
- 控制器本地的
@ExceptionHandler
方法优先级最高 - 然后是按
@ControllerAdvice
类的顺序 - 最后是默认的 Spring 异常处理
- 控制器本地的
-
相同异常处理的选择:
- 当多个
@ControllerAdvice
类中定义了相同的异常处理时,Spring 会选择第一个匹配的处理器 - 可以通过
@Order
注解或实现Ordered
接口来控制@ControllerAdvice
的执行顺序
- 当多个
示例代码
场景描述
假设我们有两个全局异常处理器和一个控制器,都处理 IllegalArgumentException
。
java
// 第一个全局异常处理器
@ControllerAdvice
@Order(1) // 较低优先级
public class GlobalExceptionHandler1 {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument1(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body("Global Handler 1: " + ex.getMessage());
}
}
// 第二个全局异常处理器
@ControllerAdvice
@Order(0) // 较高优先级
public class GlobalExceptionHandler2 {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument2(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body("Global Handler 2: " + ex.getMessage());
}
@ExceptionHandler(NullPointerException.class)
public ResponseEntity<String> handleNullPointer(NullPointerException ex) {
return ResponseEntity.badRequest().body("NullPointer handled by Handler2");
}
}
// 控制器类
@RestController
public class MyController {
@GetMapping("/test1")
public String test1(@RequestParam(required = false) String param) {
if (param == null) {
throw new IllegalArgumentException("Param is required");
}
return "Success: " + param;
}
@GetMapping("/test2")
public String test2() {
throw new NullPointerException("Oops, null pointer");
}
// 本地异常处理 - 优先级最高
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleLocalIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body("Local Controller Handler: " + ex.getMessage());
}
}
测试结果分析
-
访问
/test1
(抛出IllegalArgumentException
):- 首先检查控制器本地的处理器 → 使用本地方法
- 如果移除本地处理器 → 使用
GlobalHandler2
(因为它的@Order
值更小,优先级更高) - 如果
GlobalHandler2
也没有处理 → 使用GlobalHandler1
-
访问
/test2
(抛出NullPointerException
):- 没有本地处理器 → 使用
GlobalHandler2
中的处理方法 - 如果
GlobalHandler2
没有处理 → 查找其他@ControllerAdvice
类中的处理器 - 如果都没有 → 使用 Spring 默认异常处理
- 没有本地处理器 → 使用
执行顺序总结
- 控制器本地 的
@ExceptionHandler
方法(如果有) - 按顺序 检查
@ControllerAdvice
类:- 先检查
@Order
值较小的类(数值越小优先级越高) - 在同一类中按方法声明顺序检查
- 先检查
- 最后是 Spring 的默认异常处理
最佳实践建议
-
合理组织异常处理器:
- 按业务领域或异常类型分类组织
@ControllerAdvice
类 - 例如:
ValidationExceptionAdvice
,SecurityExceptionAdvice
,BusinessExceptionAdvice
- 按业务领域或异常类型分类组织
-
明确指定顺序:
java@ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级 public class SecurityExceptionAdvice { // 处理安全相关异常 } @ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE) // 最低优先级 public class GenericExceptionAdvice { // 处理通用异常 }
-
避免重复处理相同异常:
- 除非有明确需求,否则避免在多个
@ControllerAdvice
中定义相同的异常处理 - 可以通过继承或组合来复用异常处理逻辑
- 除非有明确需求,否则避免在多个
-
记录未处理异常:
java@ControllerAdvice @Order(Ordered.LOWEST_PRECEDENCE) public class LoggingExceptionAdvice { private static final Logger logger = LoggerFactory.getLogger(LoggingExceptionAdvice.class); @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleUncaughtException(Exception ex) { logger.error("Unhandled exception occurred", ex); return ResponseEntity .status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ErrorResponse("SERVER_ERROR", "An unexpected error occurred")); } }
通过合理利用多个 @ControllerAdvice
类和 @ExceptionHandler
方法,可以构建出清晰、灵活且易于维护的异常处理体系。