springboot啟動流程(三)Environment簡介


所有文章

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的對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM