核心是将错误码绑定到异常实例并支持热加载:errorcode作为不可变属性随异常创建即封装语义,通过自定义classloader实现模块级热更新,异常链保留cause确保根因可追溯,errorwebexceptionhandler按类型+错误码双维度响应。
核心在于让错误码逻辑脱离静态定义,绑定到异常实例本身,并通过可热加载的模块承载业务语义——这样网关重启非必需,新错误规则随新异常类加载即生效。
错误码必须作为异常实例属性,而非静态常量
把 ErrorCode 写成枚举或接口常量,再在 catch 里硬编码 throw new BusinessException(ErrorCode.ORDER_TIMEOUT),看似规范,实则锁死更新路径:每次改含义或加场景都得改代码、编译、发版、重启网关。正确做法是让 ErrorCode 成为异常对象不可变的一部分:
- ErrorCode 枚举每个值携带 code、httpStatus、messageTemplate(如 “订单 %s 超时”)
- BusinessException 构造时接收 ErrorCode + 占位参数(如 orderNo),内部立即格式化 message,并缓存原始参数供后续解析
- 异常创建即完成语义封装,只要新异常类被类加载器加载(SPI 或自定义 ClassLoader 可触发),新规则就自动参与链路
异常链必须保留 cause,确保根因可追溯
网关常见异常来源多样:下游连接拒绝、5xx 响应、鉴权拦截、限流熔断等。若在 GlobalFilter 中捕获后只抛新异常却不传 cause,原始堆栈和状态将彻底丢失:
- ❌ 错误示例:throw new BusinessException(ErrorCode.SERVICE_UNAVAILABLE, "服务暂不可用");
- ✅ 正确示例:throw new BusinessException(ErrorCode.SERVICE_UNAVAILABLE, "服务暂不可用", originalException);
- 这样 ExceptionChain 完整保留从 Netty 异常 → WebClient 失败 → 网关包装的全路径,日志中能直接区分是 Connection refused 还是 TimeoutException
网关异常处理器需按类型+错误码双维度响应
Spring Cloud Gateway 基于 WebFlux,不能用 @ControllerAdvice。必须实现 ErrorWebExceptionHandler,在 handle 方法中做两层判断:
- 先识别异常类型(BusinessException / SystemException / ExternalServiceException),判断是否属于业务可控错误
- 再提取其 errorCode 属性(通过反射或接口契约),结合 httpStatus 和 messageTemplate 渲染响应体
- 通过 SPI 加载独立异常模块(如 error-code-module.jar),替换 jar 即完成热更新,无需重启网关进程
类加载机制是热部署落地的关键支撑
热部署不等于“不重启”,而是避免全局类加载器污染。需利用双亲委派模型的突破点:
- 为异常模块设计独立的自定义 ClassLoader,打破父加载器对旧类的强引用
- 卸载旧模块时,显式清除该 ClassLoader 及其加载的所有类(配合弱引用缓存与 GC 友好设计)
- 新模块加载后,ErrorWebExceptionHandler 通过接口(如 ErrorCodeProvider)动态发现并调用新版异常逻辑











