springCloud-gateway按照服务名动态路由的改造(二)
按照服务名动态路由
如果按照我们上面的理解,所有的路由在程序中都对应一个RouteDefenition对象,下面我们来看看gateway是如何根据服务名来生成对应的路由对象RouteDefenition的?
打开GatewayDiscoveryClientAutoConfiguration,根据我们的配置会生成一个DiscoveryClientRouteDefinitionLocator对象bean。
下面我们分析一下DiscoveryClientRouteDefinitionLocator的构造方法和getRouteDefinitions方法,看看是如何生成RouteDefinition的。
在构造方法中,如上图所示,调用了ReactiveDiscoveryClient.getSerivce方法,采用的是响应式编程,返回一个Flux<List<ServiceInstance>>流对象,流对象内部的元素为注册中心的服务实例。
下面我们再看getRouteDefinitions方法,
该方法会遍历所有服务实例,为每个服务生成一个路由。如上图所示
设置uri
在buildRouteDefinition中,new了一个RouteDefinition,并设置了路由的id属性为固定前缀+serviceId,并设置了uri属性为lb://serviceId,如下图所示:
protected RouteDefinition buildRouteDefinition(Expression urlExpr,
ServiceInstance serviceInstance) {
String serviceId = serviceInstance.getServiceId();
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(this.routeIdPrefix + serviceId);
String uri = urlExpr.getValue(this.evalCtxt, serviceInstance, String.class);
routeDefinition.setUri(URI.create(uri));
// add instance metadata
routeDefinition.setMetadata(new LinkedHashMap<>(serviceInstance.getMetadata()));
return routeDefinition;
}
设置断言Predicate
紧接着,遍历了配置类里的Predicate,为路由设置了断言,如下图所示:
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);
}
这里注意到断言是从this.properties.getPredicates()取得,但是我们没有在配置文件中配置断言,是不是意味着取得的值为空呢,其实不然,
继续打开GatewayDiscoveryClientAutoConfiguration,我们可以看到类里显式创建了一个DiscoveryLocatorProperties,并通过调用initPredicates设置了断言,调用initFilters方法设置了过滤器,如下图所示:
可以分析出使用的断言为PathRoutePredicateFactory,并设置了pattern为 /serviceId/**,跟我们在配置文件中配置成 -Path=xxx是一样的,在上面给routeDefenition设置断言时是会将serviceId替换成具体的服务名。至此,断言设置完成。
@Bean
public DiscoveryLocatorProperties discoveryLocatorProperties() {
DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
properties.setPredicates(initPredicates());
properties.setFilters(initFilters());
return properties;
}
public static List<PredicateDefinition> initPredicates() {
ArrayList<PredicateDefinition> definitions = new ArrayList<>();
// TODO: add a predicate that matches the url at /serviceId?
// add a predicate that matches the url at /serviceId/**
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class));
predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'");
definitions.add(predicate);
return definitions;
}
设置filter
设置filter的代码如下,也是从this.properties.getFilters()中取得。
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);
}
在上面我们分析断言时,看到同时通过initFilters方法给properties设置了filters属性,使用的是RewritePathGatewayFilterFactory,同时指定了重写的规则,即会把路径中的服务名去掉,然后再请求到下游服务。如下图所示:
public static List<FilterDefinition> initFilters() {
ArrayList<FilterDefinition> definitions = new ArrayList<>();
// add a filter that removes /serviceId by default
FilterDefinition filter = new FilterDefinition();
filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
String regex = "'/' + serviceId + '/(?<remaining>.*)'";
String replacement = "'/${remaining}'";
filter.addArg(REGEXP_KEY, regex);
filter.addArg(REPLACEMENT_KEY, replacement);
definitions.add(filter);
return definitions;
}
至此,filter设置完成。
我们可以通过gateway暴露的endpoint来查看所有的路由信息,如下图: