资讯动态

Spring Cloud Gateway过滤器体系详解:GatewayFilter核心机制与源码拆解

发布时间:2026/10/16 4:06:50 来源:尧图企业网站定制
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: - AddRequestHeaderX-Request-Source, gw这里AddRequestHeader看起来像过滤器实际上它对应的是AddRequestHeaderGatewayFilterFactory。Spring Cloud Gateway 启动时会拿着这个名字去容器里找对应的工厂 Bean然后调用工厂的apply方法用X-Request-Source和gw这两个参数生成一个匿名的GatewayFilter实例最后塞进这条路由的过滤器链里。这个设计很像现实世界里的“标准零件 工艺参数”工厂是固定的你给不同的参数出来的是不同配置的零件。好处是显而易见的——如果每条路由的请求头都不一样直接配置就能搞定不需要为每种场景写一个新过滤器类。再看GlobalFilter它是另一种维度的过滤器不需要绑定到某条路由作用于网关收到的所有请求比如负载均衡的ReactiveLoadBalancerClientFilter、转发请求的NettyRoutingFilter都属于这一类。你不需要为每条路由单独配置它们。在FilteringWebHandler的handle方法中两类过滤器会被合并成一条完整的执行链Override public MonoVoid handle(ServerWebExchange exchange) { Route route exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); ListGatewayFilter gatewayFilters route.getFilters(); ListGatewayFilter combined combineGlobalFiltersAndGatewayFilters(gatewayFilters); ... return new DefaultGatewayFilterChain(combined).filter(exchange); }combineGlobalFiltersAndGatewayFilters内部会先把两类过滤器拉平再根据Ordered接口的getOrder()值排序。注意一个细节所有GlobalFilter都被适配成了GatewayFilterAdapter从类型上看它们也是GatewayFilter所以整条链是统一处理的。上面这段逻辑是整个过滤器体系的枢纽多读几遍。它能解释很多线上问题比如为什么某个过滤器明明写在配置中间执行顺序却不按配置顺序来——因为最终排序是看getOrder()不是看 YAML 里的先后顺序。2. 内置 GatewayFilter 全量盘点与适用场景速查Spring Cloud Gateway 内置的 GatewayFilter 数量不少而且版本之间还在持续增加。我不打算把每个都长篇大论而是给你一张速查表按功能域分组。这里的名称统一写配置里用的短名因为这才是你每天写配置时接触的东西。功能域过滤器短名核心作用常用配置示例请求头操作AddRequestHeader往请求里追加 HeaderAddRequestHeaderX-Request-Source, gw请求头操作SetRequestHeader覆盖同名 Header 值SetRequestHeaderX-Request-Source, gw请求头操作RemoveRequestHeader删除指定 HeaderRemoveRequestHeaderX-Request-Source请求头操作MapRequestHeader从已有 Header 复制值到新 HeaderMapRequestHeaderX-From, X-To请求参数AddRequestParameter追加 Query 参数AddRequestParameterdebug, true请求参数RemoveRequestParameter删除 Query 参数RemoveRequestParameterdebug路径操作StripPrefix转发前去掉前 N 段路径StripPrefix1路径操作PrefixPath为请求路径加前缀PrefixPath/api路径操作RewritePath用正则重写路径RewritePath/old/(?segment.*), /new/$\{segment}路径操作SetPath直接替换为模板路径SetPath/new/{segment}响应头操作AddResponseHeader往响应里追加 HeaderAddResponseHeaderX-Resp, ok响应头操作SetResponseHeader覆盖响应 Header 值SetResponseHeaderX-Resp, ok响应头操作RemoveResponseHeader删除响应 HeaderRemoveResponseHeaderX-Resp响应头操作RewriteResponseHeader正则改写响应 Header 值RewriteResponseHeaderX-Resp, (.*), prefix-$1响应头操作DedupeResponseHeader去重响应 HeaderDedupeResponseHeaderAccess-Control-Allow-Credentials, RETAIN_FIRST响应头操作RewriteLocationResponseHeader改写响应中的 LocationRewriteLocationResponseHeader/old, /new, false状态与跳转SetStatus设置响应状态码SetStatus201状态与跳转RedirectTo302 跳转RedirectTo302, https://example.com弹性能力Retry请求失败自动重试Retryretries:3,statuses:SERVER_ERROR弹性能力CircuitBreaker整合熔断器CircuitBreakernamecbA,fallbackUriforward:/fallback弹性能力FallbackHeaders把熔断异常写入请求头FallbackHeadersexecutionExceptionTypeHeaderNameX-Ex-Type请求体ModifyRequestBody修改请求体ModifyRequestBodyapplication/json, application/json, #{...}响应体ModifyResponseBody修改响应体ModifyResponseBodyapplication/json, application/json, #{...}限流RequestRateLimiter基于令牌桶限流RequestRateLimiterredis-rate-limiter.replenishRate:1, redis-rate-limiter.burstCapacity:3安全RequestSize限制请求体大小RequestSize100KB安全SecureHeaders追加安全响应头集合SecureHeaders安全TokenRelay把 OAuth2 Token 传给下游TokenRelay会话SaveSession转发前保存会话状态SaveSession主机头PreserveHostHeader保留原始 HostPreserveHostHeader主机头SetRequestHostHeader设置请求 HostSetRequestHostHeaderexample.com需要注意几点这些都是实际项目中容易踩到的地方。StripPrefix的中文文档描述经常让人误解。StripPrefix1的意思是去掉原始路径的第一段比如/order/list变成/list而不是“去掉前缀字符串 order”。它按/分段计数不是字符串替换。想按字符串替换请用RewritePath。RewritePath里的正则和替换模板用的是 Java 的正则语法$符号在 YAML 里要小心处理否则会被当成变量插值处理。我一般建议把整段配置用单引号包裹替换模板里的${segment}写成$\{segment}或者用一个临时变量绕开。Retry的配置非常容易引发误伤。它默认重试的是 5xx 状态码和 GET 方法这还好。但如果有人手动配置了statuses: BAD_REQUEST甚至methods: ALL那在流量高峰期可能把本来能快速失败的错误请求反复打到下游造成雪上加霜。后面我会单独讲它的源码这里先记住重试是有代价的一定要控制重试次数和条件。ModifyRequestBody和ModifyResponseBody的配置写起来最绕因为它们支持 SpEL 表达式。官方示例里那串#{...}是在构造一个用于转换的类名想用好必须自己写一个继承RewriteFunction的类并在类上标注Component然后在表达式里引用它的 bean 名。这个过滤器我建议单独开一篇讲这里先只给结论别直接在配置里写复杂逻辑表达式只是入口真正的转换逻辑放到自定义组件里。实际项目里最常见的组合是StripPrefixAddRequestHeaderRequestRateLimiterRetry分别对应路径改写、链路标识注入、限流兜底和瞬时故障容忍。这四个过滤器做基础骨架大部分路由都够用。3. 从 YAML 到过滤器链GatewayFilter 的装配源码链路这一节解决一个关键问题那行 YAML 配置到底是怎么一步步变成一个可执行的过滤器链的这条链路涉及三个核心类我按数据流方向走一遍你跟着过一遍就能在脑子里建立完整的调用图。3.1 路由定位器如何读取配置并构造过滤器实例入口是RouteDefinitionRouteLocator。它负责读取RouteDefinition也就是 YAML 里每条路由的原始定义然后把它转换成真正可用的Route对象Route里就包含了ListGatewayFilter。转换的核心方法在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 按名称组成的 MapRouteDefinitionRouteLocator在初始化时注入所以上面第二步能否成功取决于你是否在容器里注册了对应工厂。3.2 工厂的 apply 方法与快捷配置解析找到工厂之后调用apply(filterDefinition.getArgs())args是过滤器参数的有序 Map。这里就要聊 Spring Cloud Gateway 一个很巧妙的设计快捷配置shortcut configuration。比如AddRequestHeaderX-Request-Source, gw这种用逗号分隔写法底层会被解析成name和value两个参数。这是怎么做到的GatewayFilterFactory继承了ShortcutConfigurable接口接口里有一个shortcutFieldOrder()方法。AddRequestHeaderGatewayFilterFactory的实现是Override public ListString shortcutFieldOrder() { return Arrays.asList(NAME_KEY, VALUE_KEY); // [name, value] }ConfigurationService会拿着这个顺序把逗号分隔的值按顺序绑定到配置类的对应字段上。换句话说ShortcutConfigurable定义的是“如何把紧凑的配置语法映射成一个结构化的配置对象”。如果某天你想自定义一个过滤器工厂且希望支持MyFilteraaa, bbb, ccc这种写法就必须正确实现shortcutFieldOrder否则配置解析阶段就会报参数绑定错误。3.3 过滤器链的组装与执行顺序拿到所有过滤器之后真正把链组装起来的逻辑在FilteringWebHandler里。前面贴过它的核心处理逻辑这里我把它拆细一点用伪代码展示它内部的实际步骤从 exchange 里取出当前路由对象Route拿到路由自己的ListGatewayFilter从 Spring 容器中拿到所有GlobalFilter把每个GlobalFilter包一层GatewayFilterAdapter让它们的接口统一成GatewayFilter把两组过滤器相加排序依据Ordered.getOrder()构建DefaultGatewayFilterChain并执行排序这一步容易让人踩坑。GatewayFilter如果没有实现Ordered默认 order 为 0而GlobalFilter的 order 通常都很大比如NettyRoutingFilter的 order 是Integer.MAX_VALUEReactiveLoadBalancerClientFilter是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 AbstractGatewayFilterFactoryAddRequestHeaderGatewayFilterFactory.Config { public AddRequestHeaderGatewayFilterFactory() { super(Config.class); } Override public ListString 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 ListHttpStatus statuses; // 按状态码触发重试 private ListHttpMethod methods; // 按请求方法触发重试 private ListHttpStatus.Series series; // 按状态分类触发重试 private ListClass? extends Throwable exceptions; // 按异常触发重试 private BackoffConfig backoff; // 退避策略真正执行重试逻辑的过滤器内部会先判断当前请求是否已经被消费过。我截取核心逻辑的大致结构return (exchange, chain) - { return retry(exchange, chain, config.getRetries(), new ArrayList()); }; private MonoVoid retry(ServerWebExchange exchange, GatewayFilterChain chain, int retries, ListThrowable 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。正确的做法是额外加一条路由专门处理 fallbackspring: 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和OrderedComponent public class CustomHeaderFilter implements GatewayFilter, Ordered { Override public MonoVoid 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让过滤器也支持配置如果你的过滤器需要像内置过滤器一样在不同路由上使用不同参数那就用第二种方式继承AbstractGatewayFilterFactoryConfig。下面是一个给请求追加固定 Header 的可配置版本Component public class CustomAddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactoryCustomAddHeaderGatewayFilterFactory.Config { public CustomAddHeaderGatewayFilterFactory() { super(Config.class); } Override public ListString 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: - CustomAddHeaderX-Trace-Id, abc123注意配置里的短名是去掉GatewayFilterFactory后缀的部分。你的类名叫CustomAddHeaderGatewayFilterFactory配置里就写CustomAddHeader。如果你配置里写全名也能生效因为前文说过工厂查找逻辑两种写法都支持。这两种方式的对比我放在一起说对比维度直接实现 GatewayFilter继承 AbstractGatewayFilterFactory配置驱动不支持逻辑写死支持可灵活配置参数装配方式手动加到 RouteLocator扫描 Bean 后按短名装配适合场景全网关统一的固定逻辑不同路由不同参数的灵活需求实现门槛低中需理解配置绑定5.3 执行顺序控制这决定了你改的是请求还是响应前面提到过Ordered接口这里展开讲。对于自定义的GatewayFilter如果不实现Ordered默认 order 是 0。那么问题来了如果一条路由上既配置了AddRequestHeaderorder 为 0又配置了你的自定义过滤器order 也是 0执行顺序是啥排序算法在AnnotationAwareOrderComparator中当 order 相同时它会比较对象在容器中的注册顺序这在本质上是不稳定的。如果你依赖这种不确定性来保证业务逻辑顺序大概率会在某次应用重启后得到不同的行为。所以我的建议是所有自定义GatewayFilter一律显式实现Ordered并且根据你要做的事选择合适的 order 区间。需要修改请求order 设为负数比如-100确保在转发之前生效。需要修改响应order 设为正数但要小于NettyRoutingFilter的Integer.MAX_VALUE否则转发已经完成你再改响应头就晚了。需要做日志、监控这类旁路逻辑order 可以设为 0 附近不影响主流程但也别指望它能在所有过滤器的最前面或最后面。有一种更稳妥的写法如果你重复设置了相同的 order 且确实需要严格顺序直接在自定义过滤器内部维护一个“子链”也就是一个ListGatewayFilter列表按明确的次序逐个执行避免把顺序暴露给全局排序器。这种方式适合业务上强依赖执行次序的场景。还有一点容易被忽略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那些复杂的工厂思路会顺畅很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