系列文章
Spring-Cloud-Gateway-01-网关基本概念
Spring-Cloud-Gateway-02-请求调用基本流程
Spring-Cloud-Gateway-03-网关自动装配
Spring-Cloud-Gateway-04-HttpWebHandlerAdapter到DispatcherHandler调用流程
Spring-Cloud-Gateway-05-请求到HttpWebHandlerAdapter的调用链路
Spring-Cloud-Gateway-06-DispatcherHandler调用解析
Spring-Cloud-Gateway-07-GatewayFilterChain的执行过程
Spring-Cloud-Gateway-08-路由的自动装配与加载流程
Spring-Cloud-Gateway-09-动态路由与自动刷新
之前在Spring-Cloud-Gateway-DispatcherHandler调用解析这一节中专门留了一个坑,在RoutePredicateHandlerMapping
中lookupRoute关于路由的方法说是后面在分析,这一节就专门来讲一下关于路由相关的。
lookupRoute方法
RoutePredicateHandlerMapping.java
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator.getRoutes()
.concatMap(route -> Mono.just(route).filterWhen(r -> {
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
return r.getPredicate().apply(exchange);
})
.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e))
.onErrorResume(e -> Mono.empty()))
.next()
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
}
可以看到通过this.routeLocator.getRoutes()方法得到全部的路由信息,然后通过Predicate
断言过滤得到满足条件的路由配置,最后通过next方法返回第一个。
那么this.routeLocator返回的是什么,是如何处理的?下面来看。
RouteDefinitionLocator
首先来看路由的信息都是存放在RouteDefinition
对象中
@Validated
public class RouteDefinition {
private String id;
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
@NotNull
private URI uri;
private Map<String, Object> metadata = new HashMap<>();
private int order = 0;
那么这个RouteDefinition
是从哪得到的呢,下面来看
public interface RouteDefinitionLocator {
Flux<RouteDefinition> getRouteDefinitions();
}
RouteDefinitionLocator
是路由的装载器,可以看到此接口中唯一的方法就是返回所有的RouteDefinition
目前一共有6个实现类,每个实现类的作用如下
CachingRouteDefinitionLocator
:为RouteDefinition提供缓存功能
CompositeRouteDefinitionLocator
:组合多种RouteDefinitionLocator的实现,为RouteDefinition提供统一入口
DiscoveryClientRouteDefinitionLocator
:从注册中心总加载RouteDefinition
InMemoryRouteDefinitionRepository
:从内存中读取RouteDefinition
PropertiesRouteDefinitionLocator
:从配置文件中读取RouteDefinition
RedisRouteDefinitionRepository
:从Redis中读取RouteDefinition
可以看到SpringCloudGateway
提供了多种方法读取路由,我们可以自行的扩展
那么这6种装配器是如何注入以及组合起来的?
PropertiesRouteDefinitionLocator
首先来看PropertiesRouteDefinitionLocator
,从配置文件中读取路由信息,是我们最熟悉的了
GatewayAutoConfiguration.java
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
可以看到入参是GatewayProperties
,也是在GatewayAutoConfiguration
进行注入的
GatewayAutoConfiguration.java
@Bean
public GatewayProperties gatewayProperties() {
return new GatewayProperties();
}
@ConfigurationProperties(GatewayProperties.PREFIX)
@Validated
public class GatewayProperties {
/**
* Properties prefix.
*/
public static final String PREFIX = "spring.cloud.gateway";
可以看到一个熟悉的东西spring.cloud.gateway
,就是在我们在properties或者yml文件中配置的网关信息
那么我们配置的路由信息是如何加载进来的呢?
注意类声明上面加了@ConfigurationProperties(GatewayProperties.PREFIX)
注解,可以猜想到就是通过该注解读取进来的
具体如何实现的可以关注ConfigurationPropertiesBindingPostProcessor
这个后置处理器,这里就不展开了
InMemoryRouteDefinitionRepository
接着来看InMemoryRouteDefinitionRepository
,该类实现了RouteDefinitionRepository
,而此类是
RouteDefinitionLocator
的子类
GatewayAutoConfiguration.java
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
在GatewayAutoConfiguration
直接注入的
InMemoryRouteDefinitionRepository.java
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(r -> {
if (ObjectUtils.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)));
});
}
其中save和delete方法分别是用来保存和删除的,具体的可以自己去了解一下
RedisRouteDefinitionRepository
RedisRouteDefinitionRepository
和InMemoryRouteDefinitionRepository类似,
也是同样实现了RouteDefinitionRepository
接口
GatewayRedisAutoConfiguration.java
@Bean
@ConditionalOnProperty(value = "spring.cloud.gateway.redis-route-definition-repository.enabled",
havingValue = "true")
@ConditionalOnClass(ReactiveRedisTemplate.class)
public RedisRouteDefinitionRepository redisRouteDefinitionRepository(
ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
return new RedisRouteDefinitionRepository(reactiveRedisTemplate);
}
可以看到需要添加相应的配置以及对应的jar包,同样包含save和delete方法,具体的可以自己去了解一下
DiscoveryClientRouteDefinitionLocator
DiscoveryClientRouteDefinitionLocator
与注册中心相关的
ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration.java
@Bean
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
可以看到需要添加相应的配置,具体的可以自己去了解一下
CachingRouteDefinitionLocator
CachingRouteDefinitionLocator
目前好像还没看到有地方用,可能我这版本不是最高的
CompositeRouteDefinitionLocator
来看最后一个CompositeRouteDefinitionLocator
GatewayAutoConfiguration.java
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}
@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
可以看到入参是RouteDefinitionLocator
的集合,就是得到所有的RouteDefinitionLocator
的实现bean,也就是上面所说的几种途径,通过getRouteDefinitions方法将所有的RouteDefinition组合起来统一返回
RouteLocator
public class Route implements Ordered {
private final String id;
private final URI uri;
private final int order;
private final AsyncPredicate<ServerWebExchange> predicate;
private final List<GatewayFilter> gatewayFilters;
private final Map<String, Object> metadata;
可以看到Route和RouteDefinition很相似,那么它们有什么关系,两者又是怎么联系到一起的?下面来看
既然有RouteDefinitionLocator,那么应该也有RouteLocator
public interface RouteLocator {
Flux<Route> getRoutes();
}
唯一的方法就是返回所有的Route
实现类总共有三种
通过名字可以猜想到,CachingRouteLocator
是用作缓存的,CompositeRouteLocator
用来组合所有RouteLocator,RouteDefinitionRouteLocator
应该和Route、RouteDefinition之间有关系
下面来看它们是如何进行装配的
RouteDefinitionRouteLocator
GatewayAutoConfiguration.java
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
configurationService);
}
可以看到入参很多,其中包括GatewayProperties、过滤器、断言Predicate、RouteDefinitionLocator、ConfigurationService
这里我们主要关注RouteDefinitionLocator,这里注入的就是我们上面所说的CompositeRouteDefinitionLocator
里面组合着各种渠道的RouteDefinition
接着来看RouteDefinitionRouteLocator的getRoutes方法是如何处理的
@Override
public Flux<Route> getRoutes() {
//this.routeDefinitionLocator就是构造参数传入的CompositeRouteDefinitionLocator
Flux<Route> routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute);
if (!gatewayProperties.isFailOnRouteDefinitionError()) {
// instead of letting error bubble up, continue
routes = routes.onErrorContinue((error, obj) -> {
if (logger.isWarnEnabled()) {
logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
+ " will be ignored. Definition has invalid configs, " + error.getMessage());
}
});
}
return routes.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
到这里可以看到通过getRouteDefinitions方法得到所有的RouteDefinition,然后调用convertToRoute方法将RouteDefinition转换成Route
RouteDefinition和Route的关系到这就可以解释一下
Route是RouteDefinition的定义结果,正常情况下Route是会经常变动的(这也是为什么通过Event驱动其刷新机制),RouteDefinition主要用于匹配读取初始化Route的,一次性概念,往往仅在程序启动做初始化加载时候使用(位于GatewayProperties类中)。
CachingRouteLocator和CompositeRouteLocator
为什么将这两种放在一起,因为CachingRouteLocator在CompositeRouteLocator基础上做了一层包装
GatewayAutoConfiguration.java
@Bean
@Primary
@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
// TODO: property to disable composite?
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}
可以看到入参是RouteLocator的集合,得到所有RouteLocator的实现bean,并且包装了层CachingRouteLocator
CachingRouteLocator.java
public CachingRouteLocator(RouteLocator delegate) {
this.delegate = delegate;
routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch);
}
private Flux<Route> fetch() {
return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
}
通过委托CompositeRouteLocator得到所有的Route
来看CachingRouteLocator的类声明
public class CachingRouteLocator
implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {
可以看到涉及事件监听以及发布,因为Route是经常变化的,所以需要通过事件来支持动态路由
这里涉及到的知识另外开一节来说,这里就不详细展开了
RoutePredicateHandlerMapping
回到最初的问题,RoutePredicateHandlerMapping中this.routeLocator返回的是什么?
GatewayAutoConfiguration.java
@Bean
@ConditionalOnMissingBean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
}
可以看到其中之一的入参就是RouteLocator,可以找到注入的有两个RouteLocator,一个是routeDefinitionRouteLocator,另一个是cachedCompositeRouteLocator,都是上面说过的
由于@Primary注解所以最后得到的cachedCompositeRouteLocator这个bean,也就是返回CachingRouteLocator
现在知道了RoutePredicateHandlerMapping中this.routeLocator返回的就是CachingRouteLocator
CachingRouteLocator.java
@Override
public Flux<Route> getRoutes() {
return this.routes;
}
通过getRoutes方法返回所有的Route,也就是缓存中拿到的
CachingRouteLocator.java
private final Map<String, List> cache = new ConcurrentHashMap<>();
最后再来看下断言Predicate是如何加载进来的
前面提到过了在注入RouteDefinitionRouteLocator的时候
GatewayAutoConfiguration.java
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties,
configurationService);
}
通过断点调试可以看到默认是有14个Predicate来进行匹配的
我们可以通过继承AbstractRoutePredicateFactory
来自定义断言,具体的可以自己去实现一下
总结
可以发现,RouteDefinitionLocator
的作用是汇聚不同源的路由信息RouteDefinition
,RouteLocator
可以直接自定义路由,还可以通过RouteDefinitionLocator
获取所有的配置的RouteDefinition
,最终转换成Route
供调用方RoutePredicateHandlerMapping
获取。