1.简述
在之前 Gateway(一)初始化 文章中,已经把Gateway初始化阐述完了,接下来就是分析我们平时在Properties或者yml中配置的有关Gateway的配置是如何构建成Route的。
2.Route 构建方式
一般构建分为两种外部化配置和编程方式,例子都来自与官方。
外部化配置
:
spring:
cloud:
gateway:
routes:
- id: after_route //①
uri: https://example.org //②
predicates:
- Cookie=mycookie,mycookievalue//③
filters:
- AddRequestHeader=X-Request-Foo, Bar//④
- ① 配置了一个Route id 为 after_route
- ② 客户端请求转发的目的地:https://example.org
- ③ 在request 中 当存在名字 mycookie 的cookie的值匹配 mycookievalue 则算成功
- ④ 定义了一个Filter,匹配成功后,会在请求头上添加 X-Request-Foo:Bar
编程方式
:
用上面的例子,转换成编程方式
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
builder.routes()
.route(r -> r.cookie("mycookie","mycookievalue")
.filters(f -> f.addRequestHeader("X-Request-Foo","Bar"))
.uri("https://example.org")
)
.build();
}
简单介绍了构建Route的方式,下面分析Route是如何把外部化配置与编码配置之间进行转换。
3.Route 构建原理
在上面说了Route 构建有两种一种是外部配置方式
还有一种是编程方式
。
3.1 外部配置方式:
外部配置是通过 GatewayProperties
进行构建的,具体怎么构建参考我的这篇文章:
Spring Boot 注解 @ConfigurationProperties
public class GatewayProperties {
private final Log logger = LogFactory.getLog(getClass());
/**
* List of Routes.
*/
@NotNull
@Valid
private List<RouteDefinition> routes = new ArrayList<>();//① 用来对 Route 进行定义。
/**xxx
* List of filter definitions that are applied to every route.
* ② 用于定义默认的 Filter 列表,默认的 Filter 会应用到每一个 Route 上,gateway 处理时会将其与 Route 中指定的 Filter 进行合并后并逐个执行。
*/
private List<FilterDefinition> defaultFilters = new ArrayList<>();
}
-
① RouteDefinition
: 用来对 Route 进行定义。也就是,通过 GatewayProperties 会与外部化配置进行绑定,把外部化配置比如properties 或者 yml 绑定到 GatewayProperties 中。public class RouteDefinition { @NotEmpty private String id = UUID.randomUUID().toString(); // ① @NotEmpty @Valid private List<PredicateDefinition> predicates = new ArrayList<>(); // ② @Valid private List<FilterDefinition> filters = new ArrayList<>(); // ③ @NotNull private URI uri; // ④ private int order = 0; // ⑤ }
-
①
定义 Route 的 id,默认使用 UUID。 -
②
定义 Predicate。 -
③
定义 Filter。 -
④
定义目的地 URI。 -
⑤
定义 Route 的序号。
-
-
② FilterDefinition
:同上,默认的 filter,会作用到每一个 Route上。public class FilterDefinition { @NotNull private String name; //① private Map<String, String> args = new LinkedHashMap<>();//②
① filter
的名字② args
属性,参数数组。例如,name=AddRequestParameter
/args={"_genkey_0": "foo", "_genkey_1": "bar"}
,添加请求参数foo
为bar
。
-
③ PredicateDefinition
public class PredicateDefinition { @NotNull private String name; // ① private Map<String, String> args = new LinkedHashMap<>(); // ②
①
Predicate 的名称②
一个 Map 类型的参数,构造 Predicate 使用到的键值对参数。
3.1.1 RouteDefinitionLocator
在 Gateway(一)初始化 文章中的GatewayAutoConfiguration
核心配置类中,有提到此类。
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(
List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(
Flux.fromIterable(routeDefinitionLocators));
}
Gateway 提供多种方式来获取外部的配置,而RouteDefinitionLocator
就是父接口,下面有多种实现。
-
PropertiesRouteDefinitionLocator
:从配置文件( 例如,YML / Properties 等 ) 读取路由配置。代码如下 :public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator { private final GatewayProperties properties; public PropertiesRouteDefinitionLocator(GatewayProperties properties) { this.properties = properties; } @Override public Flux<RouteDefinition> getRouteDefinitions() { //从 GatewayProperties 获取路由配置数组。 return Flux.fromIterable(this.properties.getRoutes()); } }
GatewayProperties
如何构建的,参考我的这篇文章: -
InMemoryRouteDefinitionRepository
:从存储器( 例如,内存 / Redis / MySQL 等 )读取、保存、删除路由配置。InMemoryRouteDefinitionRepository 是基于内存的。public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { /** * 路由配置映射 通过此来保存route * key :路由编号 {@link RouteDefinition#id} */ private final Map<String, RouteDefinition> routes = synchronizedMap( new LinkedHashMap<String, RouteDefinition>()); @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.flatMap(r -> { if (StringUtils.isEmpty(r.getId())) { return Mono.error(new IllegalArgumentException("id may not be empty")); } routes.put(r.getId(), r); return Mono.empty(); }); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.flatMap(id -> { if (routes.containsKey(id)) { routes.remove(id); return Mono.empty(); } return Mono.defer(() -> Mono.error( new NotFoundException("RouteDefinition not found: " + routeId))); }); } @Override public Flux<RouteDefinition> getRouteDefinitions() { return Flux.fromIterable(routes.values()); } }
基于内存,通过
Map<String, RouteDefinition> routes
来保存Route,缺点是如果重启,那么route 会丢失。可以实现RouteDefinitionRepository 接口自定义比如通过Redis、Mysql 等来保存Route -
DiscoveryClientRouteDefinitionLocator
:获取在注册中心的服务列表,生成对应的 RouteDefinition 数组。public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { private static final Log log = LogFactory .getLog(DiscoveryClientRouteDefinitionLocator.class); private final DiscoveryClient discoveryClient; private final DiscoveryLocatorProperties properties; private final String routeIdPrefix; private final SimpleEvaluationContext evalCtxt; public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) { this.discoveryClient = discoveryClient; this.properties = properties; if (StringUtils.hasText(properties.getRouteIdPrefix())) { this.routeIdPrefix = properties.getRouteIdPrefix(); } else { this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_"; } evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods() .build(); } @Override public Flux<RouteDefinition> getRouteDefinitions() { SpelExpressionParser parser = new SpelExpressionParser(); Expression includeExpr = parser .parseExpression(properties.getIncludeExpression()); Expression urlExpr = parser.parseExpression(properties.getUrlExpression()); Predicate<ServiceInstance> includePredicate; if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) { includePredicate = instance -> true; } else { includePredicate = instance -> { Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class); if (include == null) { return false; } return include; }; } //① 获取discoveryClient ,然后发起请求 return Flux.defer(() -> Flux.fromIterable(discoveryClient.getServices())) .map(discoveryClient::getInstances).subscribeOn(Schedulers.elastic()) .filter(instances -> !instances.isEmpty()) .map(instances -> instances.get(0)).filter(includePredicate) .map(instance -> { String serviceId = instance.getServiceId(); RouteDefinition routeDefinition = new RouteDefinition(); // 设置 ID routeDefinition.setId(this.routeIdPrefix + serviceId); // 设置 uri String uri = urlExpr.getValue(evalCtxt, instance, String.class); routeDefinition.setUri(URI.create(uri)); final ServiceInstance instanceForEval = new DelegatingServiceInstance( instance, properties); //添加 path 断言 for (PredicateDefinition original : this.properties.getPredicates()) { PredicateDefinition predicate = new PredicateDefinition(); predicate.setName(original.getName()); for (Map.Entry<String, String> entry : original.getArgs() .entrySet()) { String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); predicate.addArg(entry.getKey(), value); } routeDefinition.getPredicates().add(predicate); } //添加path 重写过滤器 for (FilterDefinition original : this.properties.getFilters()) { FilterDefinition filter = new FilterDefinition(); filter.setName(original.getName()); for (Map.Entry<String, String> entry : original.getArgs() .entrySet()) { String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); filter.addArg(entry.getKey(), value); } routeDefinition.getFilters().add(filter); } return routeDefinition; }); } }
可以在官方的 GatewaySampleApplication 添加 Eureka 注册中心自行调试:
@EnableDiscoveryClient // 开启Eureka public class GatewaySampleApplication { // ... 省略其他代码 @Bean public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) { return new DiscoveryClientRouteDefinitionLocator(discoveryClient); } }
当然要自己加入Eureka 依赖以及配置文件
-
CachingRouteDefinitionLocator
:可以缓存一些Route在本地public class CachingRouteDefinitionLocator implements RouteDefinitionLocator, ApplicationListener<RefreshRoutesEvent> { private final RouteDefinitionLocator delegate; private final Flux<RouteDefinition> routeDefinitions; //收集Route private final Map<String, List> cache = new HashMap<>(); public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) { this.delegate = delegate; routeDefinitions = CacheFlux.lookup(cache, "routeDefs", RouteDefinition.class) .onCacheMissResume(this.delegate::getRouteDefinitions); } @Override public Flux<RouteDefinition> getRouteDefinitions() { return this.routeDefinitions; } }
-
CompositeRouteDefinitionLocator
:组合多种 RouteDefinitionLocator 的实现,为 RouteDefinitionRouteLocator 提供统一入口public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator { //RouteDefinitionLocator 数组 private final Flux<RouteDefinitionLocator> delegates; public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) { this.delegates = delegates; } //将组合的 delegates 的路由定义全部返回。 @Override public Flux<RouteDefinition> getRouteDefinitions() { return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions); } }
到这里外部化的配置
的多种方式全部解析完毕。接下来看一下编程方式是怎么样的。
3.2 编程方式
直接拿官方的例子来阐述:
//org.springframework.cloud.gateway.sample.GatewaySampleApplication
public class GatewaySampleApplication {
//...省略
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
//① RouteLocatorBuilder 可以构建多个路由信息。
return builder.routes()
//② 指定了 Predicates,这里包含两个:
//请求头Host需要匹配**.abc.org,通过 HostRoutePredicateFactory 产生。
//请求路径需要匹配/anything/png,通过 PathRoutePredicateFactory 产生。
.route(r -> r.host("**.abc.org").and().path("/anything/png")
//③ 指定了一个 Filter,下游服务响应后添加响应头X-TestHeader:foobar,
//通过AddResponseHeaderGatewayFilterFactory 产生。
.filters(f ->
f.prefixPath("/httpbin")
.addResponseHeader("X-TestHeader", "foobar"))
.uri(uri) //④ 指定路由转发的目的地 uri。
)
//...省略
//⑤ 创建RouteLocator实例
.build();
}
-
① 使用Builder 模式构建Route
-
② 创建Predicates
-
③ 创建Filter
-
④ 需要转发的目的地 uri
-
⑤ 创建RouteLocator实例
//返回一个RouteLocator public RouteLocator build() { return () -> Flux.fromIterable(this.routes) //① .map(routeBuilder -> routeBuilder.build()); } //RouteLocator 是Route 集合。 public interface RouteLocator { Flux<Route> getRoutes(); }
上面build 方法返回的是 RouteLocator对象,它是Route 的集合所以上面
①
中的build方法就是对应的Routepublic class Route implements Ordered { //id,标识符,区别于其他 Route。 private final String id; //destination uri,路由指向的目的地 uri,即客户端请求最终被转发的目的地。 private final URI uri; //order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。 private final int order; //predicate,谓语,表示匹配该 Route 的前置条件,即满足相应的条件才会被路由到目的地 uri。 private final AsyncPredicate<ServerWebExchange> predicate; //gateway filters,过滤器用于处理切面逻辑,如路由转发前修改请求头等。 private final List<GatewayFilter> gatewayFilters; //对应①处 public Route build() { Assert.notNull(this.id, "id can not be null"); Assert.notNull(this.uri, "uri can not be null"); AsyncPredicate<ServerWebExchange> predicate = getPredicate(); Assert.notNull(predicate, "predicate can not be null"); return new Route(this.id, this.uri, this.order, predicate, this.gatewayFilters); } }
这里的 AsyncPredicate 、 GatewayFilter 在 核心组件部分已经分析过了,就不重复分析了。
到这里,外部化配置的Route、Predicate、Filter会被映射成 RouteDefinition、FilterDefinition、PredicateDefinition,而 编码方式会被映射成 Route、AsyncPredicate 、GatewayFilter。
有时候我们可能又有外部化配置又有编码方式的配置,那么这时候就需要有一个转换。那接下来就看一下Gateway 是如何转换的。
4.RouteDefinitionRouteLocator
将外部化配置的 RouteDefinition、FilterDefinition、PredicateDefinition 转换成Route、AsyncPredicate 、GatewayFilter。
首先还是回到Gateway(一)初始化 文章中的核心配置类GatewayAutoConfiguration ,给我提供了分析的入口,代码如下:
//① GatewayProperties
//② RouteDefinitionLocator
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> gatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
@Qualifier("webFluxConversionService") ConversionService conversionService) {
//③ 这里进行转换
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
gatewayFilters, properties, conversionService);
}
-
①、②
在上面讲过外部化配置讲过。这里就不重复阐述了 -
③
外部化配置进行转换,RouteDefinitionRouteLocator 是 RouteLocator 的实现public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware { /** * Default filters name. */ public static final String DEFAULT_FILTERS = "defaultFilters"; // RouteDefinition Locator,一个 RouteDefinitionLocator 对象。 private final RouteDefinitionLocator routeDefinitionLocator; /** * predicates factories,Predicate 工厂列表,会被映射成 key 为 name, value 为 factory 的 Map。 * 可以猜想出 gateway 是如何根据 PredicateDefinition 中定义的 name 来匹配到相对应的 factory 了。 * key :{@link RoutePredicateFactory#name()} */ private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>(); /**filter factories,Gateway Filter 工厂列表,同样会被映射成 key 为 name, value 为 factory 的 Map。 * key :{@link GatewayFilterFactory#name()} */ private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>(); //gateway properties,外部化配置类。 private final GatewayProperties gatewayProperties; public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories, GatewayProperties gatewayProperties, ConversionService conversionService) { // 设置 RouteDefinitionLocator this.routeDefinitionLocator = routeDefinitionLocator; this.conversionService = conversionService; // ① 初始化 RoutePredicateFactory initFactories(predicates); // ② 初始化 gatewayFilterFactories gatewayFilterFactories.forEach( factory -> this.gatewayFilterFactories.put(factory.name(), factory)); // 设置 GatewayProperties this.gatewayProperties = gatewayProperties; } //③ 实现 RouteLocator 的 getRoutes() 方法 获取route @Override public Flux<Route> getRoutes() { //调用 convertToRoute 方法将 RouteDefinition 转换成 Route。 return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute) // TODO: error handling .map(route -> { if (logger.isDebugEnabled()) { logger.debug("RouteDefinition matched: " + route.getId()); } return route; }); } }
-
① 初始化 RoutePredicateFactory
private void initFactories(List<RoutePredicateFactory> predicates) { predicates.forEach(factory -> { String key = factory.name(); if (this.predicates.containsKey(key)) { this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: " + this.predicates.get(key) + ". It will be overwritten."); } this.predicates.put(key, factory); if (logger.isInfoEnabled()) { logger.info("Loaded RoutePredicateFactory [" + key + "]"); } }); }
-
② 初始化 gatewayFilterFactories
//FilterDefinition 转换成 GatewayFilter private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) { List<GatewayFilter> filters = new ArrayList<>(); // TODO: support option to apply defaults after route specific filters? //① 处理 GatewayProperties 中定义的默认的 FilterDefinition,转换成 GatewayFilter。 if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { filters.addAll(loadGatewayFilters(DEFAULT_FILTERS, this.gatewayProperties.getDefaultFilters())); } //② 将 RouteDefinition 中定义的 FilterDefinition 转换成 GatewayFilter。 if (!routeDefinition.getFilters().isEmpty()) { filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters())); } //③ 对 GatewayFilter 进行排序,排序的详细逻辑请查阅 spring 中的 Ordered 接口。 AnnotationAwareOrderComparator.sort(filters); return filters; }
-
③ 实现 RouteLocator 的 getRoutes()
方法 获取Route,真正转换的方法@Override public Flux<Route> getRoutes() { //调用 convertToRoute 方法将 RouteDefinition 转换成 Route。 return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute) // TODO: error handling .map(route -> { if (logger.isDebugEnabled()) { logger.debug("RouteDefinition matched: " + route.getId()); } return route; }); } private Route convertToRoute(RouteDefinition routeDefinition) { //3.1 将 PredicateDefinition 转换成 AsyncPredicate。 AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition); //3.2 将 FilterDefinition 转换成 GatewayFilter。 List<GatewayFilter> gatewayFilters = getFilters(routeDefinition); //3.3 根据 1 和 2 两步骤定义的变量生成 Route 对象。 return Route.async(routeDefinition).asyncPredicate(predicate) .replaceFilters(gatewayFilters).build(); }
-
3.1
将 PredicateDefinition 转换成 AsyncPredicate。private AsyncPredicate<ServerWebExchange> combinePredicates( RouteDefinition routeDefinition) { List<PredicateDefinition> predicates = routeDefinition.getPredicates(); //① 调用 lookup 方法,将列表中第一个 PredicateDefinition 转换成 AsyncPredicate。 AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0)); for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { //② 循环调用,将列表中每一个 PredicateDefinition 都转换成 AsyncPredicate。 AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate); //③ 应用and操作,将所有的 AsyncPredicate 组合成一个 AsyncPredicate 对象。 predicate = predicate.and(found); } return predicate; }
-
3.2
将 FilterDefinition 转换成 GatewayFilter。private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) { List<GatewayFilter> filters = new ArrayList<>(); // TODO: support option to apply defaults after route specific filters? //① 处理 GatewayProperties 中定义的默认的 FilterDefinition,转换成 GatewayFilter。 if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { filters.addAll(loadGatewayFilters(DEFAULT_FILTERS, this.gatewayProperties.getDefaultFilters())); } //② 将 RouteDefinition 中定义的 FilterDefinition 转换成 GatewayFilter。 if (!routeDefinition.getFilters().isEmpty()) { filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters())); } //③ 对 GatewayFilter 进行排序,排序的详细逻辑请查阅 spring 中的 Ordered 接口。 AnnotationAwareOrderComparator.sort(filters); return filters; }
-
3.3
根据 1 和 2 两步骤定义的变量生成 Route 对象。public Route build() { Assert.notNull(this.id, "id can not be null"); Assert.notNull(this.uri, "uri can not be null"); AsyncPredicate<ServerWebExchange> predicate = getPredicate(); Assert.notNull(predicate, "predicate can not be null"); return new Route(this.id, this.uri, this.order, predicate, this.gatewayFilters); }
-
到这里我们就已经知道外置配合是如何转换的,那么现在Route 已经组装完毕了,现在就是看一下Route里面的Predicate 和 Filter的实现,都比较简单
-
总结:
- 通过
RouteDefinitionLocator
的各种实现,来多样化的获取不同的外置配置。(properties、数据库、注册中心等) - 根据
RouteDefinitionRouteLocator
把这些外置配置转存成 Route。