深入理解Spring Cloud一(2)如何从配置中心加载配置?

先介绍Spring Cloud留下的扩展点,和整个加载流程。后面文章会以具体配置中心举例进行说明。

一、
PropertySourceBootstrapConfiguration 核心功能介绍

注入PropertySourceLocator实现类,PropertySourceLocator是一个接口,加载配置的扩展点。

@Autowired(required = false)
private List propertySourceLocators = new ArrayList<>();

核心实现代码如下(做了删减方便说明核心功能)

//ApplicationContextInitializer 接口的实现方法
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
   List> composite = new ArrayList<>();
   boolean empty = true;
   ConfigurableEnvironment environment = applicationContext.getEnvironment();
   for (PropertySourceLocator locator : this.propertySourceLocators) {
      //重要!!调用扩展点实现类,获得配置
      Collection> source = locator.locateCollection(environment);
      
      List> sourceList = new ArrayList<>();
      for (PropertySource p : source) {
         sourceList.add(new SimpleBootstrapPropertySource(p));
      }
      logger.info("Located property source: " + sourceList);
      composite.addAll(sourceList);
      empty = false;
   }
   if (!empty) {
      MutablePropertySources propertySources = environment.getPropertySources();
      //重要!! 合并配置到当前environment中
      insertPropertySources(propertySources, composite);
   }
}


二、initialize方法是何时被调用的?

整体过程比较绕,会涉及到bootstrap的 SpringApplication,和application的SpringApplication加载过程。

在SpringApplication的run方法中

//创建applicationContext
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
      SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   context.setEnvironment(environment);
   postProcessApplicationContext(context);
   //调用ApplicationContextInitializer
   applyInitializers(context);
   //下面代码省略
}
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接口,所以
PropertySourceBootstrapConfiguration的initialize(context)方法会被调用,进而触发配置加载扩展点的调用。

其实没那么简单,上面看到的只是比较明确的调用过程。

问一个问题,getInitializers() 是如何获取到
PropertySourceBootstrapConfiguration的?

public Set> getInitializers() {
   return asUnmodifiableOrderedSet(this.initializers);
}

在SpringApplication的构建中,我们看到从META-INF/spring.factories中加载
ApplicationContextInitializer,找完所有的文件后,你会发现
PropertySourceBootstrapConfiguration并没有配置在里面。

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
   //其他代码省略
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
}

回到上一节的
BootstrapApplicationListener中,我们知道bootstrap 的SpringApplication在这里进行创建,这里sources是关键。

builder.sources(BootstrapImportSelectorConfiguration.class);

在bootstrap的SpringApplication中会导入BootstrapImportSelector

@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {

}

BootstrapImportSelector会从META-INF/spring.factories加载BootstrapConfiguration的类

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
   // Use names and ensure unique to protect against duplicates
   List names = new ArrayList<>(SpringFactoriesLoader
         .loadFactoryNames(BootstrapConfiguration.class, classLoader));
   names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
         this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));
   //其他代码省略
   return classNames;
}
# Bootstrap components
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\

这个时候你会发现
PropertySourceBootstrapConfiguration类被加载到bootstrap的SpringApplication中。但是这里只是被加载,还没有被调用。

回到
BootstrapApplicationListener中,继续往下看

private void apply(ConfigurableApplicationContext context,
      SpringApplication application, ConfigurableEnvironment environment) {
   Set target = new LinkedHashSet<>(application.getInitializers());
   //获取bootstrap SpringApplication中所有ApplicationContextInitializer类
   target.addAll(getOrderedBeansOfType(context, ApplicationContextInitializer.class));
   //加入到application SpringApplication中
   application.setInitializers(target);
}

这时application SpringApplication的initializers就有了
PropertySourceBootstrapConfiguration,下面就顺理成章的被调用了。


三、总结

1.application的SpringApplication构建中触发environmentPrepared事件。

2.environmentPrepared事件触发bootstrap 的SpringApplication构建。

3.bootstrap 的SpringApplication会创建并配置好
PropertySourceBootstrapConfiguration对象供application的SpringApplication使用。

4.application的SpringApplication在prepareContext时,触发
PropertySourceBootstrapConfiguration.initialize()方法调用。

四、Q&A

Q:为什么SpringApplication直接加载
PropertySourceBootstrapConfiguration不可行?

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
}

A:因为
PropertySourceBootstrapConfiguration中使用了@Autowired 注入PropertySourceLocator的实现类,直接从配置文件中加载,调用initialize()时,ApplicationContext还没准备好,无法实现自动注入,则 propertySourceLocators永远为空,无法实现扩展功能。

Q:怎么理解bootstrap SpringApplication

A:bootstrap SpringApplication其实是application SpringApplication的父容器,可以理解为bootstrap要准备好各种application初始化过程用要用到的配置和对象,供其使用。