從源碼分析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的配置項加載分析完成。