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

相关推荐
小Tomkk5 分钟前
数据库 变更和版本控制管理工具 --Bytebase 安装部署(linux 安装篇)
linux·运维·数据库·ci/cd·bytebase
独断万古他化9 分钟前
【SSM开发实战:博客系统】(三)核心业务功能开发与安全加密实现
spring boot·spring·mybatis·博客系统·加密
rannn_11126 分钟前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习
灵感菇_28 分钟前
Java HashMap全面解析
java·开发语言
qq_124987075330 分钟前
基于JavaWeb的大学生房屋租赁系统(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·计算机视觉·毕业设计·计算机毕业设计
短剑重铸之日36 分钟前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
若鱼19191 小时前
SpringBoot4.0新特性-Observability让生产环境更易于观测
java·spring
倒流时光三十年1 小时前
SpringBoot 数据库同步 Elasticsearch 性能优化
数据库·spring boot·elasticsearch
觉醒大王1 小时前
强女思维:着急,是贪欲外显的相。
java·论文阅读·笔记·深度学习·学习·自然语言处理·学习方法
努力学编程呀(๑•ี_เ•ี๑)1 小时前
【在 IntelliJ IDEA 中切换项目 JDK 版本】
java·开发语言·intellij-idea