Nacos Config Spring Boot Starter项目所提供的配置管理的功能均基于Nacos Spring Project,利用了Spring Boot的特性提供了更加方便快捷的分布式环境下的配置管理。仅需要通过在Maven中添加nacos-config-spring-boot-starter依赖,在application.properties配置nacos server相关属性即可完成远程配置文件的加载,同时结合在Nacos Spring Project中提供的相关注解,即可完成属性的动态刷新。从META-INF/spring.factories中我们可以得知nacos-config-spring-boot-autoconfigure主要自动配置了以下三个内容:
NacosConfigAutoConfiguration:负责完成Nacos配置管理相关功能的配置加载与初始化
NacosConfigEnvironmentProcessor:负责在应用程序启动前加载远程配置
NacosLoggingListener:负责处理Nacos日志相关模块
这里我们主要关注前两个与配置管理功能相关的模块,NacosLoggingListener实现相对简单,仅是在收到ApplicationEvent时重新加载NacosLoggingm模块。
@ConditionalOnProperty(name = NacosConfigConstants.ENABLED, matchIfMissing = true)
@ConditionalOnMissingBean(name = CONFIG_GLOBAL_NACOS_PROPERTIES_BEAN_NAME)
@EnableConfigurationProperties(value = NacosConfigProperties.class)
@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder")
@Import(value = { NacosConfigBootBeanDefinitionRegistrar.class })
@EnableNacosConfig
public class NacosConfigAutoConfiguration {
}
NacosConfigAutoConfiguration主要是为了初始化一个全局的远程配置,首先NacosConfigAutoConfiguration会根据配置属性nacos.config.enabled来决定是否要开启Nacos,如果已经定义了全局的Nacos配置也不会继续配置。
随后会开启NacosConfigProperties,定义了Nacos配置的基本配置属性,例如远端Server地址serverAddr、明明空间namespace等等。特别的是NacosConfigProperties里面定义了extConfig属性,用于在应用中加载多个配置属性。bootstrap属性定义了Nacos启动时的加载行为配置。这两点在后续的代码分析中都会陆续提到。
最后引入了NacosConfigBootBeanDefinitionRegistrar,在NacosConfigBootBeanDefinitionRegistrar中定义了NacosBootConfigurationPropertiesBinder,与Nacos Spring Project中的NacosConfigurationPropertiesBinder功能相似,都是用于处理@NacosConfigurationProperties注解,不同的地方在于处理属性绑定。在NacosConfigurationPropertiesBinder中,属性的解析仅依赖于注解本身配置的配置文件,而在NacosBootConfigurationPropertiesBinder中的做法是将注解配置的配置文件临时加入到默认的StandardEnvironment中(包含jvm属性与系统属性),结合Environment进行属性的注入,解析完成后再移除注入。
@Override
protected void doBind(Object bean, String beanName, String dataId, String groupId,
String configType, NacosConfigurationProperties properties, String content,
ConfigService configService) {
synchronized (this) {
String name = "nacos-bootstrap-" + beanName;
NacosPropertySource propertySource = new NacosPropertySource(name, dataId, groupId, content, configType);
environment.getPropertySources().addLast(propertySource);
Binder binder = Binder.get(environment);
ResolvableType type = getBeanType(bean, beanName);
Bindable<?> target = Bindable.of(type).withExistingValue(bean);
binder.bind(properties.prefix(), target);
publishBoundEvent(bean, beanName, dataId, groupId, properties, content, configService);
publishMetadataEvent(bean, beanName, dataId, groupId, properties);
environment.getPropertySources().remove(name);
}
}
在Nacos Config Spring Boot Starter中,配置被加入到Environment中有两个时间点,一个是在Environment初始化完成时,另一个则是在ApplicationContext初始化完成时。时间点由Bootstrap属性中的logEnable与Enable控制。当logEnable为true时,配置在Environment初始化完成时则会加入到Environment中,那么此时远程的所有配置文件都会参与到应用程序的初始化过程中。NacosConfigEnvironmentProcessor利用的EnvironmentPostProcessor接口的postProcessEnvironment,在之前的关于Environment的解析中我们提到过这个接口在Environment实例化后会被回调,此时的ApplicationContext是还未创建好的状态。在postProcessEnvironment主要完成了以下几件事,首先为SpringApplication加入一个初始化监听器NacosConfigApplicationContextInitializer,随后构建一个全局的NacosConfigProperties,最后如果Bootstrap开启了logEnable,则立即开始加载远程的配置文件。
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
application.addInitializers(new NacosConfigApplicationContextInitializer(this));
nacosConfigProperties = NacosConfigPropertiesUtils
.buildNacosConfigProperties(environment);
if (enable()) {
System.out.println(
"[Nacos Config Boot] : The preload log configuration is enabled");
loadConfig(environment);
}
}
loadConfig函数主要是利用NacosConfigLoader完成初始化属性的加载,与我们在Nacos Spring Project中看到的加载过程不同的是,由于此时ApplicationContext还未创建,所以无论是Service的创建还是Config Service Listener的监听器都被缓存起来,将会等到ApplicaitonContext创建好后发布。
在NacosConfigLoader的loadConfig函数中,我们可以比较清晰的看到配置文件的加载过程,首先是加载全局的配置文件,然后加载ext-config属性中的配置文件,随后根据配置来决定加入到Environment的顺序,详细的加载步骤与Nacos Spring Project的过程类似,这里不再展开。
public void loadConfig() {
Properties globalProperties = buildGlobalNacosProperties();
MutablePropertySources mutablePropertySources = environment.getPropertySources();
List<NacosPropertySource> sources = reqGlobalNacosConfig(globalProperties,
nacosConfigProperties.getType());
for (NacosConfigProperties.Config config : nacosConfigProperties.getExtConfig()) {
List<NacosPropertySource> elements = reqSubNacosConfig(config,
globalProperties, config.getType());
sources.addAll(elements);
}
if (nacosConfigProperties.isRemoteFirst()) {
for (ListIterator<NacosPropertySource> itr = sources.listIterator(sources.size()); itr.hasPrevious();) {
mutablePropertySources.addAfter(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, itr.previous());
}
} else {
for (NacosPropertySource propertySource : sources) {
mutablePropertySources.addLast(propertySource);
}
}
}
接下来我们来看一下负责完成SpringApplication初始化的NacosConfigApplicationContextInitializer,在initialize函数中首先会判断Bootstrap的enable属性,为true时才会进行后续的初始化工作。当NacosConfigEnvironmentProcessor已经完成过配置文件的加载后,NacosConfigApplicationContextInitializer要做的事情只有将前置创建的ConfigService正式发布,即注册到ApplicationContext中,并且完成监听器的注册。如果前置没有完成配置加载,那么由NacosConfigApplicationContextInitializer完成配置的加载以及配置监听器的初始化。
@Override
public void initialize(ConfigurableApplicationContext context) {
singleton.setApplicationContext(context);
environment = context.getEnvironment();
nacosConfigProperties = NacosConfigPropertiesUtils
.buildNacosConfigProperties(environment);
final NacosConfigLoader configLoader = new NacosConfigLoader(
nacosConfigProperties, environment, builder);
if (!enable()) {
logger.info("[Nacos Config Boot] : The preload configuration is not enabled");
}
else {
// If it opens the log level loading directly will cache
// DeferNacosPropertySource release
if (processor.enable()) {
processor.publishDeferService(context);
configLoader
.addListenerIfAutoRefreshed(processor.getDeferPropertySources());
}
else {
configLoader.loadConfig();
configLoader.addListenerIfAutoRefreshed();
}
}
final ConfigurableListableBeanFactory factory = context.getBeanFactory();
if (!factory
.containsSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME)) {
factory.registerSingleton(NacosBeanUtils.GLOBAL_NACOS_PROPERTIES_BEAN_NAME,
configLoader.buildGlobalNacosProperties());
}
}
这里我们来详细看一下延迟发布Service的流程,在Nacos Spring Project的分析中我们提到过ConfigService的注解实际上最终是由ConfigCreateWorker来负责创建的,创建的service会发布对应的Spring事件,而在NacosConfigEnvironmentProcessor中创建的service是原生的ConfigService,因为创建时ApplicaitonContext并未创建好,所以事件的发送并不能生效,在publishDeferService的函数中实际上则是将所有提前创建的Service重新创建。
public void publishDeferService(ApplicationContext context) throws NacosException {
setApplicationContext(context);
for (DeferServiceHolder holder : deferServiceCache) {
final Object o = holder.getHolder();
final Properties properties = holder.getProperties();
if (o instanceof ConfigService) {
ConfigService configService = (ConfigService) o;
createWorkerManager.get(ServiceType.CONFIG).run(properties,
configService);
}
else if (o instanceof NamingService) {
NamingService namingService = (NamingService) o;
createWorkerManager.get(ServiceType.NAMING).run(properties,
namingService);
}
else if (o instanceof NamingMaintainService) {
NamingMaintainService maintainService = (NamingMaintainService) o;
createWorkerManager.get(ServiceType.MAINTAIN).run(properties,
maintainService);
}
}
deferServiceCache.clear();
}