Spring Cloud Gateway过滤器工厂源码解析与自定义实践

发布时间:2026/10/16 1:11:43
Spring Cloud Gateway过滤器工厂源码解析与自定义实践
如果你跟我一样在 Spring Cloud Gateway 的过滤器部分停留过很长时间一定会碰到 GatewayFilterFactory 这个名字。它是过滤器系列里绕不开的一环路由配置文件里每一条- XXXxxx过滤项最终都是由对应的工厂创建出真正执行的过滤器实例。这篇是过滤器源码解析的第 4.2 节专门把 GatewayFilterFactory 过滤器工厂这条线讲透。我会以 3.1.x 版本源码为主线讲清楚工厂在路由装配链路里的位置、三个核心抽象之间的关系、内置工厂的实现套路以及如何照同样的思路写一个自己的过滤器工厂。适合正在读 Gateway 源码、或者被自定义过滤器折腾过的同学直接参考。1. 过滤器工厂做了什么从路由配置到过滤器实例的装配链路1.1 装配入口RouteDefinitionRouteLocator 的 getFilters 调用链想要理解 GatewayFilterFactory不能只看这个接口本身得先从它被谁调用说起。Gateway 的整个路由装配核心在RouteDefinitionRouteLocator它负责把RouteDefinition路由配置的抽象描述转换成真正的Route而Route里面挂着两个关键东西一组Predicate和一组GatewayFilter。过滤器这一环就是通过getFilters方法完成的。我摘录一下 3.1.x 源码里核心逻辑简化后的版本方便对照思路private ListGatewayFilter getFilters(GatewayFilterDefinition filterDefinition) { ListGatewayFilter filters new ArrayList(); MapString, String args filterDefinition.getArgs(); if (args ! null) { for (Map.EntryString, String entry : args.entrySet()) { String value entry.getValue(); GatewayFilterFactory gatewayFilterFactory this.gatewayFilters.get(entry.getKey()); if (gatewayFilterFactory null) { throw new IllegalArgumentException( Unable to find gateway filter factory with name entry.getKey()); } if (value ! null) { String[] argsArray StringUtils.tokenizeToStringArray(value, ,); filters.add(gatewayFilterFactory.apply(argsArray)); } } } return filters; }这里面this.gatewayFilters是一个MapString, GatewayFilterFactorykey 是过滤器工厂的名字value 是工厂实例。这个 map 在 Gateway 自动装配阶段构建所有类型为GatewayFilterFactory的 Bean 都会被收进去名字默认从类名推导比如AddRequestHeaderGatewayFilterFactory对应的 key 就是AddRequestHeader。换句话说你在 yml 里写的过滤器名字其实就是在跟这个 map 的 key 打交道。短路配置时- AddRequestHeaderX-Request-Id, 123会被解析成GatewayFilterDefinition它的 args 里保存的是整段字符串。getFilters拿到工厂后把逗号拆开变成String[]再调用工厂的apply(String... args)得到真正的GatewayFilter。1.2 FilterDefinition、工厂与过滤器实例三者的分工这里有一个很容易绕晕的点GatewayFilterDefinition是“配置描述”GatewayFilter是“执行单元”工厂是两者之间的“翻译器”。GatewayFilterDefinition只负责描述里面有name和args本质上是路由配置里那段文本的结构化存储。GatewayFilterFactory接收配置描述校验参数、创建配置对象、返回过滤器实例。GatewayFilter真正在请求链路里执行的过滤器它拿到的已经是解析好的配置对象。为什么要分成三层而不是直接让过滤器读配置原因很明显配置来源是 yml而过滤器执行时希望直接访问类型安全的配置对象而不是自己解析字符串。工厂把“配置解析”和“请求处理”隔离了同一个工厂可以基于不同参数反复创建多个过滤器实例每个实例只负责一份参数对应的逻辑。这也解释了为什么GatewayFilterFactory的泛型是CC 就是配置类型每个工厂都有一份属于自己的配置模型。把这条链路记在心里后面读任何内置工厂都会很顺装配阶段在 YML 配置文件与过滤器实例之间做转换运行阶段过滤器实例拿到配置对象直接作用于ServerWebExchange。2. 三个核心抽象接口、模板类与短路配置绑定机制2.1 GatewayFilterFactory工厂契约的两个 apply 入口先看接口本身3.1.x 版本大致是这样public interface GatewayFilterFactoryC extends ShortcutConfigurable, ConfigurableC { GatewayFilter apply(C config); default GatewayFilter apply(String... args) { return apply(shortcutConfig().shortcut(this::newConfig, args)); } default ClassC getConfigClass() { throw new UnsupportedOperationException(...); } }注意这里有两个apply重载职责完全不同apply(C config)核心方法直接接收一个类型安全的配置对象返回过滤器。自定义工厂基本只覆盖这个方法。apply(String... args)默认方法是短路配置的入口。它把 yml 里逗号分隔的字符串参数通过ShortcutConfigurable的绑定机制转换成配置对象 C再调用apply(C config)。接口还继承了ConfigurableC这就带出了两个约定newConfig()负责创建配置对象validate()负责校验配置是否合法。继承ShortcutConfigurable则让工厂具备处理“短格式参数”的能力。两个继承合在一起构成了工厂的本质既能处理声明式配置又能创建类型安全的配置模型。2.2 AbstractGatewayFilterFactory配置类与反射创建绝大多数内置工厂不会直接实现接口而是继承AbstractGatewayFilterFactory。这个抽象类的核心职责是保存配置类类型并提供默认的配置对象创建逻辑。关键点在于构造器protected AbstractGatewayFilterFactory(ClassC configClass) { this.configClass configClass; }子类必须把配置类传进来比如开头提到的AddRequestHeaderGatewayFilterFactory的构造函数就是super(Config.class)。这里传的配置类有几个隐藏要求必须是static内部类否则反射实例化时会绑定外部类实例容易出问题。必须有可访问的无参构造方法。属性要有 getter/setter因为短路绑定机制依赖 JavaBean 的属性访问规则。newConfig()默认实现就是通过反射实例化配置类Override public C newConfig() { return BeanUtils.instantiateClass(getConfigClass()); }你不需要自己覆写newConfig除非配置类有特殊的构建逻辑。源码里大多数内置工厂都是直接继承AbstractGatewayFilterFactory只写配置类和apply方法其他全部交给父类。2.3 AbstractConfigurable 与短路配置绑定3.1 版本把短路绑定逻辑独立成了AbstractConfigurable再让AbstractGatewayFilterFactory继承它。这样做的目的是把“配置绑定”和“工厂抽象”解耦但对外看起来差别不大网上老版本源码分析里提到的ShortcutConfigurable这块逻辑依然成立。短路绑定的核心问题就是String[] args {X-Request-Id, 123}怎么变成Config { name X-Request-Id, value 123 }答案是ShortcutConfigurable的shortcutType()决定的映射策略。3.1.x 中有三种策略行为说明典型使用场景DEFAULT参数按配置类属性声明顺序逐一映射绝大多数固定数量参数的过滤器GATHER_LIST某个指定字段聚合后续所有参数值需要收集多个名称或值时GATHER_LIST_TAIL最后一个字段收集剩余全部参数参数数量不定但首部参数固定时路由配置里StringUtils.tokenizeToStringArray(value, ,)把逗号分隔的字符串拆开然后由AbstractConfigurable根据属性列表逐个赋值。这里的属性顺序不是字母序而是配置类里字段的声明顺序。写自定义工厂时Config 里的字段顺序就决定了 yml 短格式参数的顺序这一步很容易埋坑后面会专门说到。3. 拆一个内置工厂AddRequestHeader 的配置绑定与执行时机3.1 Config 的设计字段就是参数getter/setter 就是绑定入口AddRequestHeaderGatewayFilterFactory的核心代码非常短但信息量很大。它的配置类大概长这样public static class Config { private String name; private String value; public String getName() { return name; } public void setName(String name) { this.name name; } public String getValue() { return value; } public void setValue(String value) { this.value value; } }整个配置类没有业务逻辑纯粹承载参数。name是请求头名称value是要添加的头值。两个字段的声明顺序直接决定了 yml 里短格式的参数顺序- AddRequestHeaderX-Request-Id, 123就是 name 在前、value 在后如果字段声明顺序反过来绑定结果就会错位。对象格式则优雅许多不存在顺序问题filters: - name: AddRequestHeader args: name: X-Request-Id value: 123我建议团队规范里优先用对象格式尤其是参数多于两个、含义不直观的时候。短格式写起来简单但字段一多就非常考验声明顺序维护时容易踩坑。3.2 apply 中过滤器的执行时机与 mutate 用法AddRequestHeader的apply方法返回的是一个 Lambda 表达式Override public GatewayFilter apply(Config config) { return (exchange, chain) - { ServerHttpRequest request exchange.getRequest().mutate() .header(config.getName(), config.getValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); }; }这里有两个容易误解的点。第一apply方法本身不做任何请求处理它只是返回一个“待执行的过滤器”。真正的逻辑要等到请求进入过滤器链、这个过滤器被调用时才执行。所以apply的命名容易让人误以为这里执行了过滤逻辑实际上它做的是“装配过滤行为”。第二exchange.mutate()创建的是一个不可变的 Web 请求的修改副本而不是直接改原请求。WebFlux 的设计强调不可变所以你要修改请求头必须走mutate()—— 得到一个新请求再调用exchange.mutate().request(newRequest).build()得到新的ServerWebExchange最后把新 exchange 传到chain.filter()。这也是 Gateway 过滤器开发的基本功几乎所有改请求、改响应的过滤器都是这个套路。3.3 其他内置工厂的变体与套路看完AddRequestHeader再扫一眼其他内置工厂会发现结构大同小异区别主要在 mutate 的目标和返回值上。AddResponseHeaderGatewayFilterFactory改的是响应所以在过滤器里exchange.getResponse().getHeaders().add(...)不用 mutate因为响应对象可以直接操作。SetStatusGatewayFilterFactory把exchange.getResponse().setStatusCode(config.getStatus())拿出来执行没返回值也能正常工作因为状态码是响应对象上的直接变更。RewritePathGatewayFilterFactory读取exchange里的原始路径属性存于ServerWebExchangeUtils的常量键里用正则匹配替换然后 mutate 请求为一个新 URI。SetRequestHeaderGatewayFilterFactory和 Add 版本唯一的区别是调用了set而不是add语义上一个是覆盖、一个是追加。读源码时如果连续看五六个内置工厂你会发现模式高度统一配置类 apply中返回过滤器 在过滤器里做 mutate 操作。掌握了这个套路之后自定义工厂的绝大部分不确定性都会消失。4. 按同样套路自定义过滤器工厂灰度路由完整过程与踩坑4.1 场景设定让灰度请求走特定下游我拿一个实际用过的场景举例灰度发布。路由层面想做到一件事——当请求头携带版本号时把请求打上灰度标记并继续转发没有标记的请求走原逻辑保持原有路由不变。这种场景完全不需要改网关核心代码只写一个GatewayFilterFactory就够。这也是过滤器工厂设计的好处把可变的业务策略收敛成一个个配置驱动的小插件。4.2 完整代码与注册方式直接看完整实现Component public class GrayReleaseGatewayFilterFactory extends AbstractGatewayFilterFactoryGrayReleaseGatewayFilterFactory.Config { public GrayReleaseGatewayFilterFactory() { super(Config.class); } Override public GatewayFilter apply(Config config) { return (exchange, chain) - { String version exchange.getRequest().getHeaders() .getFirst(config.getHeaderName()); if (config.getVersionValue().equals(version)) { ServerWebExchange mutated exchange.mutate() .request(builder - builder.headers( headers - headers.add(X-Gray-Tag, true))) .build(); return chain.filter(mutated); } return chain.filter(exchange); }; } public static class Config { private String headerName X-Version; private String versionValue gray; public String getHeaderName() { return headerName; } public void setHeaderName(String headerName) { this.headerName headerName; } public String getVersionValue() { return versionValue; } public void setVersionValue(String versionValue) { this.versionValue versionValue; } } }这里特别注意三个细节类名去掉GatewayFilterFactory后缀后是GrayRelease路由配置里就写- GrayRelease...或- name: GrayRelease。名字对不上启动时不会报错但路由匹配时会提示找不到工厂。构造函数必须调用super(Config.class)把配置类告知父类。父类拿到之后才知道newConfig()该反射创建哪个类短路绑定才知道该读哪些属性。Config必须是public static。我见过有人写成非静态内部类反射实例化时直接抛异常原因就是内部类隐式持有外部类引用无法用无参构造创建。注册方式很简单加Component交给 Spring 容器Gateway 的自动装配会自动收集GatewayFilterFactory类型的 Bean。如果想把装配逻辑集中管理也可以写一个Configuration类用Bean方法创建并返回工厂实例两种方式最终都会被收进gatewayFiltersmap。4.3 路由配置中短路与对象两种写法配置我的这个过滤器有两种方式效果等价# 短路形式依赖字段声明顺序 filters: - GrayReleaseX-Version, gray # 对象形式按属性名绑定 filters: - name: GrayRelease args: headerName: X-Version versionValue: gray因为Config里有默认值所以即使不传headerName它也会默认读取X-Version请求头。这种默认值设计对扩展很友好老配置不用改新场景只要传一个参数就能覆盖。启动网关后用带X-Version: gray头访问下游服务会看到多出来的X-Gray-Tag: true不带这个头请求原样放行什么额外标记都没有。验证到这一步自定义工厂就跑通了。4.4 三个容易踩的坑第一个坑是泛型擦除。继承AbstractGatewayFilterFactoryConfig时父类无法在运行时拿到泛型实参所以必须在构造函数里显式传Config.class。如果漏掉super(Config.class)newConfig()一旦被调用getConfigClass()就会抛UnsupportedOperationException。而且这个报错时机很阴险通常在启动阶段不会触发要等到某个路由第一次匹配、需要创建过滤器时才炸排错成本一下子被拉高。第二个坑是字段顺序。短格式配置下headerName和versionValue哪个在前取决于 Config 里字段声明的先后而不是字母序。我一开始把versionValue写在headerName前面结果 yml 里写- GrayReleasegray, X-Version灰度请求一直不生效排查半天才意识到是顺序错位。参数多的时候务必检查顺序或者干脆换对象格式。第三个坑是validate()的隐性影响。ConfigurableC接口提供了validate方法默认空实现。一旦你覆写了它就要注意短路绑定解析出来的一些值可能不是预期格式。比如从 yml 读到的数字会被转成字符串等你自己类型转换后再进 validate顺序不对就会拿到 null 或空字符串。凡是配置校验逻辑尽量写在所有参数绑定完成之后不要在 apply 入口里做二次解析。5. 源码阅读中容易忽视的三个细节Order、配置提示与断点位置5.1 过滤器的 Order 由谁决定路由里的过滤器不像全局过滤器那样直接靠Ordered接口排序。RouteDefinitionRouteLocator拿到一组GatewayFilter之后确实会做一次 order 排序但如果没有显式实现Ordered默认 order 都是 0排序结果就是保持工厂被创建时的顺序。换句话说你在一个路由里写了三个过滤器顺序就是 yml 里的书写顺序。可一旦你希望某个自定义过滤器强制插到最前面比如统一加 traceId必须优先执行就要让它返回的过滤器实现Orderedreturn new OrderedGatewayFilter(delegate, -100);OrderedGatewayFilter是 Gateway 提供的包装器把一个普通过滤器包装成带优先级的过滤器。负数 order 比默认的 0 更靠前所以能稳定插队。这个技巧在自定义过滤器工厂里非常实用内置工厂里也有不少用到了它。不要试图通过修改 yml 顺序去覆盖代码层面的执行优先级配置只能控制同一路由的过滤器相对位置全局过滤器和路由过滤器的混杂排序逻辑另有一套规则越早意识到这一点调试时越少走弯路。5.2 配置提示元数据对自定义工厂的价值内置工厂在 IDE 里写 yml 配置时有属性补全和说明靠的是META-INF/spring-configuration-metadata.json元数据。自定义工厂如果有团队其他人使用同样值得补一份。最简单的方式是给自己的工厂类加上ConfigurationProperties风格的元数据或者在spring-configuration-metadata.json里指名GrayReleaseGatewayFilterFactory的配置属性和默认值。做这件事的收益不只在 IDE 提示属性名拼写错误能在写配置时就被发现后续维护者能快速看到每个字段的含义不用翻代码和配置中心对接时元数据能辅助自动校验。这个文件格式本身不复杂核心就是属性名、类型、默认值和描述{ properties: [ { name: spring.cloud.gateway.filters.gray-release.header-name, type: java.lang.String, defaultValue: X-Version } ] }具体的属性路径在不同版本里略有差异但思路是一样的。即使只写给自己用也能省掉不少“这个字段到底叫什么”的回忆成本。5.3 三个值得记住的调试断点如果自定义过滤器不生效我建议按顺序在三个位置打断点排查命中率极高。第一个断点打在RouteDefinitionRouteLocator#getFilters里。这里能直接看到当前路由的过滤器定义是否被正确解析工厂有没有被识别成功。用条件断点按过滤器名过滤比如GrayRelease.equals(entry.getKey())可以避开其他路由噪声只看目标工厂的调用。如果到这步就找不到工厂说明 Bean 没注册或者名字推导和配置对不上问题在装配阶段。第二个断点打在自定义的apply(Config config)方法上。能走到这里说明工厂已被正确调用。检查传入的 Config 对象字段值是否符合预期能快速区分“参数没绑上”和“过滤逻辑写错”两种情况。特别留意短格式配置时字段值是否串位。第三个断点打在过滤器内部的chain.filter(exchange)调用处。到了这里配置解析和工厂调用都已通过剩下的问题只可能是过滤逻辑本身条件判断不对、mutate 创建的新 exchange 没有往后传、或者下游根本看不到你改的请求头。这个断点也是排查“到底有没有进这个过滤器”的最直接手段。这三个断点覆盖的链路正好对应过滤器的生命周期三个阶段装配、创建、执行。我调试 Gateway 相关问题时基本只在这三处停留其他位置看日志和堆栈就够了。读源码这件事如果只盯着某个类看很容易迷失在细节里。把主线捋出来——配置如何进入工厂、工厂如何产出过滤器、过滤器如何作用于请求再结合自己的业务写一个最简单的工厂GatewayFilterFactory 这部分就算真正吃透了。我建议你也找一个没读过的内置工厂比如RewritePathGatewayFilterFactory按同样思路走一遍对比一下实现差异收获会比单纯看文档大很多。