摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud-Gateway/handler-route-predicate-factory/ 「芋道源码」欢迎转载,保留摘要,谢谢!
本文主要基于 Spring-Cloud-Gateway 2.0.X M4
阅读源码最好的方式,是使用 IDEA 进行调试 Spring Cloud Gateway 源码,不然会一脸懵逼。
1. 概述
本文主要分享 RoutePredicateFactory 路由谓语工厂。
RoutePredicateFactory 涉及到的类在 org.springframework.cloud.gateway.handler.predicate 包下,如下图 :

Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象。Predicate 对象可以赋值给 Route.predicate 属性,用于匹配请求对应的 Route 。
推荐 Spring Cloud 书籍:
请支持正版。下载盗版,等于主动编写低级 BUG 。
程序猿DD —— 《Spring Cloud微服务实战》
两书齐买,京东包邮。
2. RoutePredicateFactory
org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory , 路由谓语工厂接口。代码如下 :
@FunctionalInterface
public interface RoutePredicateFactory extends ArgumentHints {
String PATTERN_KEY = "pattern";
Predicate<ServerWebExchange> apply(Tuple args);
default String name() {
return NameUtils.normalizePredicateName(getClass());
}
}#name()默认方法,调用NameUtils#normalizePredicateName(Class)方法,获得 RoutePredicateFactory 的名字。该方法截取类名前半段,例如 QueryRoutePredicateFactory 的结果为Query。点击 链接 查看该方法。#apply()接口方法,创建 Predicate 。继承
org.springframework.cloud.gateway.support.ArgumentHints接口 ,在 《Spring-Cloud-Gateway 源码解析 —— 路由(2.2)之 RouteDefinitionRouteLocator 路由配置》「2.4 获得 Tuple」 有使用到它的代码。
RoutePredicateFactory 实现类如下图 :


