网关统一捕获异常的关键是实现errorwebexceptionhandler接口并替换默认处理器,而非使用@controlleradvice;需在globalfilter中将下游json错误码转为异常,再由自定义处理器分类响应。

Java 自定义异常在微服务网关中统一捕获,关键不在“能不能 throw”,而在于“谁来 catch、在哪 catch、怎么转译”。网关(如 Spring Cloud Gateway)是响应式架构(WebFlux),不走传统 MVC 的 @ControllerAdvice 路径,直接把自定义异常扔进网关的异常处理器里是无效的——它根本收不到。
网关层不能用 @RestControllerAdvice
Spring Cloud Gateway 基于 WebFlux,其请求生命周期由 Netty 线程驱动,异常不会经过 Spring MVC 的异常解析链。即使你在网关模块写了 @RestControllerAdvice,它对路由转发过程中的异常(如下游超时、4xx/5xx 响应、路由失败)完全无感知。
- 网关内部抛出的异常(如
ResponseStatusException、IllegalArgumentException)由 Spring Boot 默认的ErrorWebExceptionHandler处理 - 下游服务返回的 HTTP 错误码(如 500、404)会被 WebClient 封装为
WebClientResponseException,但该异常发生在 Filter 链中,需主动拦截 - 若想统一处理这些异常,必须实现
ErrorWebExceptionHandler接口,并通过@Component+@Order(-1)替换默认处理器
自定义异常要先“到达网关”才能被捕获
业务服务抛出的自定义异常(如 BusinessException)默认不会透传到网关——Feign/WebClient 调用时,它们只拿到 HTTP 响应体和状态码,原始 Java 异常堆栈早已丢失。所以网关无法直接捕获你的 BusinessException,除非你主动把它“翻译”成网关可识别的信号。
- 下游服务应在返回体中携带标准错误字段(如
{"code":"USER_NOT_FOUND","message":"用户不存在"}),并配合适当 HTTP 状态码(如 400) - 网关在
GlobalFilter中解析响应 body 和 status code,识别出业务错误后,手动构造对应语义的异常(如new BusinessException("USER_NOT_FOUND")),再抛出供ErrorWebExceptionHandler统一处理 - 避免在网关里反序列化任意下游异常类(存在类加载和安全风险),始终以 JSON 字段 + 状态码为契约
统一捕获的核心实现:ErrorWebExceptionHandler
这是网关唯一可靠的全局异常入口。你需要提供一个自定义实现,覆盖 handle() 方法,在其中做三件事:判空、分类、写响应。
- 检查
response.isCommitted(),已提交则不再干预,防止重复写响应 - 按异常类型分组处理:
ResponseStatusException→ 提取 status code;WebClientResponseException→ 解析 response body 获取 error code;其他运行时异常(如NullPointerException)→ 记录告警并返回 500 - 将所有异常映射为标准响应结构(如
ApiResult.fail("GATEWAY_TIMEOUT", "网关请求下游超时")),用WebFrameworkUtils.writeJSON()或原生ServerHttpResponse.writeWith()写出
配套策略:让异常真正“有意义”
捕获只是第一步,后续响应逻辑决定用户体验和系统韧性:
- 对下游返回的 5xx,不要简单转成 500,应补充 traceId、上游服务名、耗时等上下文,方便排查
- 对明确的业务错误码(如
"ORDER_PAID"),网关可直接返回前端友好提示,无需穿透到调用方再解析 - 结合 Resilience4j,在异常触发时同步更新熔断器状态;例如连续 3 次收到
WebClientResponseException,就触发 fallback 返回缓存结果 - 所有异常日志必须结构化(JSON 格式),包含
traceId、spanId、service、upstream、status字段,接入 ELK 或 Prometheus
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











