全局异常捕获失效的关键是异常传播路径被拦截器、过滤器、AOP或多个@ControllerAdvice提前截断;需按顺序排查preHandle异常吞没、@ControllerAdvice优先级冲突、事务/AOP静默捕获及处理器自身未生效等问题。

排查全局异常捕获被局部拦截覆盖,关键不是“找谁写了 try-catch”,而是理清异常传播路径上哪些组件提前截断了它。常见覆盖点集中在拦截器、过滤器、AOP切面和多个 @ControllerAdvice 之间,它们的执行顺序和作用范围决定了异常是否能抵达你的全局处理器。
检查拦截器或过滤器是否提前吞掉异常
Spring MVC 中,拦截器(Interceptor)的 preHandle 方法若抛出异常,不会进入 Controller,也不会触发 @ExceptionHandler;它会被 Spring 的 DispatcherServlet 拦截并转发到 /error,默认返回白页或 500 页面。Sa-Token、Shiro、JWT 校验等常在此处出问题。
- 确认所有拦截器是否在
preHandle中直接 throw 异常(如 NotLoginException),而不是调用c.setComplete()+c.getResponse().sendError() - 检查拦截器配置是否排除了
/error路径,否则转发后二次触发拦截器,可能引发上下文丢失(如 SaTokenContextException) - 优先改用 SaServletFilter 等 Servlet 过滤器替代拦截器做认证,因 Filter 在 DispatcherServlet 之前执行,异常可正常透传给全局处理器
验证多个 @ControllerAdvice 是否存在优先级冲突
@ControllerAdvice 默认无序生效,若项目中定义了多个,且其中一个未指定包路径(即作用于全部 Controller),它会覆盖其他带 basePackages 的处理器——哪怕后者更精准。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 运行时加 JVM 参数
-Dlogging.level.org.springframework.web.servlet=DEBUG,观察日志中 “Mapped to” 和 “Resolved [xxxException]” 是否命中你预期的 @ExceptionHandler 方法 - 为每个 @ControllerAdvice 显式添加 @Order(Ordered.HIGHEST_PRECEDENCE) 或数值(如
@Order(1)),确保关键处理器优先执行 - 避免使用裸
@ControllerAdvice,始终配合basePackages = "com.xxx.api"或annotations = RestController.class限定作用域
确认异常类型是否被 AOP 或事务代理意外捕获
Seata、@Transactional、自定义 AOP 切面等,都可能在方法执行前后插入逻辑。若它们在 catch 块中未重新 throw,或仅 log 后静默返回,异常就永远到不了你的全局处理器。
- 在业务方法上打日志,确认异常是否真实抛出(如
log.error("库存不足,抛出 InventoryNotEnoughException", e)) - 检查 Seata 的
@GlobalTransactional方法:若全局异常处理器先于 Seata 的 GlobalTransactionalInterceptor 拦截异常,Seata 就收不到,导致事务不回滚 - 临时移除事务注解或 AOP 切面,验证异常能否被全局处理器捕获——能,则说明是代理层拦截;不能,则问题在更上游
用最小复现验证异常传播链路
写一个最简 Controller,手动 throw 新异常,绕过所有中间件:
- 新增测试接口:
@GetMapping("/test-exception") public void test() { throw new RuntimeException("test"); } - 确认该异常是否被你的 GlobalExceptionHandler 捕获;若否,说明全局处理器本身未生效(检查包扫描、@ComponentScan、是否被条件化禁用)
- 再逐步加入拦截器、事务、参数校验(@Valid),每次加一项,观察捕获是否中断,快速定位覆盖节点

















