【Spring Cloud】Bootstrap Context初始化

1,463 阅读2分钟

1.简介

Spring Cloud比Spring Boot多了一个Bootstrap Context的上下文,对照源码看下Bootstrap Context是怎么初始化的。

2.启动方式

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • @EnableDiscoveryClient 开启服务发现

3.启动

  • SpringApplication#run

image.png

准备创建environment,然后执行创建监听事件。

  • SpringApplication#prepareEnvironment

image.png

这里可以看到有一个事件监听类EventPublishingRunListener,注意这里的监听类是实现的SpringApplicationRunListener接口。

image.png

这个类主要是获取SpringApplication中的所有ApplicationListener,然后传播事件。代码如下:

image.png

我们刚才的事件是environmentPrepared,找EventPublishingRunListener对应的代码:

image.png

从SpringApplication中获取到的ApplicationListener中有一个BootstrapApplicationListener,这里是Spring Cloud初始化的开始。

  • BootstrapApplicationListener#onApplicationEvent

@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
   // 获取当前时间的环境
   ConfigurableEnvironment environment = event.getEnvironment();
   // 不允许初始化Spirng Cloud Bootstrap
   if (!environment.getProperty("spring.cloud.bootstrap.enabled", Boolean.class,
         true)) {
      return;
   }
   // don't listen to events in a bootstrap context
   // 初始化一次后会添加一个名称为BOOTSTRAP_PROPERTY_SOURCE_NAME的PropertySource,用来判断是否已经初始化过
   if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
      return;
   }
   ConfigurableApplicationContext context = null;
   //获取bootstrap配置文件的名称 默认:bootstrap
   String configName = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}");
   for (ApplicationContextInitializer<?> initializer : event.getSpringApplication()
         .getInitializers()) {
      if (initializer instanceof ParentContextApplicationContextInitializer) {
         //尝试获取当前已经存在的Parent Context (这里一般不会进来)
         context = findBootstrapContext(
               (ParentContextApplicationContextInitializer) initializer,
               configName);
      }
   }
 
   if (context == null) {
      // 创建一个Application Context
      context = bootstrapServiceContext(environment, event.getSpringApplication(),
            configName);
      // 添加一个Close监听事件      
      event.getSpringApplication()
            .addListeners(new CloseContextOnFailureApplicationListener(context));
   }

   // 将Bootstrap Application Context中加载的信息合并到当前的application和environment中
   apply(context, event.getSpringApplication(), environment);
}
  • BootstrapApplicationListener#bootstrapServiceContext

private ConfigurableApplicationContext bootstrapServiceContext(
      ConfigurableEnvironment environment, final SpringApplication application,
      String configName) {
   // 创建一个新的environment
   StandardEnvironment bootstrapEnvironment = new StandardEnvironment();
   MutablePropertySources bootstrapProperties = bootstrapEnvironment
         .getPropertySources();
   //清空PropertySource      
   for (PropertySource<?> source : bootstrapProperties) {
      bootstrapProperties.remove(source.getName());
   }
   //获取bootstrap配置文件地址
   String configLocation = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.location:}");
   String configAdditionalLocation = environment
         .resolvePlaceholders("${spring.cloud.bootstrap.additional-location:}");
   Map<String, Object> bootstrapMap = new HashMap<>();
   // configName为bootstrap (未配置的情况下)
   bootstrapMap.put("spring.config.name", configName);
   // if an app (or test) uses spring.main.web-application-type=reactive, bootstrap
   // will fail
   // force the environment to use none, because if though it is set below in the
   // builder
   // the environment overrides it
   // 设置WebApplication类型 这里因为这个Application主要是加载配置 所以设置为none
   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);
   }
   // TODO: is it possible or sensible to share a ResourceLoader?
   // 构建一个新的SpringApplicationBuilder 注意environment
   SpringApplicationBuilder builder = new SpringApplicationBuilder()
         .profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
         .environment(bootstrapEnvironment)
         // Don't use the default properties in this builder
         .registerShutdownHook(false).logStartupInfo(false)
         .web(WebApplicationType.NONE);
   final SpringApplication builderApplication = builder.application();
   // 设置启动类
   if (builderApplication.getMainApplicationClass() == null) {
      // gh_425:
      // SpringApplication cannot deduce the MainApplicationClass here
      // if it is booted from SpringBootServletInitializer due to the
      // absense of the "main" method in stackTraces.
      // But luckily this method's second parameter "application" here
      // carries the real MainApplicationClass which has been explicitly
      // set by SpringBootServletInitializer itself already.
      builder.main(application.getMainApplicationClass());
   }
   // 判断是否刷新参数
   if (environment.getPropertySources().contains("refreshArgs")) {
      // If we are doing a context refresh, really we only want to refresh the
      // Environment, and there are some toxic listeners (like the
      // LoggingApplicationListener) that affect global static state, so we need a
      // way to switch those off.
      builderApplication
            .setListeners(filterListeners(builderApplication.getListeners()));
   }
   // 设置需要加载的source类
   builder.sources(BootstrapImportSelectorConfiguration.class);
   final ConfigurableApplicationContext context = builder.run();
   // gh-214 using spring.application.name=bootstrap to set the context id via
   // `ContextIdApplicationContextInitializer` prevents apps from getting the actual
   // spring.application.name
   // during the bootstrap phase.
   // 设置context名称为bootstrap
   context.setId("bootstrap");
   // Make the bootstrap context a parent of the app context
   
   //设置Parent Context
   addAncestorInitializer(application, context);
   
   // It only has properties in it now that we don't want in the parent so remove
   // it (and it will be added back later)
   bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME);
   
   // 合并配置
   mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties);
   return context;
}

Bootstrap Context创建完成后会context与当前的application、environment进行处理

  • BootstrapApplicationListener#apply

private void apply(ConfigurableApplicationContext context,
      SpringApplication application, ConfigurableEnvironment environment) {
   // 判断是否已经处理过
   if (application.getAllSources().contains(BootstrapMarkerConfiguration.class)) {
      return;
   }
   // 添加标记类
   application.addPrimarySources(Arrays.asList(BootstrapMarkerConfiguration.class));
   @SuppressWarnings("rawtypes")
   Set target = new LinkedHashSet<>(application.getInitializers());
   // 添加Bootstrap Context中加载的ApplicationContextInitializer Bean
   target.addAll(
         getOrderedBeansOfType(context, ApplicationContextInitializer.class));
   application.setInitializers(target);
   
   // 添加解密的`Initializer`
   addBootstrapDecryptInitializer(application);
}

至此整个Bootstrap Context初始化完成,后面的逻辑和普通的Spring Boot流程基本一致。

4.结束

今天大致的写了下Bootstrap Context的初始化逻辑,下一篇文章讲深入细节,记录下配置文件的加载逻辑。

  • bootstrap.yaml加载
  • 从配置中心加载配置
  • application.yaml加载
  • 其他配置加载