springcloud-gateway 路由加载流程

问题

Spring Cloud Gateway版本是2.2.9.RELEASE,原本项目中依赖服务自动发现来自动配置路由到微服务的,但是发现将spring.cloud.gateway.discovery.locator.enabled=false 启动之后Gateway依然会将所有微服务自动注册到路由中,百思不得其解,遂调试代码观察期启动过曾,记录此文以供参考,官方文档
在这里插入图片描述
可以看到此处明确表示,可以通过设置该值来讲微服务中服务自动添加到配置中。按照这个线索,找到了
org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator#getRouteDefinitions
这个类在之前的文章includeExpression中有提及过,在构造函数中对成员变量

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {

	private final DiscoveryLocatorProperties properties;
	...
	private Flux<List<ServiceInstance>> serviceInstances;

	/**
	 * Kept for backwards compatibility. You should use the reactive discovery client.
	 * @param discoveryClient the blocking discovery client
	 * @param properties the configuration properties
	 * @deprecated kept for backwards compatibility
	 */
	@Deprecated
	public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient,
			DiscoveryLocatorProperties properties) {
		this(discoveryClient.getClass().getSimpleName(), properties);
		serviceInstances = Flux
				.defer(() -> Flux.fromIterable(discoveryClient.getServices()))
				.map(discoveryClient::getInstances)
				.subscribeOn(Schedulers.boundedElastic());
	}

	public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient,
			DiscoveryLocatorProperties properties) {
		this(discoveryClient.getClass().getSimpleName(), properties);
		serviceInstances = discoveryClient.getServices()
				.flatMap(service -> discoveryClient.getInstances(service).collectList());
	}
	...

这里会对serviceInstances进行初始化,从注册中心获取所有已注册的微服务实例进行填充,serviceInstance的获取结束了,在DiscoveryClientRouteDefinitionLocator还有一个重要的方法

#DiscoveryClientRouteDefinitionLocator
	@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;
			};
		}

		return serviceInstances.filter(instances -> !instances.isEmpty())
				.map(instances -> instances.get(0)).filter(includePredicate)
				.map(instance -> {
					RouteDefinition routeDefinition = buildRouteDefinition(urlExpr,
							instance);

					final ServiceInstance instanceForEval = new DelegatingServiceInstance(
							instance, properties);

					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);
					}

					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;
				});
	}

根据includeExpr 配置的sl表达式判断当前的serviceInstance是否服务转发要求。填充好predicate,filter之后返回routeDefinition,urlExpr的默认值是"lb://"+serverId,predicate匹配的path就等于serverId,有配置可以转换为小写spring.cloud.gateway.discovery.locator.lower-case-service-id=true因为eureka默认会uppercase serverId,filter中会StripPrefix=1,将路径中的serverId移除再转发给下游。路由定义的部分就这里了,他的刷新逻辑,依靠SpringRefreshContext 事件,具体的定义逻辑在org.springframework.cloud.gateway.config.GatewayAutoConfiguration

	@Bean
	@ConditionalOnClass(
			name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor")
	public RouteRefreshListener routeRefreshListener(
			ApplicationEventPublisher publisher) {
		return new RouteRefreshListener(publisher);
	}

RouteRefreshListener 实现了ApplicationListener<ApplicationEvent>,监控以下几个事件,并按照条件进行逻辑执行

  • ContextRefreshEvent 启动容器,或者beans重新加载
  • RefreshScopeRefreshdEvent 是 Spring Cloud 中的一个事件,当 @RefreshScope 注解的 beans 被重新加载时发布。这个事件主要用于 Spring Cloud 的配置刷新机制,当配置中心的配置信息发生变化时,会触发该事件以刷新 beans。
  • ParentHeartbeatEvent 是 Spring Cloud Bus 中的一个事件,用于在父上下文(通常是 Spring Cloud Config Server)发生心跳时发布。这个事件用于通知子上下文(如微服务)关于父上下文的健康状态。
  • HeartbeatEvent 是 Spring Cloud Bus 中的另一个事件,用于在服务实例之间广播心跳消息,以检测和维持各个服务的健康状态。
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ContextRefreshedEvent) {
			ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;
			if (!WebServerApplicationContext.hasServerNamespace(
					refreshedEvent.getApplicationContext(), "management")) {
				reset();
			}
		}
		else if (event instanceof RefreshScopeRefreshedEvent
				|| event instanceof InstanceRegisteredEvent) {
			reset();
		}
		else if (event instanceof ParentHeartbeatEvent) {
			ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
			resetIfNeeded(e.getValue());
		}
		else if (event instanceof HeartbeatEvent) {
			HeartbeatEvent e = (HeartbeatEvent) event;
			resetIfNeeded(e.getValue());
		}
	}

