我们在上一篇文章中简单的说了一些SpringBoot配置属性相关的一些内容,我们在这篇文章中接着上一篇的文章继续进行分析。我们在上一篇文章中提到了这样一个类:ConfigFileApplicationListener,从类名来看的话这是一个配置文件应用监听器,这个类主要的一个作用是在 refresh context之前解析默认的配置文件。首先我们来看一下它的UML类图:
ConfigFileApplicationListener实现了EnvironmentPostProcessor和SmartApplicationListener这两个接口,从上图中我们可以看到SmartApplicationListener其实是继承了ApplicationListener这个接口的。SmartApplicationListener这个类相比于ApplicationListener多了两个方法,一个是用来检测是否支持给定的类型,一个是用来检测得到的source type。EnvironmentPostProcessor这个接口的作用是在refresh application context之前定制化应用的环境配置信息,需要说明的是,实现这个接口的类必须要在spring.factories进行配置。大家可以想一下这里为什么要这样。既然ConfigFileApplicationListener也是一个监听器类,那么它肯定会监听某些动作的发生,我们在之前的文章中说过,在org.springframework.boot.SpringApplication#run(java.lang.String… args)这个方法中会负责SpringBoot的整个启动工作,在这里有这样的一段代码:
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
顺着listeners.starting();这个方法往下扒的话,你就会找到调用org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEvent这个方法的地方,这个调用链不算很负责,这里就不再多说了。我们直接进入到ConfigFileApplicationListener#onApplicationEvent这个方法中:
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
但是啊但是,在onApplicationEvent中有两个条件判断,你通过listeners.starting()这个调用链一直跟踪到这里的话你会发现,这两个条件都不满足listeners.starting()的调用,那么是在什么地方触发这个监听事件的呢?在org.springframework.boot.SpringApplication#prepareEnvironment这个方法中,我们在上一篇文章中介绍了其中的一些代码,这里再看一段代码:
listeners.environmentPrepared(environment);
org.springframework.boot.SpringApplicationRunListeners#environmentPrepared
public void environmentPrepared(ConfigurableEnvironment environment) {
//这里的listeners是从spring.factories中得到的
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
org.springframework.boot.context.event.EventPublishingRunListener#environmentPrepared
public void environmentPrepared(ConfigurableEnvironment environment) {
//这里的ApplicationEvent是ApplicationEnvironmentPreparedEvent满足我们上面的提到的两个条件中的一个 this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
从上面的代码中我们可以看到在执行listeners.environmentPrepared(environment);
的时候,会调用ConfigFileApplicationListener#onApplicationEvent这个方法,并且所传入的ApplicationEvent是ApplicationEnvironmentPreparedEvent,所以这里会调用ConfigFileApplicationListener#onApplicationEvent中的org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent方法。我们进入到onApplicationEnvironmentPreparedEvent方法中看一下:
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
//从spring.factories中获取key为org.springframework.boot.env.EnvironmentPostProcessor的配置信息不再细说
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
//前面说过ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口
postProcessors.add(this);
//排序
AnnotationAwareOrderComparator.sort(postProcessors);
//循环上一步得到的List,分别调用各自的postProcessEnvironment方法,在这里我们只关注ConfigFileApplicationListener这个类的postProcessEnvironment方法即可
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}
postProcessEnvironment的源码如下:
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
//添加PropertySource 这个是我们要分析的重点
addPropertySources(environment, application.getResourceLoader());
//JavaBean内省的配置
configureIgnoreBeanInfo(environment);
bindToSpringApplication(environment, application);
}
在上面的代码中,我们目前需要重点关注的就是ConfigFileApplicationListener#addPropertySources这个方法:
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
//添加RandomValuePropertySource
RandomValuePropertySource.addToEnvironment(environment);
//加载默认配置信息,这个environment就是我们在SpringApplication中创建的StandardServletEnvironment
new Loader(environment, resourceLoader).load();
}
RandomValuePropertySource.addToEnvironment
public static void addToEnvironment(ConfigurableEnvironment environment) {
//MutablePropertySources#addAfter 这个方法我们在上一章中说过了这里就不再多说了。这段代码的意思是将RandomValuePropertySource的顺序添加到systemEnvironment的后面
environment.getPropertySources().addAfter(
//systemEnvironment
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
//random
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
}
上面这段代码的意思就将RandomValuePropertySource添加到systemEnvironment的后面,到目前为止我们的MutablePropertySources#propertySourceList中的元素顺序为:commandLineArgs、servletConfigInitParams 、servletContextInitParams 、jndiProperties 、systemProperties 、systemEnvironment、random。我们接着分析new Loader(environment, resourceLoader).load();这段代码,这个代码中的内容比较多,我们也不是全部都要关注,这里我们先把Profile相关的内容先剥离出去。我们直接到这里:
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
//获取配置文件的位置 1)
for (String location : getSearchLocations()) {
//如果不是以/结尾的话则调用下面的方法
if (!location.endsWith("/")) {
// location is a filename already, so don't search for more
// filenames
load(location, null, profile);
}
else {
//获取配置文件的名称 2)
for (String name : getSearchNames()) {
load(location, name, profile);
}
}
}
this.processedProfiles.add(profile);
}
我们先来看上面标记为1)处的代码:
private Set<String> getSearchLocations() {
Set<String> locations = new LinkedHashSet<String>();
// User-configured settings take precedence, so we do them first
//如果配置了spring.config.location这个属性值,则先解析这个属性值
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
//多个值以 , 进行分割
for (String path : asResolvedSet(
this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
if (!path.contains("$")) {
path = StringUtils.cleanPath(path);
if (!ResourceUtils.isUrl(path)) {
path = ResourceUtils.FILE_URL_PREFIX + path;
}
}
locations.add(path);
}
}
//通常我们都不会设置 ' spring.config.location ' 这个属性值 这里添加默认位置
//默认位置为 "classpath:/,classpath:/config/,file:./,file:./config/"
//asResolvedSet会用 , 分割上述字符串 并且还有一个作用将上传得到的List进行 翻转 注意这个很重要!!! 即使你在上面设置了 ' spring.config.location ' 它的位置也是在系统默认的位置的后面
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
DEFAULT_SEARCH_LOCATIONS));
return locations;
}
我们接着看2)处的代码:
private Set<String> getSearchNames() {
//如果配置了 spring.config.name 属性值
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
null);
}
//解析默认的文件名 application 这里同样会对获取到的List进行翻转
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}
所以上面while的这一段代码我们可以简化为这样(极其简化):
List<String> listString = (Arrays.asList("file:./config/,file:./,classpath:/config/,classpath:/".split(",")));
List<String> listName = Arrays.asList("application");
for (String location : listString) {
for(String name : listName){
load(location, name, profile);
}
}