http://www.javashuo.com/article/p-uvudtich-bm.htmlhtml
上一篇文章中,咱們簡單瞭解了一下SpringApplication的run方法的代碼邏輯。其中的prepareEnvironment方法正如它的方法名錶示的意思同樣,爲當前應用準備一個Environment對象,也就是運行環境。在閱讀prepareEnvironment代碼以前,咱們先了解一下Environment。java
首先,Environment是Spring3.1才提供的一個接口。它是對當前運行的應用程序的環境的抽象,下面咱們瞭解一下它的組成。react
Environment由兩部分組成web
1)profilesspring
profile中文直譯是"概述"、"簡介"、"輪廓"的意思,但在使用spring開發應用程序的時候,咱們對profile的認識更親切的是用在劃分多環境的時候。數據結構
一般,咱們會將profile劃分紅如:開發、測試、預生產、生產環境。每一個環境會有有些bean不一樣、配置不一樣等。每一個profile將有相應的bean和配置與之匹配,那麼當咱們切換profile的時候天然也就切換了相應的bean和配置文件,從而達到在不一樣環境中快速切換避免不斷修改的問題。app
這也就是spring的java doc裏面描述的"logical group"的意思。測試
2)propertiesthis
properties的概念想必咱們已經很是熟悉了,在java中properties表明着key-value的鍵值對象集合。Environment內部設計了key-value結構的對象來存儲相應的鍵值。spa
綜上所述,Environment中包含着用於切換環境的profile,還包含着存儲鍵值對的properties。
上面的內容中,咱們瞭解了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主要的相關接口設計,設計路線也比較簡單。
前面的兩個部分,咱們瞭解了Environment包含profile和properties。也知道了Environment相關接口也主要是根據profile和properties來設計的。可是咱們並不知道具體的實現裏面profile和properties的數據結構是怎麼樣的。
從uml類圖中,咱們清晰地看到Environment的具體實現是在AbstractEnvironment這個抽象類中。咱們能夠直接打開這個類
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,那麼就會採用默認的。
咱們再看看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的層級圖
上部分的內容包括了很多介紹的內容,下面咱們簡單看看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方法看看建立過程
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應用類型。
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的對象。