文章目录
- 一、前言
- 二、过滤器的两种类型
- 三、局部过滤器的标准写法
- [四、YAML 配置的两种写法](#四、YAML 配置的两种写法)
- [五、shortcutFieldOrder 是什么](#五、shortcutFieldOrder 是什么)
-
- [args 的含义](#args 的含义)
- [六、WhiteList 白名单过滤器实战](#六、WhiteList 白名单过滤器实战)
- [七、StripPrefix 路径前缀过滤器](#七、StripPrefix 路径前缀过滤器)
- 八、前置过滤器和后置过滤器
- 九、执行顺序(重点)
- 十、多个路由只走部分过滤器
- 十一、常见内置过滤器速查
- 十二、易错点清单
- 十三、总结
一篇讲透 Gateway 过滤器的文章,涵盖局部过滤器、全局过滤器、配置写法、执行顺序、
shortcutFieldOrder、StripPrefix、WhiteList实战等核心知识点。
一、前言
Spring Cloud Gateway 是 Spring Cloud 生态中的第二代网关,基于 WebFlux 响应式编程模型。它的核心能力之一就是过滤器(Filter),用于在请求转发前后做各种处理:鉴权、限流、日志、路径重写、白名单校验等。
但很多人在使用时会遇到几个困惑:
- 局部过滤器和全局过滤器到底有什么区别?
- YAML 里
WhiteList=admin,test这种简写是怎么解析的? shortcutFieldOrder()是干什么的?- 多个过滤器一起用时,执行顺序到底怎么排?
getOrder()和配置顺序,谁说了算?
这篇文章一次讲清楚。
二、过滤器的两种类型
| 对比项 | 全局过滤器 | 局部过滤器 |
|---|---|---|
| 实现方式 | 实现 GlobalFilter(通常再实现 Ordered) |
继承 AbstractGatewayFilterFactory |
| 生效范围 | 所有路由,请求必走 | 只对挂载了它的路由生效 |
| 配置位置 | 无,@Component 自动生效 |
routes[].filters |
| 能否按路由选择 | 不能(除非内部自己判断) | 能,配在哪个路由就只对哪个路由生效 |
| 典型用途 | 鉴权、全局日志、限流 | 路径重写、白名单校验、加特定请求头 |
| 顺序控制 | getOrder() |
默认按 YAML 声明顺序;实现 Ordered 则以 getOrder() 为准 |
一句话总结:
全局过滤器是代码级、对所有路由生效、不能按路由排除;局部过滤器是配置级、按路由挂载、可自由选择给哪些路由用。
三、局部过滤器的标准写法
继承 AbstractGatewayFilterFactory,并定义一个内部 Config 类。
java
@Component
public class WhiteListGatewayFilterFactory
extends AbstractGatewayFilterFactory<WhiteListGatewayFilterFactory.Config> {
private static final String VALUE = "value";
public WhiteListGatewayFilterFactory() {
super(Config.class);
}
@Override
public List shortcutFieldOrder() {
return Collections.singletonList(VALUE);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String userName = exchange.getRequest().getQueryParams().getFirst("userName");
if (config.getValue() != null
&& Arrays.asList(config.getValue().split(",")).contains(userName)) {
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
};
}
public static class Config {
private String value;
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
}
}
命名规则(重要)
YAML 里用的名字 = 类名去掉固定后缀 GatewayFilterFactory。
| 类名 | YAML 里用的名字 |
|---|---|
WhiteListGatewayFilterFactory |
WhiteList |
StripPrefixGatewayFilterFactory |
StripPrefix |
CostTimeGatewayFilterFactory |
CostTime |
注意:不是前缀匹配 ,而是去掉固定后缀后做字符串精确匹配 (大小写敏感)。写成 White、WhiteListFilter、whiteList 都找不到。
如果用了 @Component("别名") 指定 Bean 名,YAML 里就要用别名:
java
@Component("MyWhiteList")
public class WhiteListGatewayFilterFactory extends AbstractGatewayFilterFactory<...> { ... }
yaml
filters:
- MyWhiteList=admin,test
四、YAML 配置的两种写法
以 WhiteList 为例:
简写形式:
yaml
filters:
- WhiteList=admin,test
完整形式:
yaml
filters:
- name: WhiteList
args:
value: admin,test
两者完全等价。
简写能生效的前提是过滤器工厂里实现了 shortcutFieldOrder()。
五、shortcutFieldOrder 是什么
中文翻译:简写字段顺序。
含义: 当过滤器在 YAML 里用简写形式(如 WhiteList=admin,test)时,用它来指定这个简写值应该按顺序赋给 Config 类中的哪些字段。
java
private static final String VALUE = "value";
@Override
public List shortcutFieldOrder() {
return Collections.singletonList(VALUE);
}
- 返回
["value"]时,整个admin,test都赋给value字段。 - 返回
["value", "enabled"]时,WhiteList=admin,true就会把admin赋给value、true赋给enabled。 - 返回值里的字符串必须和
Config字段名完全一致 ,否则注入不进去(字段为null)。 - 只有一个字段时,逗号属于值本身,不会被拆分。
args 的含义
args 是传给过滤器工厂 Config 类的参数集合,每个 key 对应 Config 的一个字段,Spring 自动注入。
| YAML key | Config 字段 | setter |
|---|---|---|
value |
private String value; |
setValue(...) |
parts |
private int parts; |
setParts(...) |
字段名支持宽松绑定(value、Value 都能绑定)。
六、WhiteList 白名单过滤器实战
配置
yaml
filters:
- WhiteList=admin,test
传递链路
WhiteList=admin,test
→ shortcutFieldOrder() 返回 ["value"]
→ Config.setValue("admin,test")
→ apply(config) 里 config.getValue() = "admin,test"
→ split(",") 拆成 ["admin", "test"]
→ contains(userName) 判断
效果
| 请求 | userName | 结果 |
|---|---|---|
?userName=admin |
admin | 放行 |
?userName=test |
test | 放行 |
?userName=guest |
guest | 401 |
| 不带参数 | null | 401 |
拆分成多项是过滤器运行时
split(",")做的,不在配置绑定阶段。
七、StripPrefix 路径前缀过滤器
StripPrefix 是 Spring Cloud Gateway 内置的局部过滤器,作用是去掉 URL 路径的前 N 层前缀。
源码
java
public class StripPrefixGatewayFilterFactory
extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {
public static final String PARTS_KEY = "parts";
public StripPrefixGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARTS_KEY);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
String path = request.getURI().getRawPath();
String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");
StringBuilder newPath = new StringBuilder("/");
for (int i = 0; i < originalParts.length; i++) {
if (i >= config.getParts()) {
if (newPath.length() > 1) {
newPath.append('/');
}
newPath.append(originalParts[i]);
}
}
if (newPath.length() > 1 && path.endsWith("/")) {
newPath.append('/');
}
ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest).build());
};
}
public static class Config {
private int parts = 1;
public int getParts() {
return parts;
}
public void setParts(int parts) {
this.parts = parts;
}
}
}
效果
| 配置 | 原始路径 | 转发路径 |
|---|---|---|
StripPrefix=1 |
/api/order/detail/100 |
/order/detail/100 |
StripPrefix=2 |
/api/order/detail/100 |
/detail/100 |
| 不配置 | /api/order/detail/100 |
/api/order/detail/100 |
官方示例:请求 /name/blue/red,配置 StripPrefix=2,转发到下游变成 /red。
易混点:
PrefixPath=/api是加前缀,方向相反。- 层数要数对,去掉层数过多会变成空路径。
- 如果使用
lb()负载均衡,需要放在stripPrefix()之后,否则生成的 URL 可能不正确。
八、前置过滤器和后置过滤器
这不是两种独立的过滤器类型,而是同一个过滤器内部的两个执行阶段 。在 apply 方法里,chain.filter(exchange) 是分界线:
java
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// ① 前置逻辑:chain.filter() 之前
System.out.println("请求进入时执行");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// ② 后置逻辑:chain.filter() 之后,通过 then 挂载
System.out.println("响应返回时执行");
}));
};
}
| 阶段 | 位置 | 执行时机 |
|---|---|---|
| 前置 | chain.filter(exchange) 之前 |
请求进入、转发给下游之前 |
| 后置 | chain.filter(exchange).then(...) 里 |
下游响应返回之后 |
执行顺序:多个过滤器的前置按 order 升序执行,后置按 order 降序执行(栈式,像洋葱一样层层包裹)。
前置 A → 前置 B → 转发到下游 → 后置 B → 后置 A
常见用途:
- 前置:鉴权、参数校验、添加请求头、记录开始时间
- 后置:记录耗时、修改响应头、记录响应状态码
注意 :如果前置逻辑直接 return exchange.getResponse().setComplete()(比如鉴权失败返回 401),请求不会继续往下走,后面的过滤器和后置逻辑都不会执行。
九、执行顺序(重点)
核心规则
所有过滤器(全局 + 局部)进入同一个链,统一排序:
- 先按 order 值升序排(值越小越先执行)
- order 值相同时,再按声明/加载顺序排
进入按此顺序,返回时逆序(栈式)。
各类过滤器的 order 来源
| 过滤器 | order 来源 | 默认值 |
|---|---|---|
| 全局过滤器 | getOrder() 返回值 |
必须自己实现,无默认 |
局部过滤器(实现 Ordered) |
getOrder() 返回值 |
无默认,由代码决定 |
局部过滤器(未实现 Ordered) |
固定 | 0 |
局部过滤器没写 order 时
默认 order = 0,同一条路由内多个默认 order 的过滤器,按 YAML 声明顺序执行。
yaml
filters:
- StripPrefix=1 # 先执行
- WhiteList=admin,test # 后执行
- name: CostTime
args:
name: order-service # 最后执行
进入:StripPrefix → WhiteList → CostTime
返回:CostTime → WhiteList → StripPrefix
局部过滤器实现了 Ordered 后
配置仍然决定"哪个路由挂载哪些过滤器",但先后顺序改由 getOrder() 决定,不再看 YAML 声明位置。
java
@Component
public class WhiteListGatewayFilterFactory
extends AbstractGatewayFilterFactory<WhiteListGatewayFilterFactory.Config>
implements Ordered {
@Override
public int getOrder() {
return -1;
}
}
yaml
filters:
- StripPrefix=1 # order = 0
- WhiteList=admin,test # order = -1
- CostTime # order = 0
实际顺序:
WhiteList (-1) → StripPrefix (0) → CostTime (0)
WhiteList 虽写在中间,但 order 最小,最先执行。
全局 + 局部混用
一视同仁,按 order 排队。
示例:
- 全局
AuthGlobalFilter,order = -1 - 全局
LogGlobalFilter,order = 100 - 局部
StripPrefix,order = 0 - 局部
WhiteList,order = 0
排序结果:
AuthGlobalFilter (-1)
StripPrefix (0)
WhiteList (0) ← 和 StripPrefix 同 order,按声明顺序
LogGlobalFilter (100)
进入:
AuthGlobalFilter → StripPrefix → WhiteList → LogGlobalFilter
返回:
LogGlobalFilter → WhiteList → StripPrefix → AuthGlobalFilter
getOrder 常用常量
| 常量 | 值 |
|---|---|
Ordered.HIGHEST_PRECEDENCE |
Integer.MIN_VALUE |
Ordered.LOWEST_PRECEDENCE |
Integer.MAX_VALUE |
关键结论
- 全局和局部一视同仁,按 order 排队。
- order 不同 → 按 order 排,和写在 YAML 哪里无关。
- order 相同 → 才按声明顺序排。
- 局部过滤器默认 order = 0,全局过滤器 order 为负则排在局部前面,为正则排在后面。
- 配置决定"挂不挂载",order 决定"先后顺序",两者是两回事。
十、多个路由只走部分过滤器
yaml
spring:
cloud:
gateway:
routes:
# 只走 StripPrefix + WhiteList
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- StripPrefix=1
- WhiteList=admin,test
# 走 StripPrefix + WhiteList + CostTime + AddResponseHeader
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- WhiteList=admin,test,guest
- name: CostTime
args:
name: user-service
- name: MyAddResponseHeader
args:
key: X-Gateway-By
value: gateway
# 一个都不走
- id: public-service
uri: lb://public-service
predicates:
- Path=/api/public/**
filters:
- StripPrefix=1
| 请求路径 | 走的过滤器 |
|---|---|
/api/order/** |
StripPrefix → WhiteList |
/api/user/** |
StripPrefix → WhiteList → CostTime → MyAddResponseHeader |
/api/public/** |
StripPrefix |
十一、常见内置过滤器速查
| 过滤器 | 作用 | 示例 |
|---|---|---|
AddRequestHeader |
添加请求头 | AddRequestHeader=X-Request-Id, 12345 |
AddRequestParameter |
添加查询参数 | AddRequestParameter=userName, John |
RemoveRequestHeader |
移除请求头 | RemoveRequestHeader=X-Debug |
StripPrefix |
去路径前缀 | StripPrefix=1 |
PrefixPath |
加路径前缀 | PrefixPath=/api |
Retry |
请求重试 | name: Retry, args: retries: 3 |
AddResponseHeader |
添加响应头 | name: AddResponseHeader, args: key/value |
十二、易错点清单
shortcutFieldOrder()的字符串必须和Config字段名一致,否则字段为null。- 字段名和 YAML key 必须对得上(支持宽松绑定)。
StripPrefix层数要数对,别和PrefixPath搞混。StripPrefix使用lb()时要放在负载均衡之后,否则 URL 可能不对。getOrder()只影响顺序,不影响是否执行;返回时是逆序。- 局部过滤器默认 order = 0;实现了
Ordered就以getOrder()为准,声明位置不再决定顺序。 - 配置决定"挂不挂载",order 决定"先后顺序",两者是两回事。
- 自定义过滤器类名去掉
GatewayFilterFactory才是 YAML 里用的名字。 - 简写形式
Filter=value依赖shortcutFieldOrder(),不写就只能用完整形式。 - 全局和局部混用时统一按 order 排序,order 相同才看声明顺序。
十三、总结
本文围绕 Spring Cloud Gateway 的过滤器,系统梳理了以下内容:
- 过滤器分类:全局过滤器 vs 局部过滤器的区别。
- 局部过滤器写法 :继承
AbstractGatewayFilterFactory,定义Config内部类。 - 命名规则 :类名去掉
GatewayFilterFactory后缀,字符串精确匹配。 - 配置写法 :简写形式与完整形式等价,简写依赖
shortcutFieldOrder()。 shortcutFieldOrder:指定简写值按顺序赋给哪些字段。args含义 :传给Config的参数集合。- 实战案例 :
WhiteList白名单校验、StripPrefix路径重写。 - 执行顺序:先按 order 升序,order 相同再按声明顺序;进入正序,返回逆序。
- 前置/后置逻辑 :同一个过滤器内
chain.filter()前后两个阶段。
掌握这些,基本可以应对 Gateway 过滤器的绝大多数使用场景。
一篇讲透 Gateway 过滤器的文章,涵盖局部过滤器、全局过滤器、配置写法、执行顺序、