Gateway 路由谓词工厂详解与自定义
谓词(Predicate)决定"哪些请求命中这条路由"。Gateway 提供一组内置谓词工厂,全部可以组合使用(默认 AND 关系)。本文逐个拆解实现,并给出自定义谓词的方法。
谓词工厂机制
设计思想:约定式工厂
配置写法: Path=/api/order/**
│
▼
谓词工厂: RoutePredicateFactory<Config>
│ 名字 = 配置前缀(Path → PathRoutePredicateFactory)
▼
产生: Predicate<ServerWebExchange>shortcutFieldOrder 机制把配置字符串解析成 Config 对象(Path=/a/** 等价于 Path[0]=/a/**)。
java
// 工厂基类
public abstract class AbstractRoutePredicateFactory<C> implements RoutePredicateFactory<C> {
@Override
public Predicate<ServerWebExchange> apply(C config) {
throw new UnsupportedOperationException();
}
}内置谓词工厂总览
| 工厂 | 配置示例 | 匹配内容 |
|---|---|---|
| Path | Path=/api/order/** | 请求路径 |
| Header | Header=X-Request-Id, \d+ | 请求头(正则) |
| Cookie | Cookie=sessionId, abc | Cookie(正则) |
| Query | Query=page, \d+ | 查询参数 |
| Host | Host=api.example.com | Host 头(Ant 匹配) |
| Method | Method=GET,POST | HTTP 方法 |
| Before/After/Between | Before=2030-01-01T00:00:00+08:00 | 时间窗口 |
| Weight | Weight=group1, 80 | 权重路由(灰度) |
| RemoteAddr | RemoteAddr=192.168.1.1/24 | 客户端 IP |
| XForwardedRemoteAddr | XForwardedRemoteAddr=10.0.0.0/8 | 代理转发后的 IP |
Path 谓词
配置与用法
yaml
- Path=/api/order/**,/api/user/**源码实现
java
public class PathRoutePredicateFactory
extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// 编译 Ant 风格路径模式(支持 ** / * / ? 通配符)
final PathPattern pathPattern = this.pathMatcher
.getPathPatternParser().parse(config.getPattern());
return exchange -> {
// 获取请求路径(可选去前缀)
PathContainer path = exchange.getRequest().getPath();
// Ant 模式匹配
boolean match = pathPattern.matches(path);
if (!match) return false;
// 提取路径参数(/api/order/{id} → id=xxx)
Map<String, String> uriVariables = pathPattern.matchAndExtract(path).getUriVariables();
exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables);
return true;
};
}
}路径参数提取
yaml
- Path=/api/order/{orderId}java
@GetMapping("/api/order/{orderId}")
// 网关转发时 {orderId} 保留在路径中,下游正常取参**:匹配多级路径*:匹配一级{var}:路径变量(可被过滤器读取:exchange.getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE))
Header / Cookie / Query 谓词
三者机制相同:按名字取值 + 正则匹配。
Header 谓词
java
public class HeaderRoutePredicateFactory
extends AbstractRoutePredicateFactory<HeaderRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 取指定 header(支持多个同名取第一个)
List<String> values = exchange.getRequest().getHeaders()
.getOrDefault(config.getHeader(), Collections.emptyList());
// 正则匹配
return values.stream().anyMatch(value -> regexMatches(config.getRegexp(), value));
};
}
}yaml
- Header=X-Request-Id, ^[a-f0-9]{32}$ # 头存在且值匹配正则
- Header=Content-Type, application/json # 精确匹配Cookie 谓词
java
public class CookieRoutePredicateFactory
extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 解析 Cookie,取指定名字
MultiValueMap<String, String> cookies = exchange.getRequest().getCookies();
List<String> values = cookies.get(config.getName());
// 正则匹配值
return values != null && values.stream()
.anyMatch(value -> regexMatches(config.getRegexp(), value));
};
}
}yaml
- Cookie=sessionId, ^[0-9]{16}$ # sessionId cookie 是 16 位数字Query 谓词
java
public class QueryRoutePredicateFactory
extends AbstractRoutePredicateFactory<QueryRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 取查询参数
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
List<String> values = queryParams.get(config.getParam());
// 只校验参数存在(无正则)或有值匹配
if (values == null) return false;
if (config.getRegexp() == null) return true;
return values.stream().anyMatch(value -> regexMatches(config.getRegexp(), value));
};
}
}yaml
- Query=page, \d+ # 必须带 page 且为数字
- Query=debug # 只要求存在 debug 参数Host / Method 谓词
Host 谓词
支持 Ant 通配符匹配 Host:
yaml
- Host=api.example.com
- Host=**.example.com # 子域名都匹配
- Host=api.example.com:8080 # 带端口java
public class HostRoutePredicateFactory
extends AbstractRoutePredicateFactory<HostRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// 编译多个 Host 模式(Ant Path 匹配)
List<PathPattern> patterns = config.getPatterns().stream()
.map(pattern -> pathMatcher.getPathPatternParser().parse(pattern))
.collect(toList());
return exchange -> {
String host = exchange.getRequest().getHeaders()
.getFirst(HttpHeaders.HOST);
return host != null && patterns.stream()
.anyMatch(pattern -> pattern.matches(PathContainer.parsePath(host)));
};
}
}Method 谓词
java
public class MethodRoutePredicateFactory
extends AbstractRoutePredicateFactory<MethodRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
Set<HttpMethod> methods = config.getMethods();
return exchange -> methods.contains(exchange.getRequest().getMethod());
}
}yaml
- Method=GET,POST # 只允许 GET/POST 请求时间谓词:Before / After / Between
用时间窗控制路由可用性(典型场景:定时开放的营销活动入口)。
yaml
# 2026-08-01 08:00 之后才可访问
- After=2026-08-01T08:00:00+08:00[Asia/Shanghai]
# 活动结束前可访问
- Before=2026-08-31T23:59:59+08:00[Asia/Shanghai]
# 时间窗口内可访问
- Between=2026-08-01T08:00:00+08:00, 2026-08-31T23:59:59+08:00源码实现(以 After 为例)
java
public class AfterRoutePredicateFactory
extends AbstractRoutePredicateFactory<AfterRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
ZonedDateTime dateTime = config.getDatetime();
return exchange -> {
ZonedDateTime now = ZonedDateTime.now(dateTime.getZone());
return now.isAfter(dateTime); // 当前时间 > 配置时间
};
}
}时间格式
标准:ISO-8601(yyyy-MM-ddTHH:mm:ss+08:00)
可选:+08:00[Asia/Shanghai] 指定时区Weight 谓词(灰度发布)
基于权重把流量按比例分流到不同路由:
yaml
spring:
cloud:
gateway:
routes:
- id: order-v1
uri: lb://order-service
predicates:
- Path=/api/order/**
- Weight=group-order, 80 # 80% 流量
- id: order-v2
uri: lb://order-service-v2
predicates:
- Path=/api/order/**
- Weight=group-order, 20 # 20% 流量实现原理
java
public class WeightRoutePredicateFactory
extends AbstractRoutePredicateFactory<WeightRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
// 计算组内权重区间
WeightCalculator weightCalculator = getWeightCalculator();
return exchange -> {
// 随机数落在 [0, 总权重) 内,命中自己的区间则 true
int randomWeight = new Random().nextInt(weightCalculator.getTotalWeight(group));
return randomWeight < config.getWeight();
};
}
}- 同 group 的所有 Weight 谓词共享随机数(保证每次请求只命中一个路由)
- 权重之和不必为 100(按比例即可)
RemoteAddr / XForwardedRemoteAddr 谓词
限制来源 IP(白名单/黑名单):
yaml
- RemoteAddr=192.168.1.0/24,10.0.0.0/8
- XForwardedRemoteAddr=172.16.0.0/12源码实现
java
public class RemoteAddrRoutePredicateFactory
extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config> {
@Override
public Predicate<ServerWebExchange> apply(Config config) {
List<IpSubnetFilterRule> rules = config.getSources().stream()
.map(IpSubnetFilterRule::new) // 解析 IP/子网
.collect(toList());
return exchange -> {
InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
if (remoteAddress == null) return false;
// 逐个规则判断
return rules.stream().anyMatch(rule -> rule.matches(remoteAddress));
};
}
}注意:RemoteAddr 取的是直连 IP(可能是负载均衡 IP),经过 SLB/nginx 后需用 XForwardedRemoteAddr。
自定义谓词
实现步骤
- 实现
RoutePredicateFactory<Config>(或继承AbstractRoutePredicateFactory) - 定义 Config 静态内部类
- 注册为 Spring Bean
java
@Component
public class HeaderVersionRoutePredicateFactory
extends AbstractRoutePredicateFactory<HeaderVersionRoutePredicateFactory.Config> {
public HeaderVersionRoutePredicateFactory() {
super(Config.class);
}
// 短配置解析顺序:HeaderVersion=1.0 → config.version=1.0
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("version");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String version = exchange.getRequest()
.getHeaders().getFirst("X-Api-Version");
return config.getVersion().equals(version);
};
}
@Validated
public static class Config {
@NotEmpty private String version;
// getter/setter
}
}使用
yaml
- HeaderVersion=1.0命名规则
类名 XxxRoutePredicateFactory → 配置前缀 Xxx(HeaderVersion)。注册为 Bean 后 Gateway 自动发现。
组合匹配与优先级
多个谓词的组合
同一路由多个谓词 = AND:
yaml
predicates:
- Path=/api/order/**
- Method=POST
- Header=X-Request-Id, ^[a-f0-9]{32}$
# 必须全部满足才命中多个路由的优先级
路由按 order 属性(默认 0)排序,小的先匹配:
yaml
- id: specific-route
order: -1 # 优先级更高,先匹配
predicates:
- Path=/api/order/special/**
- id: general-route
order: 0
predicates:
- Path=/api/order/**匹配到第一个就停止(短路)。
常见问题
- 谓词没生效? 检查配置前缀是否与工厂名完全一致、Bean 是否注册、路由是否重新加载。
- 多个谓词的关系? 同一路由内是 AND;不同路由之间是"先到先得"(按 order)。
- Weight 路由不按比例? 同 group 名称必须完全一致,且每个路由都要加 Weight 谓词。
- 路径参数怎么传到下游? 谓词提取的
{var}会存到 exchange 属性,下游仍按原路径访问(可通过 RewritePath 改写)。