Spring Cloud Gateway 的过滤器体系最劝退人的地方,就是类名极度相似:GatewayFilter、GatewayFilterFactory、GlobalFilter,再加上配置文件里那一串像AddRequestHeader这样的短名字,很容易让人绕晕。我之前有一段经历很典型:团队在做网关改造,我照着官方文档把十几个过滤器往配置里一贴,跑起来完全没问题,但某天线上出了个诡异的响应头重复问题,我盯着配置看了半天,愣是不知道该从哪里下手排查。那一刻我意识到,光会用不行,必须把这层抽象给啃透。所以这篇我来做一次集中梳理,把GatewayFilter这个层面的东西完整过一遍,顺便把它的装配链路和几个核心过滤器的源码拆给你看。
这一篇对应的是整个源码解析系列的过滤器部分,我把 4.1 这一节当作“总览 + 索引”来处理:先讲清楚 GatewayFilter 在整个架构里的位置,再把内置过滤器全部盘点一遍,接着走一遍从路由配置到过滤器链的完整装配链路,最后挑出三个最典型的过滤器做源码级的拆解。无论你是刚开始接触网关,还是已经在生产环境里用它做鉴权、限流、灰度,这篇文章都能帮你把“配置能跑”提升到“出问题知道去哪查”的层次。
1. 先认清 GatewayFilter 在 Gateway 过滤器体系里的坐标
很多同学混淆概念的根本原因,是把“过滤器”和“过滤器工厂”当成了一回事。我先把这两个东西掰开:GatewayFilter是一个真正作用于请求/响应的组件,它拿到ServerWebExchange和GatewayFilterChain,可以做前置处理、后置处理,然后调用chain.filter(exchange)把请求传递下去。而GatewayFilterFactory是生产过滤器实例的工厂,它接收一组配置参数,返回一个具体的GatewayFilter。
配置文件里的filters段,写的其实是工厂的名字,不是过滤器本身。
spring: cloud: gateway: routes: - id: user_route uri: lb://user-service predicates: - Path=/user/** filters: - AddRequestHeader=X-Request-Source, gw这里AddRequestHeader看起来像过滤器,实际上它对应的是AddRequestHeaderGatewayFilterFactory。Spring Cloud Gateway 启动时,会拿着这个名字去容器里找对应的工厂 Bean,然后调用工厂的apply方法,用X-Request-Source和gw这两个参数生成一个匿名的GatewayFilter实例,最后塞进这条路由的过滤器链里。
这个设计很像现实世界里的“标准零件 + 工艺参数”:工厂是固定的,你给不同的参数,出来的是不同配置的零件。好处是显而易见的——如果每条路由的请求头都不一样,直接配置就能搞定,不需要为每种场景写一个新过滤器类。
再看GlobalFilter,它是另一种维度的过滤器:不需要绑定到某条路由,作用于网关收到的所有请求,比如负载均衡的ReactiveLoadBalancerClientFilter、转发请求的NettyRoutingFilter都属于这一类。你不需要为每条路由单独配置它们。
在FilteringWebHandler的handle方法中,两类过滤器会被合并成一条完整的执行链:
@Override public Mono<Void> handle(ServerWebExchange exchange) { Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); List<GatewayFilter> gatewayFilters = route.getFilters(); List<GatewayFilter> combined = combineGlobalFiltersAndGatewayFilters(gatewayFilters); ... return new DefaultGatewayFilterChain(combined).filter(exchange); }combineGlobalFiltersAndGatewayFilters内部会先把两类过滤器拉平,再根据Ordered接口的getOrder()值排序。注意一个细节:所有GlobalFilter都被适配成了GatewayFilterAdapter,从类型上看它们也是GatewayFilter,所以整条链是统一处理的。
上面这段逻辑是整个过滤器体系的枢纽,多读几遍。它能解释很多线上问题,比如为什么某个过滤器明明写在配置中间,执行顺序却不按配置顺序来——因为最终排序是看getOrder(),不是看 YAML 里的先后顺序。
2. 内置 GatewayFilter 全量盘点与适用场景速查
Spring Cloud Gateway 内置的 GatewayFilter 数量不少,而且版本之间还在持续增加。我不打算把每个都长篇大论,而是给你一张速查表,按功能域分组。这里的名称统一写配置里用的短名,因为这才是你每天写配置时接触的东西。
| 功能域 | 过滤器短名 | 核心作用 | 常用配置示例 |
|---|---|---|---|
| 请求头操作 | AddRequestHeader | 往请求里追加 Header | AddRequestHeader=X-Request-Source, gw |
| 请求头操作 | SetRequestHeader | 覆盖同名 Header 值 | SetRequestHeader=X-Request-Source, gw |
| 请求头操作 | RemoveRequestHeader | 删除指定 Header | RemoveRequestHeader=X-Request-Source |
| 请求头操作 | MapRequestHeader | 从已有 Header 复制值到新 Header | MapRequestHeader=X-From, X-To |
| 请求参数 | AddRequestParameter | 追加 Query 参数 | AddRequestParameter=debug, true |
| 请求参数 | RemoveRequestParameter | 删除 Query 参数 | RemoveRequestParameter=debug |
| 路径操作 | StripPrefix | 转发前去掉前 N 段路径 | StripPrefix=1 |
| 路径操作 | PrefixPath | 为请求路径加前缀 | PrefixPath=/api |
| 路径操作 | RewritePath | 用正则重写路径 | RewritePath=/old/(?<segment>.*), /new/$\{segment} |
| 路径操作 | SetPath | 直接替换为模板路径 | SetPath=/new/{segment} |
| 响应头操作 | AddResponseHeader | 往响应里追加 Header | AddResponseHeader=X-Resp, ok |
| 响应头操作 | SetResponseHeader | 覆盖响应 Header 值 | SetResponseHeader=X-Resp, ok |
| 响应头操作 | RemoveResponseHeader | 删除响应 Header | RemoveResponseHeader=X-Resp |
| 响应头操作 | RewriteResponseHeader | 正则改写响应 Header 值 | RewriteResponseHeader=X-Resp, (.*), prefix-$1 |
| 响应头操作 | DedupeResponseHeader | 去重响应 Header | DedupeResponseHeader=Access-Control-Allow-Credentials, RETAIN_FIRST |
| 响应头操作 | RewriteLocationResponseHeader | 改写响应中的 Location | RewriteLocationResponseHeader=/old, /new, false |
| 状态与跳转 | SetStatus | 设置响应状态码 | SetStatus=201 |
| 状态与跳转 | RedirectTo | 302 跳转 | RedirectTo=302, https://example.com |
| 弹性能力 | Retry | 请求失败自动重试 | Retry=retries:3,statuses:SERVER_ERROR |
| 弹性能力 | CircuitBreaker | 整合熔断器 | CircuitBreaker=name=cbA,fallbackUri=forward:/fallback |
| 弹性能力 | FallbackHeaders | 把熔断异常写入请求头 | FallbackHeaders=executionExceptionTypeHeaderName=X-Ex-Type |
| 请求体 | ModifyRequestBody | 修改请求体 | ModifyRequestBody=application/json, application/json, "#{...}" |
| 响应体 | ModifyResponseBody | 修改响应体 | ModifyResponseBody=application/json, application/json, "#{...}" |
| 限流 | RequestRateLimiter | 基于令牌桶限流 | RequestRateLimiter=redis-rate-limiter.replenishRate:1, redis-rate-limiter.burstCapacity:3 |
| 安全 | RequestSize | 限制请求体大小 | RequestSize=100KB |
| 安全 | SecureHeaders | 追加安全响应头集合 | SecureHeaders |
| 安全 | TokenRelay | 把 OAuth2 Token 传给下游 | TokenRelay |
| 会话 | SaveSession | 转发前保存会话状态 | SaveSession |
| 主机头 | PreserveHostHeader | 保留原始 Host | PreserveHostHeader |
| 主机头 | SetRequestHostHeader | 设置请求 Host | SetRequestHostHeader=example.com |
需要注意几点,这些都是实际项目中容易踩到的地方。
StripPrefix的中文文档描述经常让人误解。StripPrefix=1的意思是去掉原始路径的第一段,比如/order/list变成/list,而不是“去掉前缀字符串 order”。它按/分段计数,不是字符串替换。想按字符串替换请用RewritePath。
RewritePath里的正则和替换模板用的是 Java 的正则语法,$符号在 YAML 里要小心处理,否则会被当成变量插值处理。我一般建议把整段配置用单引号包裹,替换模板里的${segment}写成$\{segment}或者用一个临时变量绕开。
Retry的配置非常容易引发误伤。它默认重试的是 5xx 状态码和 GET 方法,这还好。但如果有人手动配置了statuses: BAD_REQUEST甚至methods: ALL,那在流量高峰期可能把本来能快速失败的错误请求反复打到下游,造成雪上加霜。后面我会单独讲它的源码,这里先记住:重试是有代价的,一定要控制重试次数和条件。
ModifyRequestBody和ModifyResponseBody的配置写起来最绕,因为它们支持 SpEL 表达式。官方示例里那串"#{...}"是在构造一个用于转换的类名,想用好必须自己写一个继承RewriteFunction的类,并在类上标注@Component,然后在表达式里引用它的 bean 名。这个过滤器我建议单独开一篇讲,这里先只给结论:别直接在配置里写复杂逻辑,表达式只是入口,真正的转换逻辑放到自定义组件里。
实际项目里最常见的组合是:StripPrefix+AddRequestHeader+RequestRateLimiter+Retry,分别对应路径改写、链路标识注入、限流兜底和瞬时故障容忍。这四个过滤器做基础骨架,大部分路由都够用。
3. 从 YAML 到过滤器链:GatewayFilter 的装配源码链路
这一节解决一个关键问题:那行 YAML 配置,到底是怎么一步步变成一个可执行的过滤器链的?这条链路涉及三个核心类,我按数据流方向走一遍,你跟着过一遍就能在脑子里建立完整的调用图。
3.1 路由定位器如何读取配置并构造过滤器实例
入口是RouteDefinitionRouteLocator。它负责读取RouteDefinition,也就是 YAML 里每条路由的原始定义,然后把它转换成真正可用的Route对象,Route里就包含了List<GatewayFilter>。
转换的核心方法在getFilters这个私有逻辑中,它会遍历路由定义里的filters列表,对每个短名执行以下关键步骤:
private GatewayFilter getGatewayFilter(RouteDefinition routeDefinition, FilterDefinition filterDefinition) { GatewayFilter gatewayFilter = null; String shortcutConfigurationPropertyName = null; if (filterDefinition.getName().contains(NAME_PREFIX)) { gatewayFilter = factoryMap.get(filterDefinition.getName()).apply(filterDefinition.getArgs()); } else { String factoryName = filterDefinition.getName() + NAME_PREFIX; // NAME_PREFIX = "GatewayFilterFactory" gatewayFilter = factoryMap.get(factoryName).apply(filterDefinition.getArgs()); } ... }NAME_PREFIX就是GatewayFilterFactory这个后缀。如果你在配置里写AddRequestHeader,代码会去找AddRequestHeaderGatewayFilterFactory这个 bean;如果你写完整类名AddRequestHeaderGatewayFilterFactory,它也能直接按名字取。这里有个隐藏逻辑:factoryMap是 Spring 容器中所有GatewayFilterFactory类型的 bean 按名称组成的 Map,RouteDefinitionRouteLocator在初始化时注入,所以上面第二步能否成功,取决于你是否在容器里注册了对应工厂。
3.2 工厂的 apply 方法与快捷配置解析
找到工厂之后,调用apply(filterDefinition.getArgs()),args是过滤器参数的有序 Map。
这里就要聊 Spring Cloud Gateway 一个很巧妙的设计:快捷配置(shortcut configuration)。比如AddRequestHeader=X-Request-Source, gw这种用逗号分隔写法,底层会被解析成name和value两个参数。这是怎么做到的?
GatewayFilterFactory继承了ShortcutConfigurable接口,接口里有一个shortcutFieldOrder()方法。AddRequestHeaderGatewayFilterFactory的实现是:
@Override public List<String> shortcutFieldOrder() { return Arrays.asList(NAME_KEY, VALUE_KEY); // ["name", "value"] }ConfigurationService会拿着这个顺序,把逗号分隔的值按顺序绑定到配置类的对应字段上。换句话说,ShortcutConfigurable定义的是“如何把紧凑的配置语法映射成一个结构化的配置对象”。
如果某天你想自定义一个过滤器工厂,且希望支持MyFilter=aaa, bbb, ccc这种写法,就必须正确实现shortcutFieldOrder,否则配置解析阶段就会报参数绑定错误。
3.3 过滤器链的组装与执行顺序
拿到所有过滤器之后,真正把链组装起来的逻辑在FilteringWebHandler里。前面贴过它的核心处理逻辑,这里我把它拆细一点,用伪代码展示它内部的实际步骤:
- 从 exchange 里取出当前路由对象
Route - 拿到路由自己的
List<GatewayFilter> - 从 Spring 容器中拿到所有
GlobalFilter - 把每个
GlobalFilter包一层GatewayFilterAdapter,让它们的接口统一成GatewayFilter - 把两组过滤器相加,排序(依据
Ordered.getOrder()) - 构建
DefaultGatewayFilterChain并执行
排序这一步容易让人踩坑。GatewayFilter如果没有实现Ordered,默认 order 为 0;而GlobalFilter的 order 通常都很大,比如NettyRoutingFilter的 order 是Integer.MAX_VALUE,ReactiveLoadBalancerClientFilter是LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150。所以正常情况下,先执行的是路由级的GatewayFilter,比如加头、改路径,然后才轮到负载均衡和真正的网络转发。
但如果你在自定义GatewayFilter时不小心给了一个很大的 order,比如Integer.MAX_VALUE - 1,那它可能会被排到NettyRoutingFilter之后才执行——这个时候请求可能已经发出去了,你再修改请求头就来不及了。这是一个非常隐蔽的问题,我在后面自定义部分会专门再提一次。
4. 拆三个有代表性的过滤器:AddRequestHeader、Retry 与 CircuitBreaker
内置过滤器有几十个,逐一细讲不现实。我挑三个刚好覆盖“简单、配置复杂、依赖外部组件”三种典型情况的,走一遍源码,你就能举一反三。
4.1 AddRequestHeaderGatewayFilterFactory:最标准的工厂样板
这个过滤器是所有内置工厂里最简单的,适合当入门教材。它的完整实现大致是:
public class AddRequestHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddRequestHeaderGatewayFilterFactory.Config> { public AddRequestHeaderGatewayFilterFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList(NAME_KEY, VALUE_KEY); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() .header(config.name, config.value) .build(); return chain.filter(exchange.mutate().request(request).build()); }; } public static class Config { private String name; private String value; // getter / setter 省略 } }注意两个细节。第一,AbstractGatewayFilterFactory通过泛型指定了配置类,它在内部会完成“配置参数到配置对象”的绑定,工厂类本身只关心如何消费配置。第二,apply方法返回的是一个 lambda 形式的GatewayFilter,它捕获了config,这样每个路由的过滤器实例都有自己独立的配置副本,互不干扰。
这里有一个很多人没发现的小特性:当同一请求头被多次添加时,header(config.name, config.value)是追加语义,不是覆盖。如果你在两条路由上都配了AddRequestHeader并且下游恰好接的是同一个服务,那么下游收到的可能是两个同名的请求头。这算不算 bug 要看场景,但知道这个是追加语义很重要。
4.2 RetryGatewayFilterFactory:重试不是“再调一次”那么简单
Retry的源码远比表面看起来复杂,因为它涉及请求体是否可重复读取、流量是否已经发出、连接是否已建立等一系列状态判断。
核心配置类RetryConfig包含以下字段:
private int retries; // 重试次数 private List<HttpStatus> statuses; // 按状态码触发重试 private List<HttpMethod> methods; // 按请求方法触发重试 private List<HttpStatus.Series> series; // 按状态分类触发重试 private List<Class<? extends Throwable>> exceptions; // 按异常触发重试 private BackoffConfig backoff; // 退避策略真正执行重试逻辑的过滤器内部,会先判断当前请求是否已经被消费过。我截取核心逻辑的大致结构:
return (exchange, chain) -> { return retry(exchange, chain, config.getRetries(), new ArrayList<>()); }; private Mono<Void> retry(ServerWebExchange exchange, GatewayFilterChain chain, int retries, List<Throwable> exceptions) { return chain.filter(exchange).doOnError(throwable -> { exceptions.add(throwable); if (RETRY_ATTRIBUTE_EXCEPTION_LIST ... ) { // 记录异常 } }).then(); }重试过程中最微妙的问题就是请求体缓冲:如果请求体已经被下游读取过一次,再次重试时流已经关闭,就没法重新发送了。所以官方文档特别说明:带有请求体的请求,默认情况下Retry不会重试,除非你在配置里设置了基于ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR的缓存逻辑。
另一个需要警惕的陷阱是“重试与幂等的关系”。GET 请求重试基本安全,但 POST、PUT 这类可能产生副作用的请求,一旦下游处理成功但响应在传输中丢失,重试就会导致重复下单、重复扣费这类严重后果。这个过滤器适合用在读多写少且下游具备幂等能力的场景,否则一定三思。
4.3 CircuitBreakerGatewayFilterFactory:把熔断器拉进过滤器链
CircuitBreaker过滤器是另一个典型,它不直接做熔断,而是把 Spring Cloud Circuit Breaker 抽象层上的ReactiveCircuitBreaker集成到网关过滤链路里。
它在apply方法中做了这样几件事:
- 根据配置里的
name拿到对应的ReactiveCircuitBreaker实例。 - 调用
circuitBreaker.run(...)包裹下游的chain.filter(exchange),在回调里处理熔断打开时的 fallback 逻辑。 - 如果配置了
fallbackUri,则通过exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR, true)标记当前请求已路由,然后发送 forward 请求到 fallback 地址,避免陷入无限转发。
我见过很多人在用这个过滤器时犯同一个错误:只配置了name和fallbackUri,但没有专门为 fallbackUri 再定义一条路由。结果熔断触发后,网关要把请求 forward 到/fallback,却发现根本没有匹配的路由,直接报 404。正确的做法是额外加一条路由专门处理 fallback:
spring: cloud: gateway: routes: - id: user_route uri: lb://user-service predicates: - Path=/user/** filters: - name: CircuitBreaker args: name: userCb fallbackUri: forward:/fallback - id: fallback_route uri: http://fallback-service predicates: - Path=/fallback另外要记住,CircuitBreaker 并不改变下游请求本身,它只是给下游调用加了一层“保险丝”。熔断状态、半开状态下的探测请求,这些逻辑都在底层各品牌的熔断器实现中,配置时注意选择与实现类匹配的参数,例如基于 Redis 计数还是基于本地滑动窗口。
5. 自定义 GatewayFilter 的两种方式与执行顺序控制
内置过滤器终究覆盖不了所有业务场景。我实际做过一个灰度发布的过滤器,需要根据用户请求头里的某个身份标识动态改写下游请求路径,这种逻辑没有现成的过滤器可用,只能自己写。自定义有两种常见形态,我分别说清楚,你根据自己的需求选。
5.1 直接实现 GatewayFilter 接口
最简单的形态是写一个类实现GatewayFilter和Ordered:
@Component public class CustomHeaderFilter implements GatewayFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest().mutate() .header("X-Custom-Filter", "active") .build(); return chain.filter(exchange.mutate().request(request).build()); } @Override public int getOrder() { return -100; } }这种形态的过滤器不能直接在 YAML 的filters里用短名配置,因为它没有对应的工厂。使用方式是写进一个RouteLocator的配置类里,手动装配:
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder, CustomHeaderFilter customHeaderFilter) { return builder.routes() .route("custom_route", r -> r.path("/custom/**") .filters(f -> f.filter(customHeaderFilter)) .uri("lb://custom-service")) .build(); }我一直觉得这种方式适合“固定逻辑”的过滤器,因为它的行为完全是写死的。
5.2 继承 AbstractGatewayFilterFactory:让过滤器也支持配置
如果你的过滤器需要像内置过滤器一样,在不同路由上使用不同参数,那就用第二种方式,继承AbstractGatewayFilterFactory<Config>。下面是一个给请求追加固定 Header 的可配置版本:
@Component public class CustomAddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomAddHeaderGatewayFilterFactory.Config> { public CustomAddHeaderGatewayFilterFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("headerName", "headerValue"); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() .header(config.headerName, config.headerValue) .build(); return chain.filter(exchange.mutate().request(request).build()); }; } public static class Config { private String headerName; private String headerValue; // getter / setter 省略 } }这样在 YAML 里就可以写了:
filters: - CustomAddHeader=X-Trace-Id, abc123注意:配置里的短名是去掉GatewayFilterFactory后缀的部分。你的类名叫CustomAddHeaderGatewayFilterFactory,配置里就写CustomAddHeader。如果你配置里写全名,也能生效,因为前文说过工厂查找逻辑两种写法都支持。
这两种方式的对比我放在一起说:
| 对比维度 | 直接实现 GatewayFilter | 继承 AbstractGatewayFilterFactory |
|---|---|---|
| 配置驱动 | 不支持,逻辑写死 | 支持,可灵活配置参数 |
| 装配方式 | 手动加到 RouteLocator | 扫描 Bean 后按短名装配 |
| 适合场景 | 全网关统一的固定逻辑 | 不同路由不同参数的灵活需求 |
| 实现门槛 | 低 | 中,需理解配置绑定 |
5.3 执行顺序控制:这决定了你改的是请求还是响应
前面提到过Ordered接口,这里展开讲。对于自定义的GatewayFilter,如果不实现Ordered,默认 order 是 0。那么问题来了:如果一条路由上既配置了AddRequestHeader(order 为 0),又配置了你的自定义过滤器(order 也是 0),执行顺序是啥?
排序算法在AnnotationAwareOrderComparator中,当 order 相同时,它会比较对象在容器中的注册顺序,这在本质上是不稳定的。如果你依赖这种不确定性来保证业务逻辑顺序,大概率会在某次应用重启后得到不同的行为。
所以我的建议是:所有自定义GatewayFilter一律显式实现Ordered,并且根据你要做的事选择合适的 order 区间。
- 需要修改请求:order 设为负数,比如
-100,确保在转发之前生效。 - 需要修改响应:order 设为正数,但要小于
NettyRoutingFilter的Integer.MAX_VALUE,否则转发已经完成,你再改响应头就晚了。 - 需要做日志、监控这类旁路逻辑:order 可以设为 0 附近,不影响主流程,但也别指望它能在所有过滤器的最前面或最后面。
有一种更稳妥的写法:如果你重复设置了相同的 order 且确实需要严格顺序,直接在自定义过滤器内部维护一个“子链”,也就是一个List<GatewayFilter>列表,按明确的次序逐个执行,避免把顺序暴露给全局排序器。这种方式适合业务上强依赖执行次序的场景。
还有一点容易被忽略:Route上的过滤器列表顺序在你配置时是固定的,但经过combineGlobalFiltersAndGatewayFilters合并排序后会重排。如果你在排查问题时看到执行顺序和 YAML 里写的顺序不一致,别惊讶,先去看所有相关过滤器的getOrder()值。
6. 调试过滤器链的实用技巧
最后聊几个我在生产环境排障时会用到的调试方法,这些经验写在博客里就当是给自己留的备忘录。
先在配置里打开网关的 debug 日志:
logging: level: org.springframework.cloud.gateway: debug org.springframework.web.reactive: debug开启后,请求经过过滤器链时,日志里会输出Route matched: xxx、FilteringWebHandler handled request这类关键信息,可以看到当前请求匹配到了哪个路由、走到了哪一步。
如果你想精确看到过滤器的执行顺序,可以在自定义过滤器里临时加日志,输出当前 exchange 里的路由 ID 和过滤器链的剩余数量,这样能直观看到自己写的过滤器在整条链的前后位置。
还有一种常见排查需求是:请求头到底有没有被正确加上?这个不必看日志,直接在过滤器里把当前exchange.getRequest().getHeaders()打印出来即可。我之前排查过一个问题,两个过滤器的 order 冲突导致请求头被后执行的过滤器覆盖,打一次 header 日志就立刻真相大白。
另一个很实用的场景是模拟线上问题:生产环境把某个下游服务的实例全部摘掉,观察网关的Retry过滤器是否如预期重试、CircuitBreaker是否按配置打开。这种演练建议在压测环境先做,别直接拿生产流量的真实路径试。
想深入看某一个过滤器到底做了什么,最直接的办法还是本地拉一份源码,找到对应的GatewayFilterFactory类,打上断点,然后发起一个触发该过滤器的请求,用 IDE 的调试模式单步走一遍。Spring Cloud Gateway 是响应式框架,断点调用栈比普通 Spring MVC 深很多,但多跟几次就会习惯。这里给个小提示:断点首选打在apply(Config config)方法上,因为所有工厂的入口都在这里,而且能看到配置对象完整绑定之后的值。
扫一眼内置过滤器的源码,你会发现大部分工厂类都遵循同一个套路:定义 Config、声明 shortcutFieldOrder、在 apply 里返回匿名 GatewayFilter。看懂一个就相当于看懂了一整片。这也是我为什么强烈建议你把AddRequestHeader的实现读透,因为它本身就是这个框架里最简单也最典型的工厂范式。后面你再去读Retry、CircuitBreaker那些复杂的工厂,思路会顺畅很多。