核心是将错误码变为可加载、可携带、可解析的运行时对象,绑定到异常实例而非硬编码;捕获时保留cause并注入上下文;异常处理器需按类型+错误码双维度识别并支持热更新映射规则;通过spi插件化加载错误码模块实现热部署。

核心在于把错误码从“静态常量”变成“可加载、可携带、可解析的运行时对象”,捕获闭环不是为了拦截异常,而是让异常在生成、传播、识别、响应全链路中始终携带语义明确的错误码,并支持不重启网关即可更新规则。
错误码必须绑定到异常实例,而非写死在catch块里
硬编码 throw new BusinessException(ErrorCode.ORDER_TIMEOUT) 会导致每次改码都要改代码、编译、发版、重启——根本谈不上热部署。正确做法是:
- ErrorCode 定义为枚举,每个值包含 code(数字)、httpStatus(状态码)、messageTemplate(带占位符的模板)
- BusinessException 构造器强制接收 ErrorCode + 可变参数(如 orderId、skuId),内部立即格式化 message,并将原始参数存入 final 字段供后续解析
- 新错误码逻辑封装在异常创建环节;只要新版本异常类通过 SPI 或类加载器热加载进 JVM,新规则就自动生效
捕获环节要保留 cause 并注入上下文,构建可追溯的异常链
网关常见异常来源多样:下游超时、连接拒绝、JWT 解析失败、限流触发、Schema 校验不通过……若捕获后只抛新异常却不传 cause,根因就断了。
- ❌ 错误写法:throw new BusinessException(ErrorCode.SERVICE_UNAVAILABLE, "服务暂不可用");
- ✅ 正确写法:throw new BusinessException(ErrorCode.SERVICE_UNAVAILABLE, "服务暂不可用", originalException);
- 这样 ExceptionChain 能完整呈现:Netty 异常 → WebClient 调用失败 → 网关包装异常,日志中一眼定位是 Connection refused 还是 TimeoutException
- 同时建议在构造时注入请求 ID、traceId、客户端 IP 等轻量上下文,便于问题归因
异常处理器需双维度识别:类型 + 错误码,实现动态响应路由
Spring Cloud Gateway 是响应式栈,不能用 @ControllerAdvice。必须实现 ErrorWebExceptionHandler,在 handle 方法中做两层判断:
- 第一层:识别异常是否属于业务可控类(如 BusinessException、ValidationException),排除系统级致命异常(如 OutOfMemoryError)
- 第二层:反射提取 errorCode 属性(或统一接口 getErrorCode()),查表匹配 HTTP 状态码、响应体结构、是否需审计日志
- 映射表本身应支持热更新:例如从 Nacos 配置中心监听 /error-code/routing,变更后自动刷新本地缓存,无需重启
用插件化机制承载错误码模块,实现真正热部署
把错误码定义、异常类、消息模板、HTTP 映射规则打包成独立 jar 模块,通过 SPI 加载:
- 定义 ErrorCodeProvider 接口,各模块实现并声明在 META-INF/services/ 下
- 网关启动时扫描并注册所有提供者;运行时可通过 Admin Endpoint 触发 reload,重新加载指定模块
- 配合类加载隔离(如 URLClassLoader),可做到单个错误码模块更新不影响其他功能
- 前端调用 /actuator/error-codes 可实时查看当前已加载的所有错误码及其描述










