Spring 全局异常处理机制:多个 @ControllerAdvice 与重复 @ExceptionHandler

Spring 全局异常处理机制:多个 @ControllerAdvice 与重复 @ExceptionHandler

Spring 的异常处理机制允许你通过多个 @ControllerAdvice 类来组织异常处理逻辑。当存在多个处理器处理相同异常时,Spring 会按照特定规则决定使用哪个处理器。

处理规则

  1. 优先级顺序

    • 控制器本地的 @ExceptionHandler 方法优先级最高
    • 然后是按 @ControllerAdvice 类的顺序
    • 最后是默认的 Spring 异常处理
  2. 相同异常处理的选择

    • 当多个 @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());
    }
}

测试结果分析

  1. 访问 /test1 (抛出 IllegalArgumentException):

    • 首先检查控制器本地的处理器 → 使用本地方法
    • 如果移除本地处理器 → 使用 GlobalHandler2(因为它的 @Order 值更小,优先级更高)
    • 如果 GlobalHandler2 也没有处理 → 使用 GlobalHandler1
  2. 访问 /test2 (抛出 NullPointerException):

    • 没有本地处理器 → 使用 GlobalHandler2 中的处理方法
    • 如果 GlobalHandler2 没有处理 → 查找其他 @ControllerAdvice 类中的处理器
    • 如果都没有 → 使用 Spring 默认异常处理

执行顺序总结

  1. 控制器本地@ExceptionHandler 方法(如果有)
  2. 按顺序 检查 @ControllerAdvice 类:
    • 先检查 @Order 值较小的类(数值越小优先级越高)
    • 在同一类中按方法声明顺序检查
  3. 最后是 Spring 的默认异常处理

最佳实践建议

  1. 合理组织异常处理器

    • 按业务领域或异常类型分类组织 @ControllerAdvice
    • 例如:ValidationExceptionAdvice, SecurityExceptionAdvice, BusinessExceptionAdvice
  2. 明确指定顺序

    java 复制代码
    @ControllerAdvice
    @Order(Ordered.HIGHEST_PRECEDENCE)  // 最高优先级
    public class SecurityExceptionAdvice {
        // 处理安全相关异常
    }
    
    @ControllerAdvice
    @Order(Ordered.LOWEST_PRECEDENCE)  // 最低优先级
    public class GenericExceptionAdvice {
        // 处理通用异常
    }
  3. 避免重复处理相同异常

    • 除非有明确需求,否则避免在多个 @ControllerAdvice 中定义相同的异常处理
    • 可以通过继承或组合来复用异常处理逻辑
  4. 记录未处理异常

    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 方法,可以构建出清晰、灵活且易于维护的异常处理体系。

相关推荐
野犬寒鸦几秒前
从零起步学习并发编程 || 第二章:多线程与死锁在项目中的应用示例
java·开发语言·数据库·后端·学习
张np几秒前
java进阶-Zookeeper
java·zookeeper·java-zookeeper
long316几秒前
合并排序 merge sort
java·数据结构·spring boot·算法·排序算法
定偶1 分钟前
事务、触发器、存储过程与视图全解析
数据库·oracle
一起养小猫1 分钟前
Flutter for OpenHarmony 实战:打造功能完整的云笔记应用
网络·笔记·spring·flutter·json·harmonyos
Lisson 32 分钟前
VF01修改实际开票数量增强
java·服务器·前端·abap
范纹杉想快点毕业2 分钟前
STM32单片机与ZYNQ PS端 中断+状态机+FIFO 综合应用实战文档(初学者版)
linux·数据结构·数据库·算法·mongodb
WZTTMoon2 分钟前
【无标题】
java
没有腰的嘟嘟嘟3 分钟前
[特殊字符] 本地部署 Qwen3:4B 大模型并使用 Spring Boot 对接实践指南
spring·ai·spring ai
拓云者也4 分钟前
常用的生物信息学数据库以及处理工具
数据库·python·oracle·r语言·bash