8Nacos配置中心之环境准备

举报
周杰伦本人 发表于 2022/05/15 15:47:16 2022/05/15
1.1k+ 0 0
【摘要】 8 Nacos配置中心之环境准备 prepareEnvironment()方法 BootstrapApplicationListener类 BootstrapImportSelectorConfiguration类 BootstrapImportSelector的selectImports()方法: 总结 8 Nacos配置中心之环境准备Nacos配置中心的工作流程是怎么样的呢?首先启动S...

8 Nacos配置中心之环境准备

Nacos配置中心的工作流程是怎么样的呢?
首先启动SpringBoot项目,在启动项目之后,需要把远程服务器的配置文件加载到Spring容器中

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

重点在于Environment的获取,那么如何从远处服务器上的配置加载到Environment?

我们看一下SpringBoot启动的时候,SpringApplication.run()方法做了哪些环境准备

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;
}
  1. 获取监听并启动监听
  2. 根据传入的参数创建applicationArguments对象
  3. 调用prepareEnvironment方法,进行环境准备
  4. 打印banner,准备上下文,刷新上下文,然后执行刷新之后操作等等
  5. 租后返回context上下文

我们看看prepareEnvironment方法做了什么

prepareEnvironment()方法

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(environment);中发布一个ApplicationEnvironmentPreparedEvent事件,所有定义这个的Listener监听器都会监听到这个事件。

BootstrapApplicationListener就会收到该事件并进行处理

BootstrapApplicationListener类

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
   ConfigurableEnvironment environment = event.getEnvironment();
   if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
         true)) {
      return;
   }
   // don't listen to events in a bootstrap context
   if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      return;
   }
   ConfigurableApplicationContext context = null;
   String configName = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
   for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
         .getInitializers()) {
      if (initializer instanceof ParentContextApplicationContextInitializer) {
         context = findBootstrapContext(
               (ParentContextApplicationContextInitializer) initializer,
               configName);
      }
   }
   if (context == null) {
      context = bootstrapServiceContext(environment, event.getSpringApplication(),
            configName);
      event.getSpringApplication()
            .addListeners(new CloseContextOnFailureApplicationListener(context));
   }

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

在bootstrapServiceContext方法中调用builder.sources(BootstrapImportSelectorConfiguration.class),进行自动装配,这里就不贴具体的代码了,我们看一下BootstrapImportSelectorConfiguration这个类做了什么

BootstrapImportSelectorConfiguration类

BootstrapImportSelectorConfiguration是配置类

@Configuration
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

用@Import导入BootstrapImportSelector实现自动装配

BootstrapImportSelector的selectImports()方法:

方法如下:

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

利用Spring的SPI机制查找META-INF/spring.factories扩展点

key是BootstrapConfiguration

在spring-cloud-context.jar中的spring.factories:PropertySourceBootstrapConfiguration

# 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-context.jar中的spring.factories:

NacosConfigBootstrapConfiguration

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer

水落石出了,这里自动装配了NacosConfigBootstrapConfiguration配置类

总结

这就是Nacos配置中心的环境准备方面,从SpringBoot项目启动类的环境准备的方法入手,通过BootstrapApplicationListener类的监听方法,调用了builder.sources(BootstrapImportSelectorConfiguration.class)加载了BootstrapImportSelectorConfiguration类,它的selectImports()方法中利用Spring的SPI扩展机制记载了key为BootstrapConfiguration定义的一些nacos中类,从而对nacos的启动环境做了准备。其实nacos最重要的功能就是注册中心和配置中心,作为nacos的使用者,我们要掌握好nacos的流程和工作原理,才能定位到问题,并在合适的业务场景中进行对nacos服务的功能优化和利用好nacos,这篇文章仅仅是对nacos作为配置中心功能的环境准备方面的结束,后续还会进一步分析nacos配置文件的流程和工作原理。如果你觉得这篇文章对你有帮助的话,欢迎给我留言点赞评论转发!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。