nacos配置中心 服务启动的配置加载

2022-10-25 17:19:18 浏览数 (2)

nacos配置中心 服务启动的配置加载

代码语言:javascript复制
@SpringBootApplication
public class SpringCloudNacosConfigApplication {

   public static void main(String[] args) {
      ConfigurableApplicationContext context=
            SpringApplication.run(SpringCloudNacosConfigApplication.class, args);
      String info=context.getEnvironment().getProperty("info");
      System.out.println(info);
   }
}

Spring抽象了一个environment表示Spring应用程序环境配置,整合了各种各样的外部环境,并提供统一访问的方法getProperty()

spring启动时候,会把配置加载到Environment中,当创建一个Bean时可以从Environment中把一些属性值通过@Value的形式注入到业务代码中。

springcloud要实现统一配置管理并动态刷新配置需要解决两个问题

  • 如何将远程服务器上的配置加载到Environment
  • 配置变更时,如何将新的配置更新到Environment中,保证配置变更时可以进行属性值的动态刷新。

spring boot 启动 调用SpringApplication.run()

代码语言:javascript复制
public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   ConfigurableApplicationContext context = null;
   Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting();
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(
            args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners,
            applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);
      prepareContext(context, environment, listeners, applicationArguments,
            printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass)
               .logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

prepareEnvironment 环境准备

prepareEnvironment中调用listeners.environmentPrepared方法,发布一个ApplicationEnvironmentPreparedEvent事件,所有对这个事件感兴趣的Listener都会监听到该事件。

代码语言:javascript复制
private ConfigurableEnvironment prepareEnvironment(
      SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments) {
   // Create and configure the environment
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   listeners.environmentPrepared(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader())
            .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

进入listeners.environmentPrepared方法

代码语言:javascript复制
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
   this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
         this.application, this.args, environment));
}

BootstrapApplicationListener实现了ApplicationListener<ApplicationEnvironmentPreparedEvent> 会收到该事件并进行处理

代码语言:javascript复制
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
    ConfigurableEnvironment environment = event.getEnvironment();
    if ((Boolean)environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class, true)) {
        if (!environment.getPropertySources().contains("bootstrap")) {
            ConfigurableApplicationContext context = null;
            String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
            Iterator var5 = event.getSpringApplication().getInitializers().iterator();

            while(var5.hasNext()) {
                ApplicationContextInitializer<?> initializer = (ApplicationContextInitializer)var5.next();
                if (initializer instanceof ParentContextApplicationContextInitializer) {
                    context = this.findBootstrapContext((ParentContextApplicationContextInitializer)initializer, configName);
                }
            }

            if (context == null) {
                context = this.bootstrapServiceContext(environment, event.getSpringApplication(), configName);
                event.getSpringApplication().addListeners(new ApplicationListener[]{new BootstrapApplicationListener.CloseContextOnFailureApplicationListener(context)});
            }

            this.apply(context, event.getSpringApplication(), environment);
        }
    }
}

进入bootstrapServiceContext方法:

代码语言:javascript复制
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment environment, final SpringApplication application, String configName) {
    StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
    MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources();
    Iterator var6 = bootstrapProperties.iterator();

    while(var6.hasNext()) {
        PropertySource<?> source = (PropertySource)var6.next();
        bootstrapProperties.remove(source.getName());
    }

    String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}");
    Map<String, Object> bootstrapMap = new HashMap();
    bootstrapMap.put("spring.config.name", configName);
    bootstrapMap.put("spring.main.web-application-type", "none");
    if (StringUtils.hasText(configLocation)) {
        bootstrapMap.put("spring.config.location", configLocation);
    }

    bootstrapProperties.addFirst(new MapPropertySource("bootstrap", bootstrapMap));
    Iterator var8 = environment.getPropertySources().iterator();

    while(var8.hasNext()) {
        PropertySource<?> source = (PropertySource)var8.next();
        if (!(source instanceof StubPropertySource)) {
            bootstrapProperties.addLast(source);
        }
    }

    SpringApplicationBuilder builder = (new SpringApplicationBuilder(new Class[0])).profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF).environment(bootstrapEnvironment).registerShutdownHook(false).logStartupInfo(false).web(WebApplicationType.NONE);
    SpringApplication builderApplication = builder.application();
    if (builderApplication.getMainApplicationClass() == null) {
        builder.main(application.getMainApplicationClass());
    }

    if (environment.getPropertySources().contains("refreshArgs")) {
        builderApplication.setListeners(this.filterListeners(builderApplication.getListeners()));
    }

    builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});
    ConfigurableApplicationContext context = builder.run(new String[0]);
    context.setId("bootstrap");
    this.addAncestorInitializer(application, context);
    bootstrapProperties.remove("bootstrap");
    this.mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
    return context;
}

