摘要: 原创出处 http://www.iocoder.cn/Spring-Cloud-Gateway/route-definition-locator-discover-client/ 「芋道源码」欢迎转载,保留摘要,谢谢!
本文主要基于 Spring-Cloud-Gateway 2.0.X M4
阅读源码最好的方式,是使用 IDEA 进行调试 Spring Cloud Gateway 源码,不然会一脸懵逼。
1. 概述
本文主要对 DiscoveryClientRouteDefinitionLocator 的源码实现。
DiscoveryClientRouteDefinitionLocator 通过调用 org.springframework.cloud.client.discovery.DiscoveryClient 获取注册在注册中心的服务列表,生成对应的 RouteDefinition 数组。
推荐 Spring Cloud 书籍:
请支持正版。下载盗版,等于主动编写低级 BUG 。
程序猿DD —— 《Spring Cloud微服务实战》
两书齐买,京东包邮。
2. 环境搭建
在解析源码之前,我们先以 Eureka 为注册中心,讲解下如何配置 DiscoveryClientRouteDefinitionLocator 。
第一步,以 spring-cloud-gateway-sample 项目为基础,在 pom.xml 文件添加依赖库。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>2.0.0.M1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>注意,需要排除
spring-boot-starter-web的依赖,避免和 Spring Cloud Gateway 依赖的spring-boot-starter-webflux冲突。
第二步,在 application.yml 添加 Eureka 相关配置 。
spring:
application:
name: juejin-gateway
eureka:
instance:
leaseRenewalIntervalInSeconds: 10
leaseExpirationDurationInSeconds: 30
client:
serviceUrl:
defaultZone: http://eureka.didispace.com/eureka/如果你**"懒"**的启动 Eureka ,推荐使用 《程序猿DD - 公益 - EUREKA SERVER》 。感谢 1024 。
第三步,在 org.springframework.cloud.gateway.sample.GatewaySampleApplication 类里,添加 RouteDefinitionLocator Bean 配置。
@EnableDiscoveryClient // {@link DiscoveryClientRouteDefinitionLocator}
public class GatewaySampleApplication {
// ... 省略其他代码
@Bean
public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient);
}
}第四步,启动一个注册在 Eureka 的应用服务。机智如你,这里我就不啰嗦落。
第五步,在 DiscoveryClientRouteDefinitionLocator 的 #getRouteDefinitions() 方法打断点,调试启动 spring-cloud-gateway-sample 。小功告成。撒花~
3. DiscoveryClientRouteDefinitionLocator
org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator ,通过调用 DiscoveryClient 获取注册在注册中心的服务列表,生成对应的 RouteDefinition 数组。
代码如下 :
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
private final DiscoveryClient discoveryClient;
private final String routeIdPrefix;
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(discoveryClient.getServices())
.map(serviceId -> {
RouteDefinition routeDefinition = new RouteDefinition();
// 设置 ID
routeDefinition.setId(this.routeIdPrefix + serviceId);
// 设置 URI
routeDefinition.setUri(URI.create("lb://" + serviceId));
// add a predicate that matches the url at /serviceId
/*PredicateDefinition barePredicate = new PredicateDefinition();
barePredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class));
barePredicate.addArg(PATTERN_KEY, "/" + serviceId);
routeDefinition.getPredicates().add(barePredicate);*/
// 添加 Path 匹配断言
// add a predicate that matches the url at /serviceId/**
PredicateDefinition subPredicate = new PredicateDefinition();
subPredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class));
subPredicate.addArg(PATTERN_KEY, "/" + serviceId + "/**");
routeDefinition.getPredicates().add(subPredicate);
//TODO: support for other default predicates
// 添加 Path 重写过滤器
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterName(RewritePathGatewayFilterFactory.class));
String regex = "/" + serviceId + "/(?<remaining>.*)";
String replacement = "/${remaining}";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
routeDefinition.getFilters().add(filter);
//TODO: support for default filters
return routeDefinition;
});
}
}discoveryClient属性,注册发现客户端,用于向注册中心发起请求。routeIdPrefix属性,路由配置编号前缀,以 DiscoveryClient 类名 +_。第 13 行 :调用
discoveryClient获取注册在注册中心的服务列表。第 14 行 :遍历服务列表,生成对应的 RouteDefinition 数组。
第 16 行 :设置
RouteDefinition.id。第 18 行 :设置
RouteDefinition.uri,格式为lb://${serviceId}。在 LoadBalancerClientFilter 会根据lb://前缀过滤处理,负载均衡,选择最终调用的服务地址,在 《Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.4) 之 LoadBalancerClientFilter 负载均衡》 详细解析。第 27 至 32 行 :使用 PathRoutePredicateFactory 创建 Path 匹配断言。
例如服务的
serviceId = spring.application.name = juejin-sample,通过网关http://${gateway}/${serviceId}/some_api访问服务http://some_api。PathRoutePredicateFactory 在 《Spring-Cloud-Gateway 源码解析 —— 处理器 (3.1) 之 RoutePredicateFactory 路由谓语工厂》「10. PathRoutePredicateFactory」 有详细解析。
第 36 至 44 行 :使用 RewritePathGatewayFilterFactory 创建重写网关过滤器,用于移除请求路径里的
/${serviceId}。如果不移除,最终请求不到服务。RewritePathGatewayFilterFactory 在 《Spring-Cloud-Gateway 源码解析 —— 过滤器 (4.2) 之 GatewayFilterFactory 过滤器工厂》「4.1 RewritePathGatewayFilterFactory」 有详细解析。第 48 行 :返回路由定义 RouteDefinition 。举个 RouteDefinition 例子 :

4.

本小节建议阅读完 《Spring-Cloud-Gateway 源码解析 —— 路由(2.1)之 RouteLocator 一览》 再理解。
RoutePredicateHandlerMapping 使用 CachingRouteLocator 来获取 Route 信息。在 Spring Cloud Gateway 启动后,如果有新加入的服务,则需要刷新 CachingRouteLocator 缓存。
这里有一点需要注意下 :新加入的服务,指的是新的 serviceId ,而不是原有服务新增的实例。
个人建议,写一个定时任务,间隔调用 DiscoveryClient 获取服务列表,若发现变化,刷新 CachingRouteLocator 缓存。