springboot中@ConfigurationProperties註解的作用
@ConfigurationProperties是springboot新加入的註解,主要用於配置檔案中的指定鍵值對對映到一個java實體類上。那麼它是怎麼發揮作用的呢?下面我們將揭開@ConfigurationProperties的魔法。
版本:springboot-2.0.6.RELEASE
1 概述
ConfigurationPropertiesBindingPostProcessor
這個bean後置處理器,就是來處理bean屬性的繫結的,這個bean後置處理器後文將稱之為properties後置處理器。你需要知道以下幾件事:
- ioc容器context的
enviroment.propertySources
application.properties
中的配置屬性等。properties後置處理器就是從其中找到匹配的配置項繫結到bean的屬性上去的。 - 屬性繫結是有覆蓋性的,作業系統環境變數可以覆蓋配置檔案application.properties, java系統屬性可以覆蓋作業系統環境變數。更多的可以參考官網 https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-external-config
2 解析流程
2.1 屬性資源有序性
上面提到過屬性資源具有優先順序,優先順序高的會覆蓋優先順序低的。這裡主要涉及到MutablePropertySources
這個類,這個類的層級關係如下
從類名上可以看出,這是一個可迭代查詢的多變的屬性資源容器,就像一個動態可擴容的容器list一樣。正如javadoc所描述的那樣,這個類提供了addFirst
,addLast
等方法,為PropertyResolver
進行有序的搜尋屬性資源提供了幫助。該類有一個非常重要的成員變數propertySourceList
,如下
public class MutablePropertySources implements PropertySources {
...
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
...
}
在springboot的啟動過程中,這個propertySourceList
會按照規則增加元素,優先順序越高的屬性資源在list容器中的索引值越小,位置越靠前。最終的一個可能結果如下:
systemProperties > systemEnvironment > random > applicationConfig
springboot解析屬性資源繫結到bean上,就是按照這個優先順序順序的。
2.2 解析基本型別屬性
如文末參考文章描述的那樣,如果現在有一個類People
,只有一個基本屬性name
,那麼配置檔案中的值是如何繫結的。
public class People {
private String name;
//getter, setter方法略
}
最終會呼叫到Binder類的findProperty方法,如下
private ConfigurationProperty findProperty(ConfigurationPropertyName name,
Context context) {
if (name.isEmpty()) {
return null;
}
//遍歷屬性資原始檔,按照上文提到的屬性資源順序,直到根據name引數找到第一個不為空的屬性
//才返回。
return context.streamSources()
.map((source) -> source.getConfigurationProperty(name))
.filter(Objects::nonNull).findFirst().orElse(null);
}
context.streamSource()
返回一個流式物件Stream<ConfigurationPropertySource>
, ConfigurationPropertySource是屬性資源的描述介面,提供了通過屬性名稱獲取特定屬性的介面方法。
我們接著看context.stream
方法做了什麼?
public Stream<ConfigurationPropertySource> streamSources() {
if (this.sourcePushCount > 0) {
return this.source.stream();
}
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
}
springboot啟動時,Binder.this.sources
實際上就是SpringConfigurationPropertySources
類。這個類有一個成員變數sources,儲存著springboot啟動過程中採集到的屬性資源,就是2.1節講到的MutablePropertySources。
/** 子類 MutablePropertySources**/
private final Iterable<PropertySource<?>> sources
lamda表示式真正流式遍歷執行的時候,會呼叫到SpringConfigurationPropertySources$SourcesIterator的重寫hasNext方法,而hasNext
方法最終會呼叫到SpringConfigurationPropertySources這個類的adapt
方法,這是一個介面卡方法,它將PropertySource屬性資源轉化為ConfigurationPropertySource。
這樣才能繼續執行流式lamda表示式中的map方法,map((source) -> source.getConfigurationProperty(name))
我們先看一下這個介面的一些主要實現類:
這裡著重關注一下SpringIterableConfigurationPropertySource
類,看一下它的getConfigurationProperty(name)
方法
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
// 呼叫父親的方法
ConfigurationProperty configurationProperty = super.getConfigurationProperty(
name);
if (configurationProperty == null) {
// 方法是在父類實現的
configurationProperty = find(getPropertyMappings(getCache()), name);
}
return configurationProperty;
}
由lamda表示式的findFirst()可知,如果第一次在systemProperties
屬性資源中找不到name對應的屬性,會再次遍歷,還會進入hasNext方法,debug的時候發現最終的屬性資源列表的儲存模型是CopyOnWriteArrayList$COWIterator
,它內部有一個指標cursor,記錄著處理過的資源的位置,所以再次遍歷是,不會從之前遍歷過的屬性資源中再去找name對應的屬性。
個人感覺SpringConfigurationPropertySources這個類內容很豐富,對屬性資源的優先順序處理就在這個類的內部私有靜態類SourcesIterator上,這裡也依託了2.1章節提到的MutablePropertySources.propertySourceList
儲存的屬性資源的有序性。
2.3 解析List
參考文章
- spring boot 原始碼解析[email protected]是如何生效的 https://blog.csdn.net/qq_26000415/article/details/78942494