Nacos Config Spring Boot Starter源码分析

整体概述

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模块。

NacosConfigAutoConfiguration

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

NacosConfigEnvironmentProcessor

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

你可能感兴趣的:(spring,java,spring,boot)