核心问题是原始异常在哪一层被截断、改写或静默丢弃;需在最外层加兜底try-catch验证堆栈和cause是否完整,检查切面中throw ex;、漏传cause、空catch、降级未上抛等问题,并用@order控制切面顺序及日志工具辅助定位。

核心问题不是“怎么找断层”,而是“原始异常在哪一层被截断、改写或静默丢弃”。链路追踪断层的本质,是调用栈信息(stack trace)或异常因果链(cause chain)在某一层被破坏——比如堆栈被重置、cause被覆盖、或异常根本没抛出。
确认异常是否真正冒泡到最外层
先验证基础传播路径是否通畅:
- 在最外层(如 Spring 的 @Controller 或 WebFilter)加一个兜底 try-catch,打印 ex.getStackTrace() 和 ex.getCause(),看原始异常类名、消息、堆栈行号是否还保留
- 如果堆栈里看不到业务方法(比如 UserServiceImpl.updateUser),只看到拦截器类(如 LoggingAspect 或 RetryInterceptor),说明异常在中间层被
throw ex;重抛过,堆栈已丢失 - 如果
getCause()返回 null,或嵌套深度只有 1 层,说明中间层用了new BusinessException("xxx", ex)但没传 cause,或直接 new 了新异常没包装
逐层检查切面中的异常处理逻辑
重点关注每个 @Around 切面的 proceed() 调用前后:
- 是否在 catch 块中用了
throw ex;—— 这会清空原始堆栈,必须改成throw;(无参数 throw) - 是否在包装异常时漏传 cause:
new ServiceException("timeout", ex)✅ 正确;new ServiceException("timeout")❌ 断层 - 是否在 finally 或 around 逻辑中吞掉了异常:比如 try { proceed() } catch {}(空 catch),或只记录日志后 return null
- 是否对特定异常类型做了“降级处理”却未上抛:例如捕获 SQLException 后返回默认值,但调用方其实需要知道数据库失败
验证切面执行顺序与责任边界
多个切面嵌套时,顺序错乱会导致异常被错误拦截:
- 用
@Order(1)显式声明切面优先级,确保日志/监控类切面(需完整堆栈)在最外层,权限/重试类切面在内层 - 避免让“兜底转换切面”(如统一转成 ApiResponse)位于“业务校验切面”之前——否则校验失败还没进业务逻辑就被转成 success = false
- 检查是否某个切面在 preHandle 阶段就抛异常(如 AuthInterceptor 返回 false 并 throw),导致后续切面和目标方法根本没执行,链路自然中断
用工具辅助定位断点
不依赖肉眼排查,引入可观测性手段:
- 在每个切面的 around 方法入口打日志,输出
joinPoint.getSignature().toShortString()和当前线程 ID,观察调用链是否连续 - 启用 Spring Boot 的
spring.aop.proxy-target-class=true+ 开启 CGLIB 代理日志(DEBUG 级别 org.springframework.aop),查看代理对象实际调用路径 - 在开发环境开启
DeveloperExceptionPage或自定义全局异常处理器,把Throwable.printStackTrace(new PrintWriter(stringWriter))写入 MDC,确保每条日志带完整堆栈