builder.sources(new Class[]{BootstrapImportSelectorConfiguration.class});是关键

代码语言:javascript复制
@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

BootstrapImportSelectorConfiguration是一个配置类,该配置类用@Import导入BootstrapImportSelector来实现自动装配

代码语言:javascript复制
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
   // Use names and ensure unique to protect against duplicates
   List<String> names = new ArrayList<>(SpringFactoriesLoader
         .loadFactoryNames(BootstrapConfiguration.class, classLoader));
   names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
         this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));

   List<OrderedAnnotatedElement> elements = new ArrayList<>();
   for (String name : names) {
      try {
         elements.add(
               new OrderedAnnotatedElement(this.metadataReaderFactory, name));
      }
      catch (IOException e) {
         continue;
      }
   }
   AnnotationAwareOrderComparator.sort(elements);

   String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);

   return classNames;
}

BootstrapImportSelector的selectImports方法中,利用Spring的SPI机制,可到classpath路径下查找META-INF/spring.factories预定义的一些扩展点,其中key就是BootstrapConfiguration。

分别在

spring-cloud-context.jar

代码语言:javascript复制
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,
org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,
org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration

spring-cloud-alibaba-nacos-config.jar

代码语言:javascript复制
org.springframework.cloud.bootstrap.BootstrapConfiguration=
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration

其中重要的两个类是PropertySourceBootstrapConfiguration NacosConfigBootstrapConfiguration

prepareEnvironment 环境准备到此结束。

prepareContext

开始刷新应用上下文的准备阶段,

代码语言:javascript复制
private void prepareContext(ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   applyInitializers(context);
   listeners.contextPrepared(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // Add boot specific singleton beans
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
   listeners.contextLoaded(context);
}

接着调用applyInitializers方法:

代码语言:javascript复制
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
   for (ApplicationContextInitializer initializer : getInitializers()) {
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
            initializer.getClass(), ApplicationContextInitializer.class);
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
   }
}

PropertySourceBootstrapConfiguration实现了ApplicationContextInitializer接口,所以initializer.initialize(context);调用PropertySourceBootstrapConfiguration中的initialize方法

代码语言:javascript复制
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
   CompositePropertySource composite = new CompositePropertySource(
         BOOTSTRAP_PROPERTY_SOURCE_NAME);
   AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
   boolean empty = true;
   ConfigurableEnvironment environment = applicationContext.getEnvironment();
   for (PropertySourceLocator locator : this.propertySourceLocators) {
      PropertySource<?> source = null;
      source = locator.locate(environment);
      if (source == null) {
         continue;
      }
      logger.info("Located property source: "   source);
      composite.addPropertySource(source);
      empty = false;
   }
   if (!empty) {
      MutablePropertySources propertySources = environment.getPropertySources();
      String logConfig = environment.resolvePlaceholders("${logging.config:}");
      LogFile logFile = LogFile.get(environment);
      if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
         propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
      }
      insertPropertySources(propertySources, composite);
      reinitializeLoggingSystem(environment, logConfig, logFile);
      setLogLevels(applicationContext, environment);
      handleIncludedProfiles(environment);
   }
}

然后执行source = locator.locate(environment);

PropertySourceLocator接口的主要作用是实现外部化配置可动态加载

NacosPropertySourceLocator实现了locate方法,把存放在服务端中的配置信息读取出来,然后把结果存到CompositePropertySource中

