Hello,我是大都督周瑜,本文给大家分析一下@ConfigurationProperties结合Nacos配置动态刷新的底层原理,记得点赞、关注、分享哦!
公众号:IT周瑜
应用背景
假如在Nacos中有Data ID为common.yml的配置项:
model:
name: gpt-4
在应用的application.yml中进行导入:
spring:
config:
import: optional:nacos:common.yml
对应Properties类为ModelProperties:
@Data
@Component
@ConfigurationProperties(prefix = "model")
public class ModelProperties {
private String name;
}
在ZhouyuService中使用ModelProperties:
@Service
public class ZhouyuService {
@Resource
private ModelProperties modelProperties;
public String test() {
return modelProperties.getName();
}
}
直接在Nacos中进行配置修改,ZhouyuService都能及时获取到最新的配置,注意这里的用法为modelProperties.getName()。
原理分析
先在ZhouyuService中进行Debug,查看配置更新前后ModelProperties对象是否是同一个对象。
配置更新前:

配置更新后:

对象是同一个,但属性值发生了变化,所以底层原理应该是:Nacos客户端监听到配置发生了变化之后,会找到ModelProperties对象,然后调用name的set方法进行属性值的更新。
启动过程的初始化
首先,在Spring Cloud中,定义了一个ConfigurationPropertiesBeansBean对象,它有两个功能:
- 首先,它是一个
BeanPostProcessor - 其次,它里面有一个
Map<String, ConfigurationPropertiesBean>类型的beans属性

作为BeanPostProcessor,在它的初始化前方法中,会对Spring容器中的每个Bean对象进行判断,会过滤出那些加了@ConfigurationProperties注解的Bean,在本文中,指的就是ModelProperties对象,找到ModelProperties对象后,会把它包装为一个ConfigurationPropertiesBean对象,并存在beans这个Map中,后续配置发生变化时,会从beans中取出ModelProperties对象并进行属性值的更新。
另外,Nacos的自动配置类NacosConfigAutoConfiguration中提供了一个NacosContextRefresher的Bean对象,它是一个ApplicationListener,它监听了ApplicationReadyEvent事件:

在Spring Boot启动过程的最后,Spring Boot会发布ApplicationReadyEvent事件,从而触发NacosContextRefresher的事件处理逻辑,NacosContextRefresher接收到ApplicationReadyEvent事件后,会向Naocs客户端的ConfigService中注册一个Nacos配置监听器,用来监听Naocs服务端配置的改变。

因此在Spring Boot启动过程中,核心会做两件事:
- 找到加了
@ConfigurationProperties注解的Bean,并存下来 - 注册一个Nacos的配置监听器
配置发生变化时
一旦Nacos服务端的配置发生了变化,就会触发执行Nacos客户端的配置监听器,配置监听器会利用Spring容器发布一个RefreshEvent事件,该事件是Spring Cloud定义的。
Spring Cloud中定义了一个RefreshEventListener,就是用来处理RefreshEvent事件的:

而它的核心逻辑是更新Spring容器的Environment对象:

我们可以把Environment对象理解为Nacos服务端的配置项在客户端的本地缓存,因此Nacos客户端一旦发现服务端配置发生了改变,就会发布RefreshEvent事件,从而将Environment对象中的缓存的配置项更新为新值。
同时,在更新完Environment对象后,会再次利用Spring容器发布一个EnvironmentChangeEvent事件。
在Spring Cloud中,还定义了一个ConfigurationPropertiesRebinderBean对象:

它会处理EnvironmentChangeEvent事件,它会用到前面提到的ConfigurationPropertiesBeans对象,遍历它的Map中所存的那些加了@ConfigurationProperties注解的Bean:

比如ModelProperties对象,并针对每个Bean进行rebind()操作。
所谓rebind()操作,其实就是先从容器中获取到指定的Bean对象,也就是加了@ConfigurationProperties注解的Bean对象,先进行Bean销毁,再进行Bean初始化:

而Bean的初始化过程中,会执行到ConfigurationPropertiesBindingPostProcessor中的初始化前方法,会对ModelProperties对象重新进行bind:

而bind的过程,就是利用Environment对象中的值更新ModelProperties对象中的属性,从而完成配置的刷新,这块细节暂时就不分析了。
总结
当Nacos服务端的配置发生改变时,会触发Nacos客户端的配置监听器,从而发布RefreshEvent事件,从而更新Environment对象,从而发布EnvironmentChangeEvent事件,从而利用最新的Environment对象更新ModelProperties对象中的属性。



















