1. 程式人生 > >springboot中@ConfigurationProperties註解的作用

springboot中@ConfigurationProperties註解的作用

@ConfigurationProperties是springboot新加入的註解,主要用於配置檔案中的指定鍵值對對映到一個java實體類上。那麼它是怎麼發揮作用的呢?下面我們將揭開@ConfigurationProperties的魔法。

版本:springboot-2.0.6.RELEASE


1 概述

ConfigurationPropertiesBindingPostProcessor這個bean後置處理器,就是來處理bean屬性的繫結的,這個bean後置處理器後文將稱之為properties後置處理器。你需要知道以下幾件事:

  • ioc容器context的enviroment.propertySources
    記錄著系統屬性、應用屬性以及springboot的預設配置檔案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

參考文章