SpringCloud Gateway 系列文章共五篇,由我行开发工程师 @Aaron 提供,带大家深入剖析 Gateway 工作原理,及如何基于 Gateway 进行定制化开发以适应企业特定环境需求。
第二篇:SpringCloud Gateway 路由数量对性能的影响研究。
第三篇:SpringCloud Gateway 路由转发性能优化。
第五篇:SpringCloud Gateway 过滤器。
断言即判断一个命题的真伪,路由断言则用于判断该路由是否可应用于当前请求。
见上图,Route 各属性分别为:
接下来我们看 AsyncPredicate 是如何定义的:
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {//...}
复制代码
我们对比下 jdk 中的内置 FunctionalInterface:
public interface Predicate<T> {//...}
public interface Function<T, R> {//...}
复制代码
如果你能认识到Predicate<T>
其实是一个特殊的Function<T, R>
,它的返回类型R
规定为Boolean
,你就能轻松的理解 AsyncPredicate<T>
:它是一个响应式编程模型下的Predicate<T>
,返回值为Mono<Boolean>
。
在前文动态路由章节,我们已经在路由查找过程中,看到过断言时如何工作的,见RoutePredicateHandlerMapping 源码 126-134 行
protected Mono<Route> lookupRoute(ServerWebExchange exchange) { // routeLocator.getRoutes() 获取的路由已经排序 return this.routeLocator.getRoutes() // concatMap 与 flatmap的区别就是能保持执行顺序不变 .concatMap(route -> // filterWhen 发生订阅时执行,就相当于按序遍历符合条件的记录 Mono.just(route).filterWhen(r -> { exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); // 从route中获取断言,并判断是否为真,并返回 return r.getPredicate().apply(exchange); }) //...}
复制代码
filterWhen
接收的是一个Function<T, Publisher<Boolean>>
的函数式接口,也就是上面我们介绍的 AsyncPredicate<T>
。
前文动态路由章节,我们介绍过Route
是有RouteDefinition
转换而来,执行转换的类为
private Route convertToRoute(RouteDefinition routeDefinition) { // combinePredicates 根据路由定义中的断言定义组合为路由断言 AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition); List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
return Route.async(routeDefinition).asyncPredicate(predicate) .replaceFilters(gatewayFilters).build();}
复制代码
源码 241-286 行
// 该方法使用 and 关系 组合了所有的断言定义private AsyncPredicate<ServerWebExchange> combinePredicates( RouteDefinition routeDefinition) { List<PredicateDefinition> predicates = routeDefinition.getPredicates(); if (predicates == null || predicates.isEmpty()) { // this is a very rare case, but possible, just match all return AsyncPredicate.from(exchange -> true); } AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0));
for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate); // and 表明多个断言的关系,也就是必须所有条件都符合才能命中一个路由 predicate = predicate.and(found); }
return predicate; }
// 根据路由定义中的断言定义,从指定 断言工厂 获取一个实例化 断言 private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) { RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName()); // 省略非关键代码 return factory.applyAsync(config); }
复制代码
上文我们已经解析清楚,一个请求是否命中某个路由,最终是由路由定义
中的断言定义
对应的断言工厂的applyAsync
方法决定。
见上图,RoutePredicateFactory
是一个FunctionalInterface
其唯一抽象方法为:
Predicate<ServerWebExchange> apply(C config);
复制代码
具体的RoutePredicateFactory
的实现工厂类,需实现上述方法。
Gateway 内置的路由工厂有:
以上每个工厂的apply
都实现了如何根据配置及ServerWebExchange
来判断当前请求是否匹配断言(如果需要自定义断言工厂,也需要实现此逻辑)。
每个路由工厂如何配置,官方文档均有示例。
我们看如下示例:
spring: cloud: gateway: routes: - id: path_route uri: https://example.org predicates: - Path=/red/**,/blue/**
复制代码
关键字 Path
表示采用 PathRoutePredicateFactory
工厂创建该断言;
Path
后是逗号分隔的有序字符串数据,表示只要匹配任意一个 path,该断言即为真(见apply
方法),路由命中。
下面方法可用于创建动态路由时,生成断言定义:
/** * 生成断言定义 * * @param type 断言名称 * @param orderedArgs 有序的参数数组 * @return 路由断言定义 */ public static PredicateDefinition createPredicate(@NotNull PredicateType type, @NotNull String... orderedArgs) { PredicateDefinition definition = new PredicateDefinition(); definition.setName(type.name()); int order = 0; for (String arg : orderedArgs) { if (StringUtils.isNotBlank(arg)) { definition.addArg(NameUtils.generateName(order++), arg); } } return definition; }
复制代码
断言定义的 Name 即为断言工厂的前缀字符串。
本文较为详细的分析了 Gateway 是如何判断一个请求应采用哪个路由进行处理的原理,并进一步分析了断言工厂的实现,以及配置文件及断言定义如何与断言工厂相对应。
希望通过本文,你能帮助你加深对 Gateway 的理解。
领取专属 10元无门槛券
私享最新 技术干货