Spring Boot启动会扫描application.properties或者application.yml文件作为Spring Boot的默认配置文件。在使用过程中会涉及到各种各样的配置,本篇则主要针对配置路径,看一下文件加载的前后顺序。多个配置文件配置了同样得值,会加载哪一个呢?
一、内部配置的加载顺序
1)配置文件前后顺序执行测试
-
file:/config/
-
file:/
-
classpath:/config/
-
classpath:/
以上顺序按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级的配置内容会覆盖低优先级配置的内容,其中配置文件中的内容是互补配置,即 -
存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
-
存在不同的内容的时候,高优先级和低优先级的配置内容取并集
我配置了如上几个配置文件,看项目启动结果,则可看到最终生效的配置文件。
第一种情况:五个路径配置文件都存在
- file:/config/
- file:/
- classpath:/config/
- classpath:/
结果为:
第二种:去除8025所在的file:/config/
- file:/
- classpath:/config/
- classpath:/
结果为:
第三种:再去除8026所在的file:/
- classpath:/config/
- classpath:/
结果为:
第四种:再去除8024所在的配置文件,则只剩 classpath:/下的.properties和.yml两个配置文件了,看执行结果会如何呢?
- classpath:/
结果为:
二、外部配置的加载顺序
SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数(VM Options)传入配置项,为什么通过启动参数传入的配置项会“顶掉”配置文件中的配置?
三、prepareEnvironment原理解析
run方法下有如下方法:字面理解就是准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//获取或创建环境(存在直接返回,不存在创建)
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//配置环境:配置PropertySources和activeProfiles
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
//通知所有的监听者,环境已经准备好了
listeners.environmentPrepared((ConfigurableEnvironment)environment);
// bindToSpringApplication绑定环境
this.bindToSpringApplication((ConfigurableEnvironment)environment);
// 如果是非web环境,将环境转换成StandardEnvironment
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
// 如果配置了configurationProperties属性, 那么将其放在environment的propertySources的首部
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
1)getOrCreateEnvironment
getOrCreateEnvironment方法创建并返回了一个环境:
private ConfigurableEnvironment getOrCreateEnvironment() {
// 存在则直接返回
if (this.environment != null) {
return this.environment;
} else {
// 根据webApplicationType创建对应的Environment
switch(this.webApplicationType) {
// 标准的Servlet环境,也就是我们说的web环境
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
//响应式web环境,2.0新引入
return new StandardReactiveWebEnvironment();
default:
//普通程序
return new StandardEnvironment();
}
}
}
我们断点调试,则是返回StandardServletEnvironment
该环境目前包含的内容如下
2)configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
//默认为TRUE
if (this.addConversionService) {
//environment中配置各个转换类
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService)conversionService);
}
//配置属性
this.configurePropertySources(environment, args);
//配置profile
this.configureProfiles(environment, args);
}
从源码看,将配置任务按顺序委托给configurePropertySources和configureProfiles,分别配置属性和profile。
我们来看看configureProfiles,正常项目中,我们是分各种环境的,比如开发环境,测试环境,线上环境,有可能还有什么压测环境等等。就是通过此方法来查找对应的环境配置。
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
// 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性
environment.getActiveProfiles();
Set<String> profiles = new LinkedHashSet(this.additionalProfiles);
//再次获取和配置
profileprofiles.addAll(Arrays.asList(environment.getActiveProfiles()));
//设置environment的profile
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
configureEnvironment已经配置好了环境,下一步则是发送出去
3)listeners.environmentPrepared
调用SpringApplicationRunListeners的environmentPrepared方法发布事件。该方法会遍历注册在spring.factories中SpringApplicationRunListener实现类,然后调用其environmentPrepared方法。
每一个自动注入类中都有spring.factories,其中SpringApplicationRunListener实现类注册为:
public void environmentPrepared(ConfigurableEnvironment environment) {
Iterator var2 = this.listeners.iterator();
while(var2.hasNext()) {
SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
listener.environmentPrepared(environment);
}
}
其中listeners便是注册的类的集合,这里默认只有EventPublishingRunListener。它的environmentPrepared方法实现为:
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
其实就是发布了一个监听事件。该事件会被同样注册在spring.factories中的ConfigFileApplicationListener监听到:
关于配置文件的核心处理便在ConfigFileApplicationListener中完成。在该类中我们可以看到很多熟悉的常量:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";
private static final Set<String> NO_SEARCH_NAMES = Collections.singleton((Object)null);
private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
public static final int DEFAULT_ORDER = -2147483638;
private final DeferredLog logger = new DeferredLog();
private String searchLocations;
private String names;
private int order = -2147483638;
public ConfigFileApplicationListener() {
}
比如Spring Boot默认寻找的配置文件的名称、默认扫描的类路径等。
不仅如此,该类还实现了监听器SmartApplicationListener接口和EnvironmentPostProcessor接口也就是拥有了监听器和环境处理的功能。
设置断点调试则会发现,调试到environmentPrepared方法,直接跳转到ConfigFileApplicationListener中的onApplicationEvent上。
onApplicationEvent方法对接收到事件进行判断,如果是ApplicationEnvironmentPreparedEvent事件则调用onApplicationEnvironmentPreparedEvent方法进行处理
如下图:
onApplicationEnvironmentPreparedEvent会获取到对应的EnvironmentPostProcessor并调用其postProcessEnvironment方法进行处理。而loadPostProcessors方法获取的EnvironmentPostProcessor正是在spring.factories中配置的当前类。如下图:
经过一系列的调用,最终调用到该类的如下方法:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
RandomValuePropertySource.addToEnvironment(environment);
(new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
}
其中Loader类为ConfigFileApplicationListener内部类,提供了具体处理配置文件优先级、profile、加载解析等功能。
其中Loader中的Load方法,遍历PropertySourceLoader列表,并进行对应配置文件的解析,而这里的列表中的PropertySourceLoader同样配置在spring.factories中:
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
Iterator var6 = this.propertySourceLoaders.iterator();
while(var6.hasNext()) {
PropertySourceLoader loader = (PropertySourceLoader)var6.next();
if (this.canLoadFileExtension(loader, location)) {
this.load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
Set<String> processed = new HashSet();
Iterator var14 = this.propertySourceLoaders.iterator();
while(var14.hasNext()) {
PropertySourceLoader loaderx = (PropertySourceLoader)var14.next();
String[] var9 = loaderx.getFileExtensions();
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String fileExtension = var9[var11];
if (processed.add(fileExtension)) {
this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
}
}
}
#PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=
org.springframework.boot.env.PropertiesPropertySourceLoader,
org.springframework.boot.env.YamlPropertySourceLoader
查看这两PropertySourceLoader的源代码,会发现SpringBoot默认支持的配置文件格式及解析方法。
这也就明白了最开始测试的时候,内部配置的先后顺序了。
四、同一个属性多次赋值,读取结果
- file:/config/
- file:/
- classpath:/config/
- classpath:/
优先如上路径,然后优先yml文件,再properties文件。所有若两种文件同时并存同一个目录,最终生效的是properties文件内容,覆盖掉了yml.