Nacos Config客户端与Spring Boot、Spring Cloud深度集成
从源码角度,解析Nacos Config客户端与Spring Boot、Spring Cloud的深度集成
原创博文,转载请注明来源
Nacos与Spring Boot集成
@NacosPropertySource和@NacosValue
@PropertySource的用法并不陌生,它是spring原生的注解,我们可以这么用:
@Configuration
@PropertySource(value = "classpath:demo.properties",ignoreResourceNotFound = false)
public class SpringPropertysourceApplication {
//...
}
意思是:把在classpath路径下,名为demo.properties的配置文件注入到spring容器中,这样,我们就可以直接在类的属性上通过@Value注解获取到demo.properties属性值了。
Nacos为了达到以上目的,提供了一个叫@NacosPropertySource的注解,和@PropertySource目的一样:把配置注入到spring容器;使用方式一样,用于任意被spring管理的类上。当然,Nacos提供了更高级的功能,比如Property变更,自动刷新的功能,下面来分析一下,Nacos是怎么集成的
com.alibaba.nacos.spring.core.env.NacosPropertySourcePostProcessor
这个类实现了org.springframework.beans.factory.config.BeanFactoryPostProcessor(Spring钩子,它在所有spring bean定义生成后,实例化之前调用,允许覆盖或添加其属性)等接口,主要作用是,扫描由spring所有的bean,查看其类上,是否有@NacosPropertySource注解,如果有的话,则生成com.alibaba.nacos.spring.core.env.NacosPropertySource实例对象(@NacosPropertySource注解标注了当前PropertySource指定的DataId,也就是一个完成的配置文件,生成实例其实就是调用Nacos原生API获取配置构造NacosPropertySource对象),再把实例添加到spring env.PropertySources中去,其实完成这几步,我们就可以通过使用@Value或者ENV.getProperty()这种方式获取到由Nacos管理的配置项了。
NacosPropertySourcePostProcessor代码片段:

