对于SpringBoot中的属性文件application.properties和application.yml文件应该非常熟悉,但是对于bootstrap.properties文件和bootstrap.yml这个两个文件用的估计就比较少了,用过的应该清楚bootstrap.properties中定义的文件信息会先与application.properties中的信息加载。
首先在SpringBoot中默认是不支持bootstrap.properties属性文件的。我们需要引入SpringCloud的依赖spring-cloud-starter-bootstrap或者存在spring-cloud-context包,在这些包中spring.factories文件中配置类监听器BootstrapApplicationListener
。
根据文章监听器得知,在众多org.springframework.context.ApplicationListener
类型的监听器中,BootstrapApplicationListener监听器是最先触发执行的。ConfigFileApplicationListener
监听器是SpringBoot启动过程中正常加载配置文件的监听器。
public class BootstrapApplicationListener{
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}");//#1
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);
}
private ConfigurableApplicationContext bootstrapServiceContext(ConfigurableEnvironment
environment,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:}");
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);
}
...
SpringApplicationBuilder builder = new SpringApplicationBuilder()
.profiles(environment.getActiveProfiles()).bannerMode(Mode.OFF)
.environment(bootstrapEnvironment)
.registerShutdownHook(false).logStartupInfo(false)
.web(WebApplicationType.NONE);
SpringApplication builderApplication = builder.application();
...
builder.sources(BootstrapImportSelectorConfiguration.class);//#2
final ConfigurableApplicationContext context = builder.run();
addAncestorInitializer(application, context);//#3
...
return context;
}
}
步骤1:监听器从环境变量获取spring.cloud.bootstrap.name
配置项,否则为默认配置文件名bootstrap
。
步骤3:设置父子容器。application创建常规容器AnnotationConfigServletWebServerApplicationContext
,并将第二个容器AnnotationConfigApplicationContext
设置为其父容器。
1、BootstrapImportSelectorConfiguration
BootstrapImportSelector
类似于正常启动类通过@Import导入的AutoConfigurationImportSelector。
@Configuration(proxyBeanMethods = false)
@Import(BootstrapImportSelector.class)
public class BootstrapImportSelectorConfiguration {}
public class BootstrapImportSelector implements EnvironmentAware, DeferredImportSelector {
...
@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));//#1
names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(
this.environment.getProperty("spring.cloud.bootstrap.sources", ""))));
List<OrderedAnnotatedElement> elements = new ArrayList<>();
for (String name : names) {
elements.add(new OrderedAnnotatedElement(this.metadataReaderFactory, name));
}
AnnotationAwareOrderComparator.sort(elements);
String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new);
return classNames;
}
}
步骤1:通过SPI机制获取org.springframework.cloud.bootstrap.BootstrapConfiguration
接口的子类。其中比较熟悉的子类为ConsulConfigBootstrapConfiguration
。
2、父子容器之AncestorInitializer
父容器ConsulConfigBootstrapConfiguration是通过AncestorInitializer被添加为AnnotationConfigServletWebServerApplicationContext的父容器。
private void addAncestorInitializer(SpringApplication application,
ConfigurableApplicationContext context) {
boolean installed = false;
for (ApplicationContextInitializer<?> initializer : application
.getInitializers()) {
if (initializer instanceof AncestorInitializer) {
installed = true;
// New parent
((AncestorInitializer) initializer).setParent(context);
}
}
if (!installed) {
application.addInitializers(new AncestorInitializer(context));
}
}
AncestorInitializer的回调:SpringApplication#run~prepareContext
SpringBoot解析启动类过程中如果存在父容器则优先从父容器获取当前bean。
3、ConsulConfigBootstrapConfiguration
在SpringBoot通过BootstrapApplicationListener监听器加载bootstrap
配置文件过程中,通过延迟加载
策略实现自动装配候选类的加载。
@Configuration(proxyBeanMethods = false)
@ConditionalOnConsulEnabled
public class ConsulConfigBootstrapConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import(ConsulAutoConfiguration.class)
@ConditionalOnProperty(name = "spring.cloud.consul.config.enabled",
matchIfMissing = true)
protected static class ConsulPropertySourceConfiguration {
@Autowired
private ConsulClient consul;
@Bean
@ConditionalOnMissingBean
public ConsulConfigProperties consulConfigProperties() {
return new ConsulConfigProperties();
}
@Bean
public ConsulPropertySourceLocator consulPropertySourceLocator(
ConsulConfigProperties consulConfigProperties) {
return new ConsulPropertySourceLocator(this.consul, consulConfigProperties);
}
}
}
在Consul源码相关分析中关于spring.cloud.consul
相关的配置信息是映射于ConsulProperties
。ConsulProperties是通过ConsulAutoConfiguration
间接引入的。而ConsulAutoConfiguration是在解析bootstrap配置文件时通过当前配置类间接引入的,所以ConsulProperties的属性值是解析bootstrap配置文件得到的。