Spring Environment的加載

 這節介紹environment,默認環境變量的加載以及初始化。前端

 以前在介紹spring啓動過程講到,第一步進行環境準備時就會初始化一個StandardEnvironment。下圖爲Environment類圖的接口,能夠分爲4塊內容:spring

  1. ConversionService(藍):類型轉換服務shell

  2. PropertySource(綠):鍵值對數據源緩存

  3. PropertyResolver(紅):鍵值對服務,包括類型轉換ui

  4. Environment(紫):環境配置數據服務3d

file

1.ConversionService

 提供了類型轉換服務,能將源目標轉換爲目標類型,同時提供了管理功能,內部維護了各種型轉換映射關係。其實從ConversionService和ConverterRegistry接口就能看出該模塊的功能,以下:code

file

 ConversionService接口爲主要的對外功能接口,提供查詢的能力。對象

file

 ConverterRegistry接口爲主要的管理接口,提供添加和刪除的能力。而ConfigurableConversionService繼承自上面兩者,則提供了Converter的CRUD功能。結構上也延續了Spring固有的風格,將執行接口做爲主要功能對外提供單一的接口,再經過繼承的方式,以Configurable開頭的子接口,擴展出管理功能,使得責任分離更加立體。blog

 接下來是GenericConversionService類,該類提供了接口所有實現,下圖展現了其主要實現:繼承

file

 GenericConversionService結構上能夠說是一個小型的管理系統,內部維護了一個Converters對象,用於「底層」管理全部的GenericConverter。同時還維護了一個ConcurrentReferenceHashMap用於緩存經常使用的GenericConverter。

 Converters在存儲GenericConverter時還進行了分類,若是GenericConverter有指定可以解析的類別(ConvertiablePair:包括SourceType和TargetType)時,則使用一個LinkedHashMap按Key Value進行存儲,在存儲時會遍歷可解析的類別,將該GenericConverter追加到對應的Value列表末尾,於是能夠看到該Map的Value是一個LinkedList。對應沒有指定能解析的類別的GenericConverter,則直接放到LinkedHashSet維護的集合中。

 Converters在查詢時會遍歷源類型和目標類型的組合結果,以查找匹配的目標GenericConverter對象。以下:

file

 對於getRegisteredConverter方法,會先使用Key從LinkedHashMap中查找是否有匹配的Converter,再遍歷相應的Value,查找到能處理的轉換器。若Map中沒法查到,則遍歷LinkedHashSet,以查到到能處理的轉換器。

 由上知道,Converters在查找時存在屢次遍歷列表的過程,在頻率過多時效率會比較低下,於是GenericConversionService內部維護了一個ConcurrentReferenceHashMap提供緩存的功能,該Map提供了同ConcurrentHashMap相同的功能,可是可以存儲對應的軟引用,從而能在內存不足時自動進行內存回收。在查到轉換器時,會先試着從緩存中查找,若是獲取不到,則會轉而從Converters中查找,當從Converters中查找到後便會put到ConcurrentReferenceHashMap緩存中。

 DefaultConversionService是一個單例,繼承自GenericConversionService,在初始化後自動添加了默認的轉換器,包括Scalar相關的、集合相關的等轉換器。

2.PropertySource

 PropertySource表明了一個包含鍵值對的數據源。從類定義上看,有一個表示數據源名字的name字段,還有一個表示具體數據源泛型T的source字段。而數據源的設置則是經過構造方法傳入的,同時方法提供了經過鍵名獲取鍵值的抽象方法getProperty。此外還有其餘抽象方法,如containsProperty等。

 EnumerablePropertySource繼承自PropertySource,增長了getPropertyNames方法,要求子類返回內存持有的鍵名列表。同時實現了containsProperty方法,經過判斷所給的鍵名是否存在上述返回的鍵名列表中從而判斷是否包含該鍵名。

 MapPropertySource繼承自EnumerablePropertySource,顧名思義,內部經過Map維護各鍵值對內容。相似的還有PropertiesPropertySource,內部經過Properties維護各鍵值對內容。

 SystemEnvironmentPropertySource是MapPropertySource的裝飾器,繼承自MapPropertySource,爲其添加了鍵名轉換功能,以應對環境變量、shell參數的環境。在經過鍵名獲取鍵值時,會先根據原鍵名進行查找,查找不到則經過對鍵進行轉換再嘗試查找,具體查找過程爲:

  1. 經過name查找

  2. 將name中的 . 轉換爲 _ 查找

  3. 將name中的 – 轉換爲 _ 查找

  4. 將name中的 . 和 _ 轉換爲 – 查找

  5. 將name轉換爲大寫,再進行(1) - (4)的過程

 PropertySources的實現以下,擴展了PropertySource接口,將單個數據源的能力擴展到了多個。MutablePropertySources做爲PropertySources的實現,內部維護了一個List對象,用以存儲多個數據源,並將自身的行爲封裝爲List。

file