容器启动完成之后,该监听器接收到ContextRefreshedEvent ,会执行reset方法,reset方法主要功能是发送一个RefreshRoutesEvent 事件,定义如下

public class RefreshRoutesEvent extends ApplicationEvent {
	public RefreshRoutesEvent(Object source) {
		super(source);
	}
}

同样在AutoConfig中声明的CachingRouteLocator接收该参数,

tip:另外还有个CachingRouteDefinitionLocator也生命监听 RefreshRoutesEvent事件,但是默认配置下并不会产生任何逻辑

	@Bean
	@Primary
	@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
	// TODO: property to disable composite?
	public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
		return new CachingRouteLocator(
				new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
	}

event invoke方法如下

	public void onApplicationEvent(RefreshRoutesEvent event) {
		try {
			fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
					.materialize().collect(Collectors.toList()).subscribe(signals -> {
						applicationEventPublisher
								.publishEvent(new RefreshRoutesResultEvent(this));
						cache.put(CACHE_KEY, signals);
					}, throwable -> handleRefreshError(throwable)));
		}
		catch (Throwable e) {
			handleRefreshError(e);
		}
	}

CompositeRouteLocator的目的是混合 多个来源的Routers定义比如配置文件,接口,注册中心,通过@Bean形式定义的路由。可以理解成mixed route locator。
fetch方法就是获取所有RouteLocator的路由定义
private Flux<Route> fetch() { return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE); }
这里其实就是指定,之前org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator#getRouteDefinitions的方法也就是获取具体的路由定义,获取成功之后放入缓存,Fire RefreshRoutesResultEvent事件就解决了,开发人员可以通过 监听RefreshRoutesResultEvent 主动感知当前的路由变化。路由加载流程到此结束,

后语

我的问题是通过设置spring.cloud.gateway.discovery.locator.enabled依然没有办法让微服务注册失效,在整个流程中并没有看到使用该数值的地方,难道这是一个bug?转念一想DiscoveryClientRouteDefinitionLocator只需要阻止该Bean的注册即可取消掉从eureka获取服务实例的能力,通过引用关系可知

  • org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration.ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration
  • org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration.BlockingDiscoveryClientRouteDefinitionLocatorConfiguration

这里两处找到了其定义,一个是为reactive非阻塞io准备的,一个是为阻塞io准备的,根据源代码可知,具体执行提供那个Locator取决于spring.cloud.discovery.reactive.enabled,我的项目中是通过VM Options参数的形式传入的-Dspring.cloud.config.discovery.enabled=true,所以我在这两个方法处都打了断点。

		@Configuration(proxyBeanMethods = false)
	@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",
			matchIfMissing = true)
	public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {

		@Bean
		@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
		public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
				ReactiveDiscoveryClient discoveryClient,
				DiscoveryLocatorProperties properties) {
			return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
		}

	}

	@Configuration(proxyBeanMethods = false)
	@Deprecated
	@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",
			havingValue = "false")
	public static class BlockingDiscoveryClientRouteDefinitionLocatorConfiguration {

		@Bean
		@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
		public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
				DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
			return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
		}

	}

当我准备要执行调试模式想看看具体是哪个Bean被初始化的时候,恍然大悟看到了DiscoveryClientRouteDefinitionLocator的依赖,我自己在GatewayApplication 中手动注册了Bean

    @Bean
    public DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator(ReactiveDiscoveryClient discoveryClient,
            DiscoveryLocatorProperties discoveryLocatorProperties) {
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, discoveryLocatorProperties);
    }

真的是笑死,出这个问题的原因是因为最开始的代码使用的出c4o老师生成的,一开始并不明确该代码的作用,后面查看文档才知道用于微服务发现。已经不在关注这里了。