NacosPropertySourceLocator的locate方法是核心方法,作用是

  • 初始化ConfigService对象,这是Nacos客户端提供的用于访问实现配置中心基本操作的类
  • 按顺序加载共享配置,扩展配置,应用名称对应的配置。
代码语言:javascript复制
@Override
public PropertySource<?> locate(Environment env) {

   ConfigService configService = nacosConfigProperties.configServiceInstance();

   if (null == configService) {
      log.warn("no instance of config service found, can't load config from nacos");
      return null;
   }
   long timeout = nacosConfigProperties.getTimeout();
   nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
         timeout);
   String name = nacosConfigProperties.getName();

   String dataIdPrefix = nacosConfigProperties.getPrefix();
   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = name;
   }

   if (StringUtils.isEmpty(dataIdPrefix)) {
      dataIdPrefix = env.getProperty("spring.application.name");
   }

   CompositePropertySource composite = new CompositePropertySource(
         NACOS_PROPERTY_SOURCE_NAME);

   loadSharedConfiguration(composite);
   loadExtConfiguration(composite);
   loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);

   return composite;
}

进入loadApplicationConfiguration方法

代码语言:javascript复制
private void loadApplicationConfiguration(
      CompositePropertySource compositePropertySource, String dataIdPrefix,
      NacosConfigProperties properties, Environment environment) {

   String fileExtension = properties.getFileExtension();
   String nacosGroup = properties.getGroup();

   // load directly once by default
   loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
         fileExtension, true);
   // load with suffix, which have a higher priority than the default
   loadNacosDataIfPresent(compositePropertySource,
         dataIdPrefix   DOT   fileExtension, nacosGroup, fileExtension, true);
   // Loaded with profile, which have a higher priority than the suffix
   for (String profile : environment.getActiveProfiles()) {
      String dataId = dataIdPrefix   SEP1   profile   DOT   fileExtension;
      loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
            fileExtension, true);
   }
}

进入loadNacosDataIfPresent方法:

代码语言:javascript复制
private void loadNacosDataIfPresent(final CompositePropertySource composite,
      final String dataId, final String group, String fileExtension,
      boolean isRefreshable) {
   if (null == dataId || dataId.trim().length() < 1) {
      return;
   }
   if (null == group || group.trim().length() < 1) {
      return;
   }
   NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
         fileExtension, isRefreshable);
   this.addFirstPropertySource(composite, propertySource, false);
}

进入loadNacosPropertySource方法

代码语言:javascript复制
private NacosPropertySource loadNacosPropertySource(final String dataId,
      final String group, String fileExtension, boolean isRefreshable) {
   if (NacosContextRefresher.getRefreshCount() != 0) {
      if (!isRefreshable) {
         return NacosPropertySourceRepository.getNacosPropertySource(dataId);
      }
   }
   return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
         isRefreshable);
}

进入nacosPropertySourceBuilder.build方法

代码语言:javascript复制
NacosPropertySource build(String dataId, String group, String fileExtension,
      boolean isRefreshable) {
   Properties p = loadNacosData(dataId, group, fileExtension);
   NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
         propertiesToMap(p), new Date(), isRefreshable);
   NacosPropertySourceRepository.collectNacosPropertySources(nacosPropertySource);
   return nacosPropertySource;
}

进入loadNacosData方法:

代码语言:javascript复制
private Properties loadNacosData(String dataId, String group, String fileExtension) {
   String data = null;
   try {
      data = configService.getConfig(dataId, group, timeout);
      if (StringUtils.isEmpty(data)) {
         log.warn(
               "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
               dataId, group);
         return EMPTY_PROPERTIES;
      }
      log.info(String.format(
            "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
            group, data));

      Properties properties = NacosDataParserHandler.getInstance()
            .parseNacosData(data, fileExtension);
      return properties == null ? EMPTY_PROPERTIES : properties;
   }
   catch (NacosException e) {
      log.error("get data from Nacos error,dataId:{}, ", dataId, e);
   }
   catch (Exception e) {
      log.error("parse data from Nacos error,dataId:{},data:{},", dataId, data, e);
   }
   return EMPTY_PROPERTIES;
}

最终通过configService.getConfig方法从nacos配置中心中加载配置进行填充。

0 人点赞