3.PropertyResolver

 PropertyResolver定義了一系列接口,以提供了對外根據鍵名獲取相應值的功能,同時提供了類型轉換和佔位符替換的功能,是ConversionService和PropertySource的結合。ConfigurablePropertyResolver接口繼承自PropertyResolver接口,老規則,擴展了設置的功能,主要是設置類型轉換器和佔位符的相關屬性。

 AbstractPropertyResolver提供了除PropertySource功能外的其他實現。使用DefaultConversionService做爲默認的類型轉換實現,使用 ${ 和 } 做爲佔位符的先後綴,使用:做爲默認值分割符,同時引入PropertyPlaceholderHelper用於佔位符的解析和替換。而getProperty的實現則留到了了子類PropertySourcesPropertyResolver中,其引入了PropertySources用以維護多個鍵值對數據源。獲取指定屬性值過程以下:

file

 經過遍歷數據源的方式,查到對應的值後,會進行佔位符的替換,替換完佔位符後會進行類型的轉換。類型轉換直接用的DefaultConversionService,這個上面已經介紹過了,下面介紹佔位符替換。

 佔位符替換的功能是在PropertyResolver接口中定義的,分爲嚴格和不嚴格模式,以下:

file

 resolvePlaceholders爲不嚴格模式,若是無法替換佔位符,則直接忽略,resolveRequiredPlaceholders爲嚴格模式,若是佔位符無法替換則會拋出異常。如上面說的,AbstractPropertyResolver實現時都委託給PropertyPlaceholderHelper的replacePlaceholders方法。

file

 如上,該方法要求傳入一個源字符串,同時提供一個PlaceholderResolver數據源,一遍解析出佔位符內容後可以從數據源中獲取對應的值。爲了保持類功能的單一職責,從而增長了一個內部接口PlaceholderResolver。上面提到,在這個模塊中的鍵值對數據源都是由PropertySourcesPropertyResolver維護的,事實上上面方法截圖的實現中,getPropertyAsRawString方法也確實是由PropertySourcesPropertyResolver提供實現的,下面看下佔位符的解析。

file

 佔位符的解析過程如上流程,主要過程爲:

  1. 根據${前綴獲得startIndex

  2. 查找跟${前綴配對的}後綴,如${xxx${yy}z},獲得第二個}後綴的下標endIndex

  3. 截取${和}中間的內容獲得placeholder

  4. 因爲placeholder的內容可能也可能包含佔位符,於是要遞歸處理placeholder,既佔位符能夠嵌套,內層的結果能夠當作外層的Key使用

  5. placeholder解析完後,將其做爲Key從鍵值對源中獲取對應的值propVal

  6. 若是propVal值爲空,則判斷是否存在:分割符,若是有分割符,則進行分割,並使用前端內容做爲Key再次查找值。若該次查找結果不爲空,則使用該次結果爲propVal的值,不然使用第二段內容做爲默認值

  7. 若第(5)/(6)步中propVal結果不爲空,則判斷從鍵值對源中獲取的值是否也有佔位符,如有佔位符,則再次進行解析,若沒有,則將結果替換回原字段中,更新startIndex,繼續下次解析。

  8. 若第(5)/(6)步中propVal結果不空,則會根據設置的解析模式來判斷下一步行爲,若是未不嚴格模式,則跳過該次內容,更新startIndex,繼續下一次解析,若爲嚴格模式,則拋出異常,流程結束。

 下面以一個例子進行演示,以下

file

輸出結果爲:

file

 若將解析模式設置爲嚴格模式則會拋出異常

4.Environment

 Environment繼承自PropertyResolver接口,增長了Profiles功能,即咱們平時看到的,多環境特性,可以在不一樣環境下加載不一樣的配置。ConfigurableEnvironment繼承自Environment,老規矩,又是添加了修改的擴展接口,同時增長了獲取系統參數的接口。另外,該接口也繼承自ConfigurablePropertyResolver,有了鍵值對數據源管理、獲取和處理的能力,集合Environment接口的功能,可以達到在不一樣環境下經過加載不一樣配置源實現環境隔離的效果。

 AbstractEnvironment是ConfigurablePropertyResolver的實現,提供了默認的環境源default,同時內部組合使用PropertySourcesPropertyResolver做爲PropertyResolver的實現。

 它還維護了一個MutablePropertySources對象,用於存儲多個數據源,在Context的父子上下文中,經過merge方法,可以將父上文中的環境變量內容添加進來(在AbstractApplicationContext設置父Context時,會將父Environment進行合併)。同時還有一個方法customizePropertySources,會在構造方法中進行調用,開放給子類添加默認的鍵值對源,以下:

file

 最後是StandardEnvironment類,繼承自AbstractEnvironment,重寫了customizePropertySources方法,在該方法中添加了系統相關的屬性和應用環境變量相關的屬性的鍵值對源。以下

file

 而這兩個數據源來自於前面提到的PropertySource實現。其中,系統相關屬性SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME的數據源來源於System.getProperties(),而應用環境變量相關屬性SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME則來源於System.getenv()

file

我的公衆號:啊駝

相關文章
相關標籤/搜索