<think>好的,我现在需要为用户解析Spring Cloud Gateway中的RouteDefinitionRepository存储器。首先,我需要回忆一下Spring Cloud Gateway的基本架构,特别是路由存储的部分。 用户提到了RouteDefinitionRepository,这是负责存储路由定义的接口。我需要先理解这个接口的作用和它在整个路由机制中的位置。根据之前的了解,RouteDefinitionRepository用于加载和保存路由配置,可能涉及不同的实现类,比如内存存储或从配置中心读取。 接下来,我需要确定RouteDefinitionRepository的继承关系和主要方法。记得它可能有几个实现,比如InMemoryRouteDefinitionRepository,这个类将路由信息存储在内存中。另外,可能还有基于配置文件的实现,或者集成外部配置中心如Nacos、Consul的实现。 然后,我需要详细说明每个方法的功能。例如,save方法用于保存路由定义,delete方法用于删除,getRouteDefinitions返回所有路由的Flux流。这里需要解释反应式编程中的Flux,以及如何与Spring WebFlux结合使用。 还要考虑RouteDefinitionRepository如何与其他组件交互,比如RouteLocator,它可能使用Repository中的路由定义来创建可用的路由。需要说明整个流程,从加载路由配置到转换为Route对象的过程。 另外,用户可能对如何自定义RouteDefinitionRepository感兴趣,比如如何实现自己的存储逻辑,连接到数据库或远程配置服务。这部分需要给出一个简单的例子,说明如何通过实现接口来扩展功能。 需要注意源码中的关键点,比如事件发布机制。当路由变更时,是否会有RefreshRoutesEvent事件被触发,导致路由重新加载。这一点对于动态更新路由很重要,应该提及。 最后,总结RouteDefinitionRepository的作用,强调它在路由管理中的核心地位,以及不同实现方式的适用场景。确保解释清晰,结构合理,符合用户的需求。</think>Spring Cloud Gateway 中的 **`RouteDefinitionRepository`** 是路由定义(`RouteDefinition`)的核心存储器,负责路由配置的加载、存储与动态管理。以下从源码角度解析其设计逻辑与关键实现: --- ### 1. 接口定义 `RouteDefinitionRepository` 继承自 `ReactiveCrudRepository`,是一个反应式存储接口: ```java public interface RouteDefinitionRepository extends ReactiveCrudRepository<RouteDefinition, String> { Flux<RouteDefinition> getRouteDefinitions(); } ``` - **核心方法**: - `save()` / `delete()`:保存或删除路由定义(支持动态更新) - `getRouteDefinitions()`:返回所有路由的 `Flux` 流(反应式特性) --- ### 2. 默认实现:内存存储 **`InMemoryRouteDefinitionRepository`** 是默认实现,使用 `ConcurrentHashMap` 存储路由: ```java public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { private final Map<String, RouteDefinition> routes = synchronizedMap(new ConcurrentHashMap<>()); @Override public Flux<RouteDefinition> getRouteDefinitions() { return Flux.fromIterable(routes.values()); } @Override public Mono<Void> save(Mono<RouteDefinition> route) { return route.doOnNext(r -> routes.put(r.getId(), r)).then(); } @Override public Mono<Void> delete(Mono<String> routeId) { return routeId.doOnNext(id -> routes.remove(id)).then(); } } ``` - **特点**: - 路由存储在 JVM 内存中,重启后失效 - 适用于静态配置或开发环境 --- ### 3. 动态路由扩展 通过自定义 `RouteDefinitionRepository` 可实现动态路由: #### 示例:从数据库加载路由 ```java @Bean public RouteDefinitionRepository jdbcRouteRepository(DataSource dataSource) { return new RouteDefinitionRepository() { @Override public Flux<RouteDefinition> getRouteDefinitions() { // 从数据库查询并转换为 RouteDefinition return Flux.fromIterable(queryRoutesFromDB()); } @Override public Mono<Void> save(Mono<RouteDefinition> route) { // 保存到数据库 return route.doOnNext(r -> saveToDB(r)).then(); } @Override public Mono<Void> delete(Mono<String> routeId) { // 从数据库删除 return routeId.doOnNext(id -> deleteFromDB(id)).then(); } }; } ``` --- ### 4.路由定位器的协作 `RouteDefinitionRouteLocator` 负责将 `RouteDefinition` 转换为可执行的 `Route`: ```java public class RouteDefinitionRouteLocator implements RouteLocator { private final RouteDefinitionRepository routeDefinitionRepository; public Flux<Route> getRoutes() { return routeDefinitionRepository.getRouteDefinitions() .map(this::convertToRoute); } private Route convertToRoute(RouteDefinition routeDefinition) { // 转换为 Route 对象(包含 Predicate 和 Filter) } } ``` - **流程**:`RouteDefinitionRepository` → `RouteDefinition` → `Route` --- ### 5. 动态刷新机制 当路由变更时,通过发布 `RefreshRoutesEvent` 事件触发更新: ```java // 示例:手动触发刷新 applicationContext.publishEvent(new RefreshRoutesEvent(this)); ``` --- ### 总结 - **核心角色**:`RouteDefinitionRepository` 是路由配置的抽象存储层 - **扩展性**:可通过自定义实现对接 Nacos/Consul/DB 等外部配置源 - **动态能力**:结合事件机制,实现路由热更新 - **性能考量**:反应式设计避免阻塞,适合高并发场景 实际应用中,建议结合 `Spring Cloud Config` 或 `Nacos` 实现持久化动态路由管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值