本文針對版本2.2.0.RELEASE
來分析SpringBoot的配置處理源碼,經過查看SpringBoot的源碼來弄清楚一些常見的問題好比:java
yaml
和properties
類型的配置文件?json
配置應該如何作?帶着咱們的問題一塊兒去看一下SpringBoot配置相關的源代碼,找出問題的答案。spring
SpringBoot加載配置文件的入口是由ApplicationEnvironmentPreparedEvent
事件進入的,SpringBoot會在SpringApplication的構造函數中經過spring.factories
文件獲取ApplicationListener的實例類:shell
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
...
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
}
複製代碼
spring.factories
中有一個ConfigFileApplicationListener
類,它會監聽ApplicationEnvironmentPreparedEvent
而後再加載配置文件 :json
# Application Listeners
org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener
...
複製代碼
有了事件和事件處理的類後,再找出發送事件的地方,就能夠搞清楚SpringBoot是怎麼加載配置文件的了,SpringBoot在啓動以前先初始化好SpringApplicationRunListeners這個類,它會實現SpringApplicationRunListener接口而後對事件進行轉發:bash
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
...
}
複製代碼
獲取SpringApplicationRunListeners的代碼以下:架構
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
複製代碼
一樣也會去加載spring.factories
文件,該文件有一個EventPublishingRunListener類,該類的做用就是SpringBoot的事件轉換成ApplicationEvent發送出去。app
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
複製代碼
ApplicationEnvironmentPreparedEvent
事件來加載配置文件的上面已經分析到ConfigFileApplicationListener是處理配置文件的主要類,而後進一步的查看SpringBoot是從哪些地址加載配置文件,進入ConfigFileApplicationListener類後會有兩個默認的常量:機器學習
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
private static final String DEFAULT_NAMES = "application";
複製代碼
首先在沒有任何配置的狀況下,會從DEFAULT_SEARCH_LOCATIONS
常量列出來的位置中加載文件名爲DEFAULT_NAMES(.properties或yml)
的文件,默認位置包括:ide
上面說的是沒有額外配置的狀況,SpringBoot足夠靈活能夠指定配置文件搜索路徑、配置文件名,在ConfigFileApplicationListener類中有個getSearchLocations
方法,它主要負責獲取配置搜索目錄:函數
private Set<String> getSearchLocations() {
if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
return getSearchLocations(CONFIG_LOCATION_PROPERTY);
}
Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY);
locations.addAll(
asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS));
return locations;
}
複製代碼
它的操做步驟大體以下:
spring.config.location
屬性,若是存在則直接使用它的值spring.config.additional-location
屬性中獲取搜索路徑這裏就能夠肯定SpringBoot配置的搜索路徑有兩種狀況:若是配置了spring.config.location
則直接使用,不然使用spring.config.additional-location
的屬性值 + 默認搜索路徑。
yaml
和properties
類型的配置文件?SpringBoot的配置支持properties
和yaml
文件,SpringBoot是如何解析這兩種文件的呢,繼續分析ConfigFileApplicationListener
這個類,裏面有個子類叫Loader
加載配置文件主要的工做就是由這貨負責,可是直接讀取properties
和yaml
並轉換成PropertySource
仍是由裏面的PropertySourceLoader
負責:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
...
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
複製代碼
構造Loader
對象的時候就會先加載PropertySourceLoader,加載方式仍是從spring.factories
中讀取:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
複製代碼
其中配置了兩個PropertySourceLoader的實現類:
看名字就知道是分別負責properties
和yaml
的啦。
json
配置應該如何作?若是不喜歡properties
和yaml
這兩種格式,想要定義json
作爲配置文字格式能夠直接定義json類型的PropertySourceLoader:
public class JSONPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] {"json"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
if(resource == null || !resource.exists()){
return Collections.emptyList();
}
Map<String, Object> configs = JSON.parseObject(resource.getInputStream(), Map.class);
return Collections.singletonList(
new MapPropertySource(name, configs)
);
}
}
複製代碼
而後在resources
目錄裏面創建個META-INF
,再添加個spring.factories
裏面的內容以下:
org.springframework.boot.env.PropertySourceLoader=\
com.csbaic.arch.spring.env.loader.JSONPropertySourceLoader
複製代碼
最後在resources
目錄裏面建個application.json
的配置文件 :
{
"spring.application.name": "JSONConfig"
}
複製代碼
正常啓動SpringBoot獲取spring.applicaiton.name
的配置的值就是JSONConfig
:
2019-11-02 14:50:17.730 INFO 55275 --- [ main] c.c.a.spring.env.SpringEnvApplication : JSONConfig
複製代碼
SpringBoot中有個PropertySource
接口,專門用來保存屬性常見的實現類有:
另外爲了集中管理PropertySource
還抽象出一個PropertySources
接口,PropertySources就一個實現類叫:MutablePropertySources,它將全部的PropertySource都放置在一個名叫propertySourceList
集合中,同時提供一些修改操做方法:
public void addFirst(PropertySource<?> propertySource) {}
public void addLast(PropertySource<?> propertySource) {}
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {}
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {}
public int precedenceOf(PropertySource<?> propertySource) { }
public PropertySource<?> remove(String name) {}
public void replace(String name, PropertySource<?> propertySource) {}
複製代碼
全部的PropertySource都保存在propertySourceList中,越小的索引優先級越高,因此若是想要覆蓋屬性只要保證優化級夠高就行。
繼續分析ConfigFileApplicationListener
的Loader
子類,在構造時還會建立一個PropertySourcesPlaceholdersResolver
,placeholder的解析都由它來完成:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment);
}
複製代碼
分析PropertySourcesPlaceholdersResolver發現,真正完成解析是由PropertyPlaceholderHelper
完成,PropertySourcesPlaceholdersResolver 在構造的時候就會建立一個PropertyPlaceholderHelper
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
}
複製代碼
PropertySourcesPlaceholdersResolver 在建立 PropertyPlaceholderHelper 的時候會傳遞三個參數:前綴、後綴、默認值分割符,分別由如下三個常量表示:
public static final String PLACEHOLDER_PREFIX = "${";
public static final String PLACEHOLDER_SUFFIX = "}";
public static final String VALUE_SEPARATOR = ":";
複製代碼
這樣 PropertyPlaceholderHelper 在解析placeholder時就能知道以什麼格式來解析好比:${spring.application.name}這個placeholder就會被解析成屬性值。
SpringBoot的配置很是靈活配置能夠來自文件、環境變量、JVM系統屬性、配置中心等等,SpringBoot經過 PropertySource
和PropertySources
實現屬性優先級、CRUD的統一管理,爲開發者提供統一的配置抽象。
《架構文摘》天天一篇架構領域重磅好文,涉及一線互聯網公司應用架構(高可用、高性> 能、高穩定)、大數據、機器學習等各個熱門領域。