另外,org.springframework.cloud.gateway.handler.predicate.RoutePredicates ,RoutePredicates 工厂,其调用 RoutePredicateFactory 接口的实现类,创建各种 Predicate 。
3. AfterRoutePredicateFactory
Route 匹配 :请求时间满足在配置时间之后。
配置 :
spring: cloud: gateway: routes: # ===================================== - id: after_route uri: http://example.org predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver]RoutePredicates 方法 :#after(ZonedDateTime) 。
代码 :
public class AfterRoutePredicateFactory implements RoutePredicateFactory { public static final String DATETIME_KEY = "datetime"; @Override public List<String> argNames() { return Collections.singletonList(DATETIME_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { Object value = args.getValue(DATETIME_KEY); final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); return now.isAfter(dateTime); }; } }Tulpe 参数 :
datetime。第 13 行 :调用
BetweenRoutePredicateFactory#getZonedDateTime(value)方法,解析配置的时间值,在 「5. BetweenRoutePredicateFactory」 详细解析。
4. BeforeRoutePredicateFactory
Route 匹配 :请求时间满足在配置时间之前。
RoutePredicates 方法 :#before(ZonedDateTime) 。
配置 :
spring: cloud: gateway: routes: # ===================================== - id: before_route uri: http://example.org predicates: - Before=2017-01-20T17:42:47.789-07:00[America/Denver]代码 :
public class BeforeRoutePredicateFactory implements RoutePredicateFactory { public static final String DATETIME_KEY = "datetime"; @Override public List<String> argNames() { return Collections.singletonList(DATETIME_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { Object value = args.getValue(DATETIME_KEY); final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); return now.isBefore(dateTime); }; } }Tulpe 参数 :
datetime。第 13 行 :调用
BetweenRoutePredicateFactory#getZonedDateTime(value)方法,解析配置的时间值,在 「5. BetweenRoutePredicateFactory」 详细解析。
5. BetweenRoutePredicateFactory
Route 匹配 :请求时间满足在配置时间之间。
RoutePredicates 方法 :
#between(ZonedDateTime, ZonedDateTime)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: between_route uri: http://example.org predicates: - Betweeen=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]代码 :
public class BetweenRoutePredicateFactory implements RoutePredicateFactory { public static final String DATETIME1_KEY = "datetime1"; public static final String DATETIME2_KEY = "datetime2"; @Override public Predicate<ServerWebExchange> apply(Tuple args) { //TODO: is ZonedDateTime the right thing to use? final ZonedDateTime dateTime1 = getZonedDateTime(args.getValue(DATETIME1_KEY)); final ZonedDateTime dateTime2 = getZonedDateTime(args.getValue(DATETIME2_KEY)); Assert.isTrue(dateTime1.isBefore(dateTime2), args.getValue(DATETIME1_KEY) + " must be before " + args.getValue(DATETIME2_KEY)); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); return now.isAfter(dateTime1) && now.isBefore(dateTime2); }; } public static ZonedDateTime getZonedDateTime(Object value) { ZonedDateTime dateTime; if (value instanceof ZonedDateTime) { dateTime = ZonedDateTime.class.cast(value); } else { dateTime = parseZonedDateTime(value.toString()); } return dateTime; } public static ZonedDateTime parseZonedDateTime(String dateString) { ZonedDateTime dateTime; try { // 数字 long epoch = Long.parseLong(dateString); dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0)) .toZonedDateTime(); } catch (NumberFormatException e) { // 字符串 // try ZonedDateTime instead dateTime = ZonedDateTime.parse(dateString); } return dateTime; } }Tulpe 参数 :
datetime1/datetime2。第 20 至 44 行 :解析配置的时间值 。
第 22 至 23 行 :当值类型为 ZonedDateTime 。主要使用 Java / Kotlin 配置 Route 时,例如
RoutePredicates#between(ZonedDateTime, ZonedDateTime)。第 33 至 36 行 :当值类型为 Long 。例如配置文件
1511795602765。当 38 至 41 行 :当值类型为 String 。例如配置文件里
2017-01-20T17:42:47.789-07:00[America/Denver]。
6. CookieRoutePredicateFactory
Route 匹配 :请求指定 Cookie 正则匹配指定值。
RoutePredicates 方法 :
#cookie(String, String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: cookie_route uri: http://example.org predicates: - Cookie=chocolate, ch.p代码 :
public class CookieRoutePredicateFactory implements RoutePredicateFactory { public static final String NAME_KEY = "name"; public static final String REGEXP_KEY = "regexp"; @Override public List<String> argNames() { return Arrays.asList(NAME_KEY, REGEXP_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { String name = args.getString(NAME_KEY); String regexp = args.getString(REGEXP_KEY); return exchange -> { List<HttpCookie> cookies = exchange.getRequest().getCookies().get(name); for (HttpCookie cookie : cookies) { // 正则匹配 if (cookie.getValue().matches(regexp)) { return true; } } return false; }; } }Tulpe 参数 :
name/regexp。第 20 行 :指定 Cookie 正则匹配指定值。
7. HeaderRoutePredicateFactory
Route 匹配 :请求指定 Cookie 正则匹配指定值。
RoutePredicates 方法 :
#header(String, String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: header_route uri: http://example.org predicates: - Header=X-Request-Id, \d+代码 :
public class HeaderRoutePredicateFactory implements RoutePredicateFactory { public static final String HEADER_KEY = "header"; public static final String REGEXP_KEY = "regexp"; @Override public List<String> argNames() { return Arrays.asList(HEADER_KEY, REGEXP_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { String header = args.getString(HEADER_KEY); String regexp = args.getString(REGEXP_KEY); return exchange -> { List<String> values = exchange.getRequest().getHeaders().get(header); for (String value : values) { // 正则匹配 if (value.matches(regexp)) { return true; } } return false; }; } }Tulpe 参数 :
header/regexp。第 20 行 :指定 Header 正则匹配指定值。
8. HostRoutePredicateFactory
Route 匹配 :请求 Host 匹配指定值。
RoutePredicates 方法 :
#host(String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: host_route uri: http://example.org predicates: - Host=**.somehost.org代码 :
public class HostRoutePredicateFactory implements RoutePredicateFactory { private PathMatcher pathMatcher = new AntPathMatcher("."); public void setPathMatcher(PathMatcher pathMatcher) { this.pathMatcher = pathMatcher; } @Override public List<String> argNames() { return Collections.singletonList(PATTERN_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { String pattern = args.getString(PATTERN_KEY); return exchange -> { String host = exchange.getRequest().getHeaders().getFirst("Host"); // 匹配 return this.pathMatcher.match(pattern, host); }; } }Tulpe 参数 :
pattern。pathMatcher属性,路径匹配器,默认使用org.springframework.util.AntPathMatcher。通过#setPathMatcher(PathMatcher)方法,可以重新设置。第 21 行 :请求路径 匹配指定值。
9. MethodRoutePredicateFactory
Route 匹配 :请求 Method 匹配指定值。
RoutePredicates 方法 :
#method(String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: method_route uri: http://example.org predicates: - Method=GET代码 :
public class MethodRoutePredicateFactory implements RoutePredicateFactory { public static final String METHOD_KEY = "method"; @Override public List<String> argNames() { return Arrays.asList(METHOD_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { String method = args.getString(METHOD_KEY); return exchange -> { HttpMethod requestMethod = exchange.getRequest().getMethod(); // 正则匹配 return requestMethod.matches(method); }; } }Tulpe 参数 :
method。第 16 行 :请求 Method 匹配指定值。
10. PathRoutePredicateFactory
Route 匹配 :请求 Path 匹配指定值。
RoutePredicates 方法 :
#path(String, String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: host_route uri: http://example.org predicates: - Path=/foo/{segment}代码 :
public class PathRoutePredicateFactory implements RoutePredicateFactory { private PathPatternParser pathPatternParser = new PathPatternParser(); public void setPathPatternParser(PathPatternParser pathPatternParser) { this.pathPatternParser = pathPatternParser; } @Override public List<String> argNames() { return Collections.singletonList(PATTERN_KEY); } @Override public Predicate<ServerWebExchange> apply(Tuple args) { // 解析 Path ,创建对应的 PathPattern String unparsedPattern = args.getString(PATTERN_KEY); PathPattern pattern; synchronized (this.pathPatternParser) { pattern = this.pathPatternParser.parse(unparsedPattern); } return exchange -> { PathContainer path = parsePath(exchange.getRequest().getURI().getPath()); // 匹配 boolean match = pattern.matches(path); traceMatch("Pattern", pattern.getPatternString(), path, match); if (match) { // 解析 路径参数,例如 path=/foo/123 <=> /foo/{segment} PathMatchInfo uriTemplateVariables = pattern.matchAndExtract(path); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); return true; } else { return false; } }; } }Tulpe 参数 :
pattern。pathPatternParser属性,路径模式解析器。第 17 至 21 行 :解析配置的 Path ,创建对应的 PathPattern 。考虑到解析过程中的线程安全,此处使用
synchronized修饰符,详见PathPatternParser#parse(String)方法的注释。第 24 至 27 行 :解析请求的 Path ,匹配配置的 Path 。
第 30 至 32 行 :解析路径参数,设置到
ServerWebExchange.attributes属性中,提供给后续的 GatewayFilter 使用。举个例子,当配置的 Path 为/foo/{segment},请求的 Path 为/foo/123,在此处打断点,结果如下图 :
FROM 《Spring Cloud Gateway》
This predicate extracts the URI template variables (likesegmentdefined in the example above) as a map of names and values and places it in theServerWebExchange.getAttributes()with a key defined inPathRoutePredicate.URL_PREDICATE_VARS_ATTR. Those values are then available for use by GatewayFilter Factories
11. QueryRoutePredicateFactory
Route 匹配 :请求 QueryParam 匹配指定值。
RoutePredicates 方法 :
#query(String, String)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: query_route uri: http://example.org predicates: - Query=baz - Query=foo, ba.代码 :
public class QueryRoutePredicateFactory implements RoutePredicateFactory { public static final String PARAM_KEY = "param"; public static final String REGEXP_KEY = "regexp"; @Override public List<String> argNames() { return Arrays.asList(PARAM_KEY, REGEXP_KEY); } @Override public boolean validateArgs() { return false; } @Override public Predicate<ServerWebExchange> apply(Tuple args) { validateMin(1, args); String param = args.getString(PARAM_KEY); return exchange -> { // 包含 参数 if (!args.hasFieldName(REGEXP_KEY)) { // check existence of header return exchange.getRequest().getQueryParams().containsKey(param); } // 正则匹配 参数 String regexp = args.getString(REGEXP_KEY); List<String> values = exchange.getRequest().getQueryParams().get(param); for (String value : values) { if (value.matches(regexp)) { return true; } } return false; }; } }Tulpe 参数 :
param( 必填 ) /regexp( 选填 ) 。第 18 行 :调用
#validateMin(...)方法,校验参数数量至少为1,即param非空 。第 22 至 26 行 :当
regexp为空时,校验param对应的QueryParam存在。第 28 至 35 行 :当
regexp非空时,请求param对应的 QueryParam 正则匹配指定值。当
QueryParams为空时,会报空指针 BUG 。
12. RemoteAddrRoutePredicateFactory
Route 匹配 :请求来源 IP 在指定范围内。
RoutePredicates 方法 :
#remoteAddr(String...)。配置 :
spring: cloud: gateway: routes: # ===================================== - id: remoteaddr_route uri: http://example.org predicates: - RemoteAddr=192.168.1.1/24
代码 :
public class RemoteAddrRoutePredicateFactory implements RoutePredicateFactory { private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class); @Override public Predicate<ServerWebExchange> apply(Tuple args) { validate(1, args); // List<SubnetUtils> sources = new ArrayList<>(); if (args != null) { for (Object arg : args.getValues()) { addSource(sources, (String) arg); } } return exchange -> { InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); if (remoteAddress != null) { // 来源 IP String hostAddress = remoteAddress.getAddress().getHostAddress(); String host = exchange.getRequest().getURI().getHost(); if (!hostAddress.equals(host)) { log.warn("Remote addresses didn't match " + hostAddress + " != " + host); } // for (SubnetUtils source : sources) { if (source.getInfo().isInRange(hostAddress)) { return true; } } } return false; }; } private void addSource(List<SubnetUtils> sources, String source) { boolean inclusiveHostCount = false; if (!source.contains("/")) { // no netmask, add default source = source + "/32"; } if (source.endsWith("/32")) { //http://stackoverflow.com/questions/2942299/converting-cidr-address-to-subnet-mask-and-network-address#answer-6858429 inclusiveHostCount = true; } //TODO: howto support ipv6 as well? SubnetUtils subnetUtils = new SubnetUtils(source); subnetUtils.setInclusiveHostCount(inclusiveHostCount); sources.add(subnetUtils); } }Tulpe 参数 :字符串数组。
第 7 行 :调用
#validateMin(...)方法,校验参数数量至少为1,字符串数组非空。第 10 至 15 行 :使用 SubnetUtils 工具类,解析配置的值。
第 21 至 25 行 :获得请求来源 IP 。
第 28 至 32 行 :请求来源 IP 在指定范围内。