上面的功能仅仅是把Nacos的配置注入到spring中,那动态刷新的功能怎么做的呢。
回顾一下,原生的Nacos sdk是怎么样监听配置变化的
ConfigService configService = NacosFactory.createConfigService(properties);
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("recieve:" + configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
可以看到,监听器与配置文件(DataId)是一对一的,所以,对于一个NacosPropertySource来说,应该有一个对应的监听器,在上诉NacosPropertySourcePostProcessor的代码片段截图中可以看到对应的代码:

这个添加listener的逻辑可以根据上面Nacos sdk的用法得出:

可以看到,在listener回调的逻辑里面,当有配置变更时会重新生成NacosPropertySource并替换掉ENV中过时的NacosPropertySource,完成这个部分的逻辑,我们通过ENV.getProperty()就可以动态获取到属性值了,但是通过@Value方式注入的到Bean对象的配置项,由于Bean已经生成,还是没办法动态更新,那Nacos是怎么做的?
com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor
上面说到如果通过ENV获取配置项的话,已经可以做到动态的目的了,但是如果此时持有配置项的Bean已经生成,则需要通过反射的机制,去动态更新了,从功能设计角度举个例子来讲清原理:
有类TestController,通过@Value的方式把demo.properties中app.config.threshold的配置项注入到属性threshold,那应该是这样的:
@RestController
@PropertySource(value = "classpath:demo.properties")
public class TestController {
@Value("${app.config.threshold}")
private String threshold;
}
那如果要通过反射设置属性的话那就需要这么一个映射关系:
app.config.threshold -> TestController.threshold
所以如果把这个映射关系保存在内存,当listener回调通知的时候,找到配置中的对应属性,反射设置进去就好了。
Nacos也是这么做的:
NacosValueAnnotationBeanPostProcessor实现了org.springframework.beans.factory.config.BeanPostProcessor(spring钩子函数,当bean对象实例化完成,注入容器之前调用 ),在其Object postProcessBeforeInitialization(Object bean, String beanName) 方法中,我们可以解析bean中所有注有@Value的注解,并将上诉映射关系,保存在内存中:

其中doWithFields实现如下:

其中有一点需要注意的地方,Nacos并没有使用原生的@Value注解去达到动态刷新的目的,因为违背了spring使用@Value的初衷,nacos自己实现了@NacosValue的注解
综上所诉,@NacosPropertySource和@NacosValue组合使用达到动态配置的效果是这样实现的:

@NacosValue
前面解析了@NacosPropertySource和@NacosValue组合使用达到动态配置原理,遗漏了一个细节点就是使用自定义注解@NacosValue是怎么在bean初始化的过程中注入属性的(前面说的动态刷新,是通过反射设置的,是建立在bean已经初始化完毕的基础上)。
还是com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor这个类,除了上面说的postProcessBeforeInitialization建立配置项和属性的映射关系这个方法外,还有两个方法,就是用于@NacosValue在bean初始过程中注入属性的:

简单理解一下这两个方法的目的:
doGetInjectedBean首先获取了@NacosValue中的配置项比如app.config.threshold,通过beanFactory解析出配置项对应的值(在ENV中),Member是一个队Field和Method的抽象类,如果Mem是Field则把取出的值进行转换和Field保持一致,如果是方法,则取出方法参数的Field进行转换
buildInjectedObjectCacheKey用于对doGetInjectedBean方法中已经转换过的值生一个cacheKey,这样就不用做多次转换的无用功
这两个方法都不是spring的钩子函数的方法,是在alibaba的spring-context-support包下,抽象类com.alibaba.spring.beans.factory.annotation.AnnotationInjectedBeanPostProcessor提供的,这个类的作用是:解析被子类泛型指定的注解(public class NacosValueAnnotationBeanPostProcessor extends ValueAnnotationBeanPostProcessor)标记的属性或方法(抽象成了Member),并注入由getInjectedObject返回的值,这个方法只做了一层缓存(buildInjectedObjectCacheKey),并调用由子类扩展的方法doGetInjectedBean,完成注入

AnnotationInjectedBeanPostProcessor这个类的实现,参照了org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor的实现,而这个类就是用于Spring原生注解@Autowired 和@Value在bean初始化过程中注入依赖的。
Nacos Config客户端与Spring Boot、Spring Cloud深度集成的更多相关文章
- 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚
新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...
- Java面试题(Spring Boot/Spring Cloud篇)
Spring Boot/Spring Cloud 104.什么是 spring boot? SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring ...
- spring Boot+spring Cloud实现微服务详细教程第二篇
上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...
- spring Boot+spring Cloud实现微服务详细教程第一篇
前些天项目组的大佬跟我聊,说项目组想从之前的架构上剥离出来公用的模块做微服务的开发,恰好去年的5/6月份在上家公司学习了国内开源的dubbo+zookeeper实现的微服务的架构.自己平时对微服务的设 ...
- Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台
Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台: https://gitee.com/leecho/cola-cloud
- spring boot、cloud v2.1.0.RELEASE 使用及技术整理
2018年10月30日 springboot v2.1.0.RELEASE 发布: https://github.com/spring-projects/spring-boot/releases/ta ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(七):集成 Druid 数据源
数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏 ...
- Spring Boot/Spring Cloud、ESB、Dubbo
如何使用Spring Boot/Spring Cloud 实现微服务应用spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现. ...
- 使用Spring Boot,Spring Cloud和Docker实现微服务架构
https://github.com/sqshq/PiggyMetrics Microservice Architecture with Spring Boot, Spring Cloud a ...
随机推荐
- PHP连接FTP服务的简单实现
PHP连接FTP服务: <?php class Ftp { private $connect; private $getback; /** * ftp连接信息 * @var array */ p ...
- linux从head.s到start_kernelstart_kernel之---内核重定位后分析
参考: https://biscuitos.github.io/blog/ARM-BOOT/ zImage 重定位之后实践 zImage 重定位之后,ARM 将 pc 指针指向了重定位 zImage ...
- PHP将mysql数据表转换为excel文件
测试代码: <?php $DB_Server = "127.0.0.1"; $DB_Username = "root"; $DB_Password = & ...
- POJ1426-Find The Multiple-bfs
Given a positive integer n, write a program to find out a nonzero multiple m of n whose decimal repr ...
- spring data jpa 使用方法命名规则查询
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写.框架在进行方法名解析时,会先把方法名多余的前缀 ...
- iOS 自己写的对话框中加入三个输入框
-(void)dialog:(NSString*)title okTitle:(NSString*)okTitle placeholder:(NSString*)placeholder finish: ...
- 修改el-input
无论是input框,还是select, 都是对 el-input 进行修改: .houseRegisterCompany .el-input{ width: 130px; }
- iview 分割面板效果(二)
源码地址:https://gitee.com/yolanda624/coffer/tree/master/src/components/a-split-panel
- go中整型的用法小结
示例 // 整型的用法小结 // 注意: // 整型变量在使用时,遵循保小不保大的原则 // 尽量使用占用空间小的数据类型 package main import ( "fmt" ...
- 【Tensorflow】slim.arg_scope()的使用
https://blog.csdn.net/u013921430/article/details/80915696