不能靠全局静态变量长期持有异常对象来打造容灾层,因其违背容灾原则,引发线程安全、内存泄漏和状态污染风险;异常是瞬时上下文,应即时处理、不外泄,容灾层应持有无状态、线程安全的策略配置而非异常实例。

不能靠全局静态变量长期持有异常对象来打造容灾层。这种做法不仅违背容灾设计原则,还会引入线程安全、内存泄漏和状态污染等严重风险。
异常对象本质是瞬时上下文,不该被“存”
异常实例(如 NullPointerException、SQLException)携带的是某次执行中特定的堆栈、消息、业务参数等临时上下文。它不是配置、规则或资源——没有复用价值,也不具备跨请求/跨线程共享的合理性。
- 持有异常对象会固化错误现场,掩盖真实发生时间与调用链路
- 若被多线程读写(例如静态变量被并发设置),极易出现脏读、覆盖、NPE 等不可预测行为
- 异常对象通常引用了局部栈帧中的对象(如 Controller 参数、Service 实例),长期持有会阻止 GC,造成内存泄漏
容灾层真正该持有的是“可复用的处理策略”
容灾能力来自对故障的**识别、隔离、降级、恢复**逻辑,而非保存某个具体错误。应把精力放在以下静态可初始化的、无状态的、线程安全的支撑要素上:
-
异常类型到响应规则的映射表:如
Map<Class<? extends Throwable>, ResponseRule>,在静态块中预加载,只读共享 -
降级策略容器:如
static final FallbackRegistry,注册各服务的 fallback 方法引用(不持异常,只持方法句柄或 Supplier) -
熔断器配置模板:如
static final CircuitBreakerConfig DEFAULT_CONFIG,定义失败率阈值、超时、滑动窗口等元数据 -
兜底响应体模板:如
static final ErrorResponse SERVICE_UNAVAILABLE = new ErrorResponse(503, "服务暂不可用", "/")
异常对象应在作用域内即时处理,绝不外泄
从抛出到捕获再到响应,整个生命周期应控制在单次请求线程内。Spring 的 @ControllerAdvice 或过滤器链就是典型范式:
- 异常由业务代码
throw,进入框架异常分发器 - 分发器查静态规则表匹配类型,构造新响应(非复用原异常对象)
- 原始异常仅用于日志记录或监控上报(如提取 class、message、traceId),记录后即丢弃
- 绝不将
throwable赋值给public static Throwable LAST_ERROR这类字段
替代方案:用 ThreadLocal + 清理钩子做有限上下文透传
若确需在同一线程内跨多层传递异常相关信息(如 traceId、errorCode、重试次数),可用 ThreadLocal<ErrorContext>,但必须配合严格约束:
-
ErrorContext是轻量 POJO,不含异常实例本身,只含结构化字段(code、id、level) - 在请求入口
set(),出口(如 filter finally 或 WebMvcConfigurer#afterCompletion)中remove() - 避免在线程池中复用线程时残留上下文(尤其使用
Executors.newCachedThreadPool()时)

















