Nacos源码分析四、BootstrapApplicationListener运行原理(1)

x33g5p2x  于2021-12-20 转载在 Bootstrap  
字(6.4k)|赞(0)|评价(0)|浏览(568)

要理解nacos的配置变更如何更新到应用的配置上,这部分的内容属于SpringCloud部分的,要全部串起来,需要了解spring-cloud-context和spring-cloud-alibaba-nacos-config相关的内容。以下学习过程spring-cloud-alibaba-nacos-config版本为2.2.1.RELEASE,对应spring-cloud-context版本为2.2.2.RELEASE。涉及到spring的内容使用版本为 5.2.4.RELEASE
其实写着一部分的时候挺纠结的,实际上这部分的内容应该属于Spring的范畴,只是由于nacos引出来的,就暂时放这个地方吧。

我们先看BootStrapApplicationListener怎么来的吧

首先springboot启动,创建SpringApplication对象:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));这一句添加ApplicationListener监听器。当我们引入spring-cloud-alibaba-nacos-config时,会同时引入spring-cloud-context包,我们看一下这个包下的spring.factories文件:

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.cloud.bootstrap.BootstrapApplicationListener,\
org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\
org.springframework.cloud.context.restart.RestartListener

可以看到这里引入了BootstrapApplicationListener类。SpringApplication的run方法执行时,当执行到prepareEnvironment环境准备时,会广播ApplicationEnvironmentPreparedEvent事件,此时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);
}

这里主要是创建了一个新的上下文context,我们看一下bootstrapServiceContext这个方法:

private ConfigurableApplicationContext bootstrapServiceContext(
      ConfigurableEnvironment environment, final SpringApplication application,
      String configName) {
    // 新的环境
   StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
   MutablePropertySources bootstrapProperties = bootstrapEnvironment
         .getPropertySources();
    // 清除属性
   for (PropertySource<?> source : bootstrapProperties) {
      bootstrapProperties.remove(source.getName());
   }
   String configLocation = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
   String configAdditionalLocation = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.additional-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);
   }
   if (StringUtils.hasText(configAdditionalLocation)) {
      bootstrapMap.put("spring.config.additional-location",
            configAdditionalLocation);
   }
   bootstrapProperties.addFirst(
         new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap));
   for (PropertySource<?> source : environment.getPropertySources()) {
      if (source instanceof StubPropertySource) {
         continue;
      }
       //老的放进新的里
      bootstrapProperties.addLast(source);
   }
   SpringApplicationBuilder builder = new SpringApplicationBuilder()
         .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
         .environment(bootstrapEnvironment)
         .registerShutdownHook(false).logStartupInfo(false)
         .web(WebApplicationType.NONE);
   final SpringApplication builderApplication = builder.application();
   if (builderApplication.getMainApplicationClass() == null) {
      builder.main(application.getMainApplicationClass());
   }
   if (environment.getPropertySources().contains("refreshArgs")) {
      builderApplication
            .setListeners(filterListeners(builderApplication.getListeners()));
   }
   builder.sources(BootstrapImportSelectorConfiguration.class);
   final ConfigurableApplicationContext context = builder.run();
   context.setId("bootstrap");
   addAncestorInitializer(application, context);
   bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
   mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
   return context;
}

代码比较长,简单分析一下

  1. 首先是创建一个新的环境,将里面的属性源删除干净
  2. 放入spring.config.name=bootstrap的属性源,如果有spring.config.locationspring.config.additional-location也一起放入,封装成一个MapPropertySource属性源namebootstrap,放进新环境属性源里,然后把老的环境属性源放入到新的里
  3. 创建一个SpringApplicationBuilder,就是建造者模式,里面就是创建SpringApplication的,然后设置一些属性,是一个最简单的一般上下文设置,然后设置一个配置文件BootstrapImportSelectorConfiguration
public SpringApplicationBuilder(Class<?>... sources) {
   this.application = createSpringApplication(sources);
}
protected SpringApplication createSpringApplication(Class<?>... sources) {
    return new SpringApplication(sources);
}
  1. SpringApplicationBuilder的run方法:
public ConfigurableApplicationContext run(String... args) {
   if (this.running.get()) {
      // If already created we just return the existing context
      return this.context;
   }
   configureAsChildIfNecessary(args);
   if (this.running.compareAndSet(false, true)) {
      synchronized (this.running) {
         // If not already running copy the sources over and then run.
         this.context = build().run(args);
      }
   }
   return this.context;
}

public SpringApplication build() {
    return build(new String[0]);
}
public SpringApplication build(String... args) {
    configureAsChildIfNecessary(args);
    this.application.addPrimarySources(this.sources);
    return this.application;
}

最终还是调用SpringApplication的run方法,这里primarySources添加了前面注册进去的BootstrapImportSelectorConfiguration配置类。

总结

在springcloud环境下会创建一个应用程序上下文,实际上这个上下文是主应用上下文的parent。他们共享同一个环境信息environment。配置名使用bootstrap(和application.yml区别)。同时该上下文添加了一个BootstrapImportSelectorConfiguration配置类。我们后面分析。

相关文章