从源码分析springboot环境配置加载
从源码分析springboot环境配置加载
一直没有搞清楚springboot环境配置信息到底是怎么加载的,是不是在启动时指定--spring.profiles.active之后spring就去指定读取这个文件了,因此这次从源码角度研究一下它的加载过程。
首先从入口开始分析:
public static void main(String[] args) {
//这里的run是springboot启动入口
SpringApplication.run(StarServiceAgencyOrderCoreApplication.class, args);
}
public ConfigurableApplicationContext run(String... args) {
//其他的先不管,springboot的环境信息封装在Environment这个对象中,加载也是从这里开始的,
//而在Environment对象中有个MutablePropertySources,这里是存储具体的环境配置信息的地方
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
}
接下来开始分析一下prepareEnvironment大致干了些啥。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//创建environment对象
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
//做了一些配置初始化,以及加载了一些默认信息,这里可以看到在这个方法执行结束后environment中的propertySource中加载了一些信息,这里其实是在environment初始化时就已经初始化好的,在构造方法中就可以看到了
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
//从这里开始加载了。
listeners.environmentPrepared((ConfigurableEnvironment)environment);
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
可以看到这一块加载是通过listener去完成的。
public void environmentPrepared(ConfigurableEnvironment environment) {
Iterator var2 = this.listeners.iterator();
while(var2.hasNext()) {
//这里通过的EventPublishingRunListener来处理
SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
listener.environmentPrepared(environment);
}
}
接下来在来看EventPublishingRunListener。
//这里没啥好看的,一层一层剥洋葱吧。
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
//这里也没有看到重点,继续剥
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Iterator var4 = this.getApplicationListeners(event, type).iterator();
while(var4.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var4.next();
Executor executor = this.getTaskExecutor();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
一直到ConfigFileApplicationListener类中的onApplicationEvent方法。可以看到ConfigFileApplicationListener中的成员变量,有那么一些熟悉的字眼,接下来还有几层,从ConfigFileApplicationListener#onApplicationEvent() ->onApplicationEnvironmentPreparedEvent->postProcessEnvironment->addPropertySources->ConfigFileApplicationListener.Loader#load。一直到这里才真正开始加载了。
public void load() {
//...
//这个方法初始化profiles的信息,
this.initializeProfiles();
//...
//开始加载配置文件信息
this.load(profile, this::getPositiveProfileFilter, this.addToLoaded(MutablePropertySources::addLast, false));
}
这个方法还是看一下,在这里可以看到在Profiles中首先添加了一个null,另外添加了指定的active配置信息,如果active没有找到的情况下,默认添加了一个default。这也就是springboot会默认加载application-default.xx文件的原因。至于为什么添加一个null,在后面代码处就可以看明白了。
private void initializeProfiles() {
this.profiles.add((Object)null);
Set<ConfigFileApplicationListener.Profile> activatedViaProperty = this.getProfilesActivatedViaProperty();
this.profiles.addAll(this.getOtherActiveProfiles(activatedViaProperty));
this.addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) {
String[] var2 = this.environment.getDefaultProfiles();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
String defaultProfileName = var2[var4];
ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
接下来看一下load方法。
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
this.getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
//这个name是配置文件前缀名。
Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
//加载的方法
this.load(location, name, profile, filterFactory, consumer);
});
});
}
//这里还是在准备读取过程中
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
//...
this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer);
}
private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
//...
//在这里可以看到spring-default.xx的文件是在这里拼接上去的
String profileSpecificFile = prefix + "-" + profile + fileExtension;
//...
this.load(loader, prefix + fileExtension, profile, profileFilter, consumer);
}
//完成配置文件加载的方法
private void load(PropertySourceLoader loader, String location, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilter filter, ConfigFileApplicationListener.DocumentConsumer consumer) {
//通过resourceLoader去寻找文件
Resource resource = this.resourceLoader.getResource(location);
StringBuilder descriptionxx;
//...
String name = "applicationConfig: [" + location + "]";
//最终在loadDocument中完成加载
List<ConfigFileApplicationListener.Document> documents = this.loadDocuments(loader, name, resource);
//...
}
private List<ConfigFileApplicationListener.Document> loadDocuments(PropertySourceLoader loader, String name, Resource resource) throws IOException {
//..
//加载完成,是properties文件就通过PropertiesPropertySourceLoader加载,是yaml文件就通过YamlPropertySourceLoader加载。
List<PropertySource<?>> loaded = loader.load(name, resource);
//..
}
附上分析的时序图:到这初始化的配置文件加载就分析的差不多了。可以得出的结论是spring在初始化时application.xx文件一定会加载,另外则是看你配置的active指定环境文件。那么最后,假如两边都定义了一个变量,spring是怎么去取值呢,哪个值优先呢?
那么回到SpringApplication中,refreshContext看spring容器的初始化方法,这里是入口,具体的步骤很复杂,可以自己跟一下或者单独去看spring的初始化过程,这里就省略了。那么跳到PropertySourcesPropertyResolver#getProperty,这里是spring中对初始化对象赋值的其中一个方法。
//这里的key就是你要赋值的对象key,例如你定义的成员变量
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
//这个propertySources就是刚才加载配置文件后拿到的MutablePropertySources,而这里赋值是通过循环来查找PropertySources中对应存在的key值,找到后就return了。
Iterator var4 = this.propertySources.iterator();
while(var4.hasNext()) {
PropertySource<?> propertySource = (PropertySource)var4.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
}
//取值过程
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = this.resolveNestedPlaceholders((String)value);
}
this.logKeyFound(key, propertySource, value);
//取到值后就return了
return this.convertValueIfNecessary(value, targetValueType);
}
}
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
从上面的代码可以看出来,假如两边都定义了一个变量,spring是怎么去取值优先顺序跟MutablePropertySources中的顺序有关,具体的顺序spring官方给出了定义。参考:https://docs.spring.io/spring-boot/docs/2.2.0.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config 中的Externalized Configuration项。如下:
Spring Boot uses a very particular PropertySource
order that is designed to allow sensible overriding of values. Properties are considered in the following order:
- Devtools global settings properties in the
$HOME/.config/spring-boot
folder when devtools is active. @TestPropertySource
annotations on your tests.properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application.- Command line arguments.
- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). ServletConfig
init parameters.ServletContext
init parameters.- JNDI attributes from
java:comp/env
. - Java System properties (
System.getProperties()
). - OS environment variables.
- A
RandomValuePropertySource
that has properties only inrandom.*
. - Profile-specific application properties outside of your packaged jar (
application-{profile}.properties
and YAML variants). - Profile-specific application properties packaged inside your jar (
application-{profile}.properties
and YAML variants). - Application properties outside of your packaged jar (
application.properties
and YAML variants). - Application properties packaged inside your jar (
application.properties
and YAML variants). @PropertySource
annotations on your@Configuration
classes.- Default properties (specified by setting
SpringApplication.setDefaultProperties
).
自此springboot的配置项加载分析完成。