Spring-Cloud-Gateway-08-路由的自动装配与加载流程



系列文章

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;

可以看到RouteRouteDefinition很相似,那么它们有什么关系,两者又是怎么联系到一起的?下面来看

既然有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的作用是汇聚不同源的路由信息RouteDefinitionRouteLocator可以直接自定义路由,还可以通过RouteDefinitionLocator获取所有的配置的RouteDefinition,最终转换成Route供调用方RoutePredicateHandlerMapping获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值