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*/@Deprecatedpublic 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@Overridepublic 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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/360797.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.8 HTTP协议结构

我们来看一下HTTP协议到底由哪些部分组成&#xff0c;也就是HTTP协议的结构。知道了这些知识才能在接口测试中游刃有余。 我们看上图&#xff0c;HTTP协议由四部分组成 起始行 描述请求和响应的基本信息。 当是请求时&#xff1a;请求方法是GET&#xff0c;调用的地址&#…

JAVA【案例5-2】模拟默认密码自动生成

【模拟默认密码自动生成】 1、案例描述 本案例要求编写一个程序&#xff0c;模拟默认密码的自动生成策略&#xff0c;手动输入用户名&#xff0c;根据用户名自动生成默认密码。在生成密码时&#xff0c;将用户名反转即为默认的密码。 2、案例目的 &#xff08;1&#xff09…

区块链技术与数字货币

1.起源 ➢中本聪(Satoshi Nakamoto), 2008 ➢比特币:一种点对点的电子现金系统 2.分布式账本技术原理 1.两个核心技术&#xff1a; ➢以链式区块组织账本数据实现账本数据的不可篡改 ➢分布式的可信记账机制 2.共识机制&#xff1a;由谁记账 ➢目的&#xff1a; ⚫ 解…

C语言基础——函数(2)

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 文章目录 前言 一、return语句 二、数组做函数参数 三、嵌套调用和链式访问 3.1 嵌套调用 3.2 链式访问 四、函数声明和定义 4.1 单个文件 4.2 多个文件 总结 前言 大家好啊&#xff0c;继我们上一…

django学习入门系列之第三点《案例 小米商城二级菜单》

文章目录 样例划分区域搭建骨架logo区域完整代码 小结往期回顾 样例 划分区域 搭建骨架 <!-- 二级菜单部分 --> <div class"sub-header"><div class"container"><div class"logo">1</div><div class"sea…

Python爬虫学习 | Scrapy框架详解

一.Scrapy框架简介 何为框架&#xff0c;就相当于一个封装了很多功能的结构体&#xff0c;它帮我们把主要的结构给搭建好了&#xff0c;我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据&#xff0c;提取数据的框架&#xff0c;我们熟知爬虫总共有四大部分&am…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(十七)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 27节&#xff09; P27《26.Stage模型-UIAbility的启动模式》 本节讲解 UIAbility的启动模式&#xff1a;Stage模型的应用&#x…

排序之插入排序----直接插入排序和希尔排序(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 排序之插入排序----直接插入排序和希尔排序(1) 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨…

算法训练营day20--235. 二叉搜索树的最近公共祖先+701.二叉搜索树中的插入操作 +450.删除二叉搜索树中的节点

一、235. 二叉搜索树的最近公共祖先 题目链接&#xff1a;https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/ 文章讲解&#xff1a;https://programmercarl.com/0235.%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E8%BF%91…

天马学航——智慧教务系统(移动端)开发日志三

天马学航——智慧教务系统(移动端)开发日志三 日志摘要&#xff1a;更新了学生选课模块、我的课程模块以及退课的功能&#xff0c;优化了后端数据库的缓存 1、学生选课模块 学生选课模块主要实现&#xff0c;学生根据需求进行选课操作&#xff0c;通过后端查询到所有教师的课…

一份简单的海外问卷,改变经济现状

在许多人看来&#xff0c;赚钱似乎总是与资金和技术密切相关。然而&#xff0c;即使没有丰富的资金和高超的技术&#xff0c;仍然有机会赚取可观的收入。 首先&#xff0c;需要明确的是&#xff0c;赚钱并非完全依赖于物质资本和技术能力。在这个充满机遇的时代&#xff0c;选…

深入源码设计!Vue3.js核心API——Computed实现原理

如果您觉得这篇文章有帮助的话&#xff01;给个点赞和评论支持下吧&#xff0c;感谢~ 作者&#xff1a;前端小王hs 阿里云社区博客专家/清华大学出版社签约作者/csdn百万访问前端博主/B站千粉前端up主 此篇文章是博主于2022年学习《Vue.js设计与实现》时的笔记整理而来 书籍&a…

谷歌云(GCP)4门1453元最热门证书限时免费考

谷歌云(GCP)最新活动&#xff0c;完成免费官方课程&#xff0c;送4门最热门考试免费考试券1张(每张价值200刀/1453元)&#xff0c;这4门也包括最近大热的AI/ML考试&#xff0c;非常值得学习和参加&#xff0c;活动7/17截止 谷歌云是全球最火的三大云计算厂商(前两名AWS, Azure…

投票多功能小程序(ThinkPHP+Uniapp+FastAdmin)

&#x1f389;你的决策小助手&#xff01; 支持图文投票、自定义选手报名内容、自定义主题色、礼物功能(高级授权)、弹幕功能(高级授权)、会员发布、支持数据库私有化部署&#xff0c;Uniapp提供全部无加密源码。​ 一、引言&#xff1a;为什么我们需要多功能投票小程序&#…

NSSCTF中的[WUSTCTF 2020]朴实无华、[FSCTF 2023]源码!启动! 、[LitCTF 2023]Flag点击就送! 以及相关知识点

目录 [WUSTCTF 2020]朴实无华 [FSCTF 2023]源码&#xff01;启动! [LitCTF 2023]Flag点击就送&#xff01; 相关知识点 1.intval 绕过 绕过的方式&#xff1a; 2.session伪造攻击 [WUSTCTF 2020]朴实无华 1.进入页面几乎没什么可用的信息&#xff0c;所以想到使用dis…

【c语言】二级指针

1&#xff0c;定义 本质还是从指针的角度去理解&#xff0c;只不过存的指针的值 2&#xff0c;使用方法

Android使用DevRing框架搭建数据库实体类以及使用

一、引用DevRing依赖 //导入DevRing依赖implementation com.ljy.ring:devring:1.1.8创建数据库表的依赖implementation org.greenrobot:greendao:3.2.2 // add libraryimplementation org.greenrobot:greendao-generator:3.0.0 二、修改工程目录下的.idea->gradle.xml文件&…

Golang笔记:使用serial包进行串口通讯

文章目录 目的使用入门总结 目的 串口是非常常用的一种电脑与设备交互的接口。这篇文章将介绍golang中相关功能的使用。 本文使用的包为 &#xff1a;go.bug.st/serial https://pkg.go.dev/go.bug.st/serial https://github.com/bugst/go-serial 另外还有一些常见的包如&…

Springboot民宿信息管理系统-计算机毕业设计源码08818

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对民宿信息管理系统等问题&#xff0c;对民宿…

大模型应用研发基础环境配置(Miniconda、Python、Jupyter Lab、Ollama等)

老牛同学之前使用的MacBook Pro电脑配置有点旧&#xff08;2015 年生产&#xff09;&#xff0c;跑大模型感觉有点吃力&#xff0c;操作起来有点卡顿&#xff0c;因此不得已捡起了尘封了快两年的MateBook Pro电脑&#xff08;老牛同学其实不太喜欢用 Windows 电脑做研发工作&am…