在 Spring Cloud Gateway 的响应式架构中,全局异常捕获实际上只有两条可行路径:要么采用 ErrorWebExceptionHandler 实现框架级别的兜底处理,要么借助 GlobalFilter 在链路层面进行拦截。传统的 Servlet 模式下的 @ControllerAdvice 在 WebFlux 环境中完全无法生效——这一点必须首先明确,避免走弯路。

之所以如此限定,是因为 Gateway 基于 WebFlux,不依赖 Servlet 容器的 Filter 链,因此自然无法使用 ja vax.servlet.Filter 来捕获路由异常。真正有效的只有这两类,但它们各自的定位与适用场景差异较大,使用前需先理清。
使用 ErrorWebExceptionHandler 替换默认异常处理器
这是最推荐的全局兜底方案,没有之一。它能够捕获所有未被上游过滤器处理的异常——包括路由匹配失败、转发超时、下游服务不可达、限流触发等场景,并且优先级高于 Spring Boot 自带的默认实现。
- 实现
ErrorWebExceptionHandler接口,重写handle()方法 - 添加
@Component和@Order(-1)确保优先加载 - 先检查
response.isCommitted(),避免重复写入已提交的响应 - 统一构建 JSON 格式响应体(例如
{"code":500,"message":"服务暂时不可用"}),并设置Content-Type: application/json - 对常见异常分类处理:
ResponseStatusException提取状态码,TimeoutException返回 504,ConnectException返回 503
利用 GlobalFilter 拦截并提前终止异常链
此方案适用于在请求进入路由前进行前置校验(如鉴权、参数校验),或在路由执行后捕获转发过程中抛出的异常。但需注意,它不能直接捕获网关内部组件(如断路器、限流器)抛出的原始异常——除非这些组件显式抛出并且异常被链路传递下来。
- 实现
GlobalFilter,使用@Order(-2)确保其早于路由过滤器执行 - 在
chain.filter(exchange).onErrorResume(...)中捕获异常 - 区分异常类型:
ResponseStatusException直接设置对应状态码,否则设置为 500 并记录日志 - 调用
exchange.getResponse().setComplete()终止后续过滤器执行 - 避免在
onErrorResume中抛出新异常——否则会再次触发ErrorWebExceptionHandler,形成循环
注意限流、断路器等组件的异常穿透问题
像 RequestRateLimiter 或 Spring Cloud CircuitBreaker 抛出的异常,默认不会进入 GlobalFilter 的 onErrorResume,因为这些异常是在独立过滤器中完成的。若想统一处理,需要换一种思路:
- 在配置中显式指定回退逻辑,例如限流失败时返回自定义响应体(需要配合
redis-rate-limiter的denyEmptyResponse和自定义RateLimiter实现) - 让这些组件抛出标准
ResponseStatusException(例如new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS)),这样就能被ErrorWebExceptionHandler统一接管 - 不建议在
GlobalFilter中尝试重写限流过滤器逻辑——容易破坏原有的执行顺序与状态一致性
不要使用 @ControllerAdvice
这一点必须单独强调。该注解依赖于 Spring MVC 的同步请求上下文,在 WebFlux 响应式网关中完全无效。网关没有 Controller 层,也不经过 DispatcherHandler 的异常处理流程。即使添加 @RestControllerAdvice,也不会产生任何效果——它只是一个摆设。
