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

相关推荐
月夕·花晨13 小时前
Gateway-过滤器
java·分布式·spring·spring cloud·微服务·gateway·sentinel
hssfscv14 小时前
JAVA学习笔记——9道综合练习习题+二维数组
java·笔记·学习
初听于你16 小时前
缓存技术揭秘
java·运维·服务器·开发语言·spring·缓存
小蒜学长17 小时前
springboot多功能智能手机阅读APP设计与实现(代码+数据库+LW)
java·spring boot·后端·智能手机
恒悦sunsite18 小时前
Ubuntu之apt安装ClickHouse数据库
数据库·clickhouse·ubuntu·列式存储·8123
奥尔特星云大使18 小时前
MySQL 慢查询日志slow query log
android·数据库·mysql·adb·慢日志·slow query log
来自宇宙的曹先生19 小时前
MySQL 存储引擎 API
数据库·mysql
间彧19 小时前
MySQL Performance Schema详解与实战应用
数据库
间彧19 小时前
MySQL Exporter采集的关键指标有哪些,如何解读这些指标?
数据库
weixin_4462608519 小时前
Django - 让开发变得简单高效的Web框架
前端·数据库·django