配置文件加载
引入
在开发配置同步工具时,遇到一个问题:在resource下增加自定义yml后,需要在bootstrap.yml 中指定配置文件名称;并且需要同时指定默认配置文件名称 application.yml,不然就不扫了
yml
spring:
config:
name: config-sysnchronous,application
自此引发了几个思考:
- 为什么要在
bootstrap.yml
里配置? - 配置后为什么
application.yml
就失效了?
问题研究
首先,需要明白这两个文件都是如何被SpringBoot加载的
监听器与事件
上面这个类图描述了加载bootstrap.yml
和application.yml
设计的监听器和监听器监听的事件。
bootstrap.yml
:
- 监听器:
BootstrapApplicationListener
- 事件:
ApplicationEnvironmentPreparedEvent
- 核心代码:
java
@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);
}
apply(context, event.getSpringApplication(), environment);
}
application.yml
:
- 监听器 :
ConfigFileApplicationListener
- 事件:
ApplicationPreparedEvent
- 核心代码:
java
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
事件顺序
在SprintBoot的启动方法中,优先进行了 ApplicationEnvironmentPreparedEvent
类型事件的触发,再进行了ApplicationPreparedEvent
类型事件的触发:
java
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// ApplicationEnvironmentPreparedEvent 类型事件触发
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// ApplicationPreparedEvent 类型事件触发
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
...
}
监听器执行类EventPublishingRunListener
分发不同类型事件
java
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
java
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(this.application, this.args, context));
}
监听器顺序
bootstrap.yml
对应的监听器监听ApplicationEnvironmentPreparedEvent
类型事件,application.yml
对应监听器监听 ApplicationEvent
类型事件,包含了ApplicationEnvironmentPreparedEvent
,并根据这两个事件类型有不同处理方式。那么ApplicationEnvironmentPreparedEvent
事件是如何保证被BootstrapApplicationListener
优先执行的呢?
主要通过类图中的Order类
:
这两个监听器都继承了Ordered类并通过重新getOrdered
方法设置自己的顺序:
BootstrapApplicationListener
java
private int order = DEFAULT_ORDER;
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 5;
@Override
public int getOrder() {
return this.order;
}
ConfigFileApplicationListener
java
private int order = DEFAULT_ORDER;
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
@Override
public int getOrder() {
return this.order;
}
在SpringBoot进行初始化加载Listenner类是根据order排序:
java
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
}
java
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
其中 AnnotationAwareOrderComparator.sort(instances)
使用的对比类 AnnotationAwareOrderComparator
中根据Order类顺序进行排序。
加载顺序总结
bootstrap.yml
的加载事件ApplicationEnvironmentPreparedEvent
在Springboot启动时优先被创建分发并被优先级较高的BootstrapApplicationListener
类监听,application.yml
的加载事件ApplicationPreparedEvent
在SprintBoot中在 ApplicationEnvironmentPreparedEvent
类事件后被分发并被监听。
默认配置与自定义配置加载
在ConfigFileApplicationListener
加载配置文件时,如果在 bootstrap.yml
中配置了配置文件,会影响默认配置文件application.yml
的加载:
yml
spring:
config:
name: config-sysnchronous,application
java
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
private static final String DEFAULT_NAMES = "application";
private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
return asResolvedSet(property, null);
}
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
此时,bootstrap.yml
中的内容已经被加载入enviroment,如果在 bootstrap.yml
中配置了配置文件,默认配置文件application.yml
就不会被加载
问题总结
- 为什么要在
bootstrap.yml
里配置?
SprintBoot 优先加载
bootstrap.yml
的内容,这个文件通常包含一些用于系统环境的配置,后续加载应用配置文件时会根据该文件的配置信息进行判断。
- 配置后为什么
application.yml
就失效了?
根据源码,默认配置文件与
bootstrap.yml
中配置的配置文件名为或关系,所以配置了就失效了。