所有文章
https://www.cnblogs.com/lay2017/p/11478237.html
簡介
上一篇文章中,我們簡單了解了一下SpringApplication的run方法的代碼邏輯。其中的prepareEnvironment方法正如它的方法名表示的意思一樣,為當前應用准備一個Environment對象,也就是運行環境。在閱讀prepareEnvironment代碼之前,我們先了解一下Environment。
組成
首先,Environment是Spring3.1才提供的一個接口。它是對當前運行的應用程序的環境的抽象,下面我們了解一下它的組成。
Environment由兩部分組成
1)profiles
profile中文直譯是"概述"、"簡介"、"輪廓"的意思,但在使用spring開發應用程序的時候,我們對profile的認識更親切的是用在划分多環境的時候。
通常,我們會將profile划分成如:開發、測試、預生產、生產環境。每個環境會有有些bean不同、配置不同等。每個profile將有相應的bean和配置與之匹配,那么當我們切換profile的時候自然也就切換了相應的bean和配置文件,從而達到在不同環境中快速切換避免不斷修改的問題。
這也就是spring的java doc里面描述的"logical group"的意思。
2)properties
properties的概念想必我們已經非常熟悉了,在java中properties代表着key-value的鍵值對象集合。Environment內部設計了key-value結構的對象來存儲相應的鍵值。
綜上所述,Environment中包含着用於切換環境的profile,還包含着存儲鍵值對的properties。
核心uml類圖

上面的內容中,我們了解了Environment的組成部分包括profile和properties。spring在對Environment進行設計的時候也把這兩個部分進行了隔離。
如上圖所示,PropertyResolver包含了properties相關的操作,如:getProperty(String key),Environment繼承於PropertyResolver同時也就將properties的相關能力給組合了進來。
Environment的則包含了profile的相關操作,如:getActiveProfiles()。
如果查看PropertyResolver和Environment接口的方法,我們就會發現這兩個接口都只是包含了如getter方法的獲取操作,並沒有setter樣子的操作。這或許也意味着spring希望在程序的開發運行過程中,Environment盡量是維持穩定的,而不是不斷地被修改、變化。
那么在程序啟動過程中勢必要對Environment進行配置,因此我們會看到多個繼承自Environment和PropertyResolver接口地子接口,如:ConfigurableEnvironment和ConfigurablePropertyResolver。
再往下看,AbstractEnvironment顯然包含了Environment設計地大部分實現,而從StandardEnvironment再往下走了兩個分支,也就是針對reactive和Servlet的Environment實現。
到這里,我們基本了解了Environment主要的相關接口設計,設計路線也比較簡單。
profile和properties的數據結構
前面的兩個部分,我們了解了Environment包含profile和properties。也知道了Environment相關接口也主要是根據profile和properties來設計的。但是我們並不知道具體的實現里面profile和properties的數據結構是怎么樣的。
從uml類圖中,我們清晰地看到Environment的具體實現是在AbstractEnvironment這個抽象類中。我們可以直接打開這個類
profile數據結構
AbstractEnvironment類中包含着profile的成員變量
private final Set<String> activeProfiles = new LinkedHashSet<>(); private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
profile的存儲結構看起來相對簡單,就是兩個set集合,每個profile就是單純的一個String類型的字符串表示而已。
activeProfiles表示的是當前應用中"激活"的profile集合,比如我當profile=test的時候表示當前環境是測試環境。
而defaultProfiles則表示的是默認的profile集合,也就是說如果沒有任何指定的profile,那么就會采用默認的。
properties數據結構
我們再看看AbstractEnvironment中properties的數據結構
private final MutablePropertySources propertySources = new MutablePropertySources();
前面我們一直提到,properties是一種key-value的鍵值對存儲的集合。那么也就是說MutablePropertySources這個類實現了這個概念。
我們先看看MutablePropertySources的繼承結構是怎么樣的

看起來很簡單的設計路線,Iterable接口表明MutablePropertySources像集合一樣是可以迭代的,我們可以大膽猜測其內部就是組成了一個集合。Iterable往下,就是PropertySources,這個接口表示的是PropertySource類的集合,也就是說被迭代的元素就是PropertySource。MutablePropertySources則直接繼承於PropertySources。
那么,我們基本可以想得到PropertySource這個類就是properties概念得設計,是我們主要得關注對象。
現在讓我們打開MutablePropertySources看看PropertySource的具體結構
public class MutablePropertySources implements PropertySources { private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); // 省略 }
跟我們想象得差不多,就是一個PropertySource類的集合作為成員組合在MutablePropertySources中。
我們繼續跟進PropertySource這個類,更多得了解一下
public abstract class PropertySource<T> { protected final String name; protected final T source; // 省略 }
看起來就是一個key-value的數據結構是嗎?這里請注意!跟我們想象的稍微有點不同,舉例說明
我們創建了一個config.properties文件,內容如
username=test
password=a123456
那么當config.properties這個文件被加載到內存中,並作為一個PropertySource存在的時候,name=config而非name=username或者password。也就是說,加載config.properties這樣的資源,泛型T將會是一個Map集合,而Map集合包含着config.properties文件中所有的鍵值對。
另外,我們注意到PropertySource是一個抽象類。spring將會針對資源的不同來源而使用不同的實現,例如上例中的config.properties加載到內存作為Properties對象添加的,就是PropertySource的其中一個實現類PropertiesPropertySource。
還有諸如
1)來自命令行的配置:CommandLinePropertySource
2) 來自Servlet的配置:ServletConfigPropertySource、ServletContextPropertySource
等
下面是一張PropertySource的層級圖

prepareEnvironment創建Environment
上部分的內容包括了不少介紹的內容,下面我們簡單看看SpringApplication的run方法中包含的prepareEnvironment方法,跟進方法
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments ) { // 創建一個Environment對象 ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置Environment對象 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 觸發監聽器(主要是觸發ConfigFileApplicationListener,這個監聽器將會加載如application.properties/yml這樣的配置文件) listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
該方法核心內容包括三部分
1)創建一個Environment對象
2)配置Environment對象
3)觸發ConfigFileApplicationListener監聽器(加載application.properties/yml將再后續文章中說明)
getOrCreateEnvironment
我們跟進getOrCreateEnvironment方法看看創建過程
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
在第一篇文章中,我們提到SpringApplication在deduceFromClassPath方法中會推斷出WebApplicationType具體的枚舉實例,代表了當前應用的類型。
getOrCreateEnvironment方法中根據WebApplicationType類型選擇具體的Environment類型,也就是我們提到過的Servlet類型、Reative類型或者非Web應用類型。
configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // 添加初始的properties(注意:當前並未加載如application.properties/yml的properties) configurePropertySources(environment, args); // 添加初始的profile(注意:當前並未加載如application.properties/yml配置profile) configureProfiles(environment, args); }
總結
本文,我們大體地講解了Environment的接口設計、profile和properties的數據結構設計。再從prepareEnvironment方法中看到了Environment是根據webApplicationType匹配后創建的。到這里,Environment相關的內容簡單介紹就結束了,我們也初步地為spring地Context創建了一個Environment的對象。
