欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-10 17:23:27
要理解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我们先看BootStrapApplicationListener怎么来的吧首先sp...

要理解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);
    }
    
  4. 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配置类。我们后面分析。

本文地址:https://blog.csdn.net/qq_19414183/article/details/112238697