SpringBoot成長(zhǎng)記4:Run方法中配置文件的處理

file

上一節(jié),我們主要了解了SpringBoot的一個(gè)擴(kuò)展點(diǎn)設(shè)計(jì)SpringApplicationRunListeners娩缰。并沒(méi)有找到我們想要找到的Spring容器創(chuàng)建和web容器啟動(dòng)、自動(dòng)裝配配置的這些核心功能白对。

之前我們說(shuō)過(guò)逝慧,xxxxEnvironment表示了配置文件的封裝,這一節(jié)就讓我們來(lái)看下笑陈,SpringBoot啟動(dòng)過(guò)程中,如何通過(guò)處理配置文件葵袭,設(shè)置到Environment對(duì)象中的涵妥。

file

我們接著往下繼續(xù)分析run方法,會(huì)看到如下代碼:

public ConfigurableApplicationContext run(String... args) {
    //1坡锡、擴(kuò)展點(diǎn) SpringApplicationRunListeners listeners.starting();
    //2蓬网、配置文件的處理(待分析)
     ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     ConfigurableEnvironment environment = prepareEnvironment(listeners,
      applicationArguments);
     configureIgnoreBeanInfo(environment);
     Banner printedBanner = printBanner(environment);
     //其他邏輯
}

我們還是抓大放小,核心關(guān)注這一段邏輯鹉勒,明顯是反復(fù)出現(xiàn)了environment這個(gè)詞匯相關(guān)的代碼帆锋,肯定就是這里處理的配置文件。

這段代碼主要的邏輯可以概括如下:

1)DefaultApplicationArguments命令行參數(shù)的解析和封裝禽额,也不是我們關(guān)注的重點(diǎn)

2)prepareEnvironment這個(gè)方法應(yīng)該就是真正配置文件的處理邏輯

3)后面這兩個(gè)方法窟坐,configureIgnoreBeanInfo、printBanner绵疲,明細(xì)就是基于配置設(shè)置一個(gè)參數(shù)、打印一下Banner日志輸出而已臣疑,一看就不是重點(diǎn)盔憨。

即如下圖所示:

file

通過(guò)上面的初步分析,我們可以看到讯沈,核心關(guān)注的應(yīng)該是prepareEnvironment這個(gè)方法的邏輯郁岩,那接下來(lái)就讓我們看看它是如何處理的配置文件吧。

SpringBoot的配置文件解析如何設(shè)計(jì)的缺狠?

在分析SpringBoot prepareEnvironment方法是如何處理配置文件之前问慎,你可以思考下,如果讓你編寫配置文件的解析挤茄,你會(huì)怎么考慮呢如叼?你可能會(huì)考慮:

從哪里找到配置文件,之后根據(jù)文件格式解析下配置文件穷劈,那每個(gè)配置文件我都可以抽象為一個(gè)對(duì)象笼恰,對(duì)象中可以有配置文件的名稱踊沸,位置,具體配置值等等社证。最后配置文件可能是多個(gè) 逼龟,需要一個(gè)集合來(lái)存放這些對(duì)象。構(gòu)成一個(gè)列表追葡,表示多個(gè)配置文件解析后的結(jié)果腺律。

這個(gè)思路其實(shí)你稍微思考下,就可以得出宜肉。

那么SpringBoot其實(shí)也沒(méi)有什么高深的匀钧,它大體也是這個(gè)思路,你有這個(gè)思路崖飘,再去理解代碼榴捡,其實(shí)就會(huì)輕松很多。這個(gè)思想是我想要教給你們的朱浴,對(duì)理解代碼會(huì)非常有用吊圾。

讓我們來(lái)一起看下代碼:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

這個(gè)方法的脈絡(luò),大體就是:

1)創(chuàng)建了一個(gè)ConfigurableEnvironment

2)之后不斷往里面設(shè)置或者放了一些值翰蠢,很可能就是各種配置文件的解析后的結(jié)果项乒。configureEnvironment、attach之類的梁沧。

3)中間還執(zhí)行了關(guān)鍵的listeners的擴(kuò)展點(diǎn)的一個(gè)方法—environmentPrepared()

整體如下圖所示:

file

上面的邏輯看著還是比較多的檀何,但是核心就是創(chuàng)建了ConfigurableEnvironment,之后給它設(shè)置了一堆屬性而已廷支。

創(chuàng)建的ConfigurableEnvironment配置對(duì)象抽象了哪些東西频鉴?

ConfigurableEnvironment是通過(guò)一個(gè)方法getOrCreateEnvironment()創(chuàng)建得來(lái)的。

你可以打開接口看下它的脈絡(luò)恋拍,而ConfigurableEnvironment這個(gè)類時(shí)一個(gè)接口垛孔,定義了一些和配置相關(guān)方法。

profile表示多環(huán)境配置看施敢,MutablePropertySources是個(gè)很關(guān)鍵的對(duì)象周荐,內(nèi)部包含了一個(gè)List<PropertySource>對(duì)象,這個(gè)你可以猜想到它就是對(duì)配置文件的抽象對(duì)象,每一個(gè)PropertySource表示一個(gè)配置文件僵娃。其余的就是一些get方法了概作,整體類圖如下所示:

file

了解了這個(gè)接口,你大體應(yīng)該有個(gè)印象了默怨, 配置文件抽象的關(guān)鍵點(diǎn)讯榕,就是PropertySource+profile封裝到ConfigurableEnvironment這個(gè)接口實(shí)現(xiàn)類中。

這個(gè)其實(shí)跟我們之前提到過(guò)的匙睹,讓你自己設(shè)計(jì)配置文件的解析的思想很類似的瘩扼。你可以思考下這里的亮點(diǎn):

1)PropertySource name可以用作配置文件名稱谆甜,T source屬性是泛型,可以放常見的key-value的properties和yml的配置文件解析結(jié)果集绰,此時(shí)T就是一個(gè)Map规辱,也可以放其他配置格式解析成的對(duì)象,這里沒(méi)有限制的栽燕。這點(diǎn)設(shè)計(jì)非常好罕袋。

2)通過(guò)List<PropertySource>統(tǒng)一管理多個(gè)配置文件,并且通過(guò)MutablePropertySources封裝對(duì)這個(gè)集合的常見操作碍岔。

3)至于profile的設(shè)計(jì)浴讯,就是支持多環(huán)境配置,只要通過(guò)一個(gè)Set<String> profile集合就可以實(shí)現(xiàn)蔼啦,這個(gè)思路其實(shí)很簡(jiǎn)單榆纽。

創(chuàng)建時(shí)候默認(rèn)添加了那些配置文件的解析?

當(dāng)你知道了上面的設(shè)計(jì)思想捏肢,那么理解代碼就不困難了奈籽。首先是創(chuàng)建Environment的代碼:

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();
   }
}

根據(jù)webApplicationType應(yīng)用類型,之前創(chuàng)建SpringApplication時(shí)推斷過(guò)鸵赫,默認(rèn)是SERVLET衣屏。所以這里創(chuàng)建了一個(gè)實(shí)現(xiàn)類是StandardServletEnvironment。

而這個(gè)實(shí)現(xiàn)類辩棒,默認(rèn)加載了幾個(gè)配置狼忱。雖然默認(rèn)構(gòu)造方法為空,但是父類的構(gòu)造方法調(diào)用了customizePropertySources一睁,實(shí)際還是初始化了一些配置文件钻弄。代碼如下:

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

    public StandardServletEnvironment() {
    }

    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }

    public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {
        WebApplicationContextUtils.initServletPropertySources(this.getPropertySources(), servletContext, servletConfig);
    }
}

public class StandardEnvironment extends AbstractEnvironment {

    /** System environment property source name: {@value}. */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value}. */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(
                new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(
                new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

}

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    //省略其他
    public AbstractEnvironment() {
        customizePropertySources(this.propertySources);
    }
}

可以看出來(lái)默認(rèn),創(chuàng)建的時(shí)候調(diào)用了父類的構(gòu)造函數(shù)者吁,配置文件主要添加了如下:

1)servletConfigInitParams斧蜕、servletContextInitParams,從名字上看砚偶,我們可以連蒙帶猜下,stub應(yīng)該是表示是樁洒闸,可能是預(yù)留存儲(chǔ)servlet相關(guān)配置用的染坯,默認(rèn)都是空的值。jndiProperties配置根據(jù)條件加載丘逸,默認(rèn)沒(méi)有加載单鹿。它配置的值默認(rèn)是空的Object()

2)systemEnvironment和systemProperties配置,從名字可以猜出來(lái)深纲,是系統(tǒng)屬性和系統(tǒng)環(huán)境變量相關(guān)配置,配置的值解析后為Map

最終將所有的配置解析好放入到了List<PropertySource>中仲锄。

也就是執(zhí)行完創(chuàng)建劲妙,此時(shí)已經(jīng)相當(dāng)于構(gòu)建了4個(gè)配置對(duì)象了,也就是4個(gè)PropertySource了儒喊。如下圖所示:

file

你可能有一個(gè)疑惑镣奋,這些不同的配置是怎么來(lái)的?

其實(shí)怀愧,你細(xì)心思考下侨颈,你會(huì)發(fā)現(xiàn),這些配置有的是自己構(gòu)建的芯义,有的是從系統(tǒng)環(huán)境變量加載的哈垢,有的是從配置文件讀取的。

通過(guò)抽象了一個(gè)接口PropertySource扛拨。定義不同的實(shí)現(xiàn)耘分,來(lái)實(shí)現(xiàn)配置文件的解析,這個(gè)設(shè)計(jì)思想值得我們學(xué)習(xí)绑警,這就是經(jīng)典的java面向接口的多態(tài)編程思想求泰。

目前為止配置文件處理創(chuàng)建就完成了:

file

ConfigurableEnvironment其他的添磚加瓦操作

創(chuàng)建了ConfigurableEnvironment之后,執(zhí)行了其他的一些方法待秃。核心思路主要是給ConfigurableEnvironment設(shè)置其他的一些屬性拜秧,比如轉(zhuǎn)換器、profile章郁,在增加一些PropertySource而已枉氮。

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        //1.創(chuàng)建Environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //2.ConfigurableEnvironment其他的添磚加瓦操作
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

上面代碼的這些添磚加瓦的我就不仔細(xì)介紹了,主要就是設(shè)置一些屬性暖庄,或者添加了配置文件對(duì)象聊替。直接給大家畫一個(gè)圖概括下就好了,主要邏輯如下圖紅色箭頭所示:

file

通過(guò)上圖培廓,可以看到惹悄,主要執(zhí)行的邏輯,具體如下:

1)configureEnvironment

 a) environment.setConversionService 設(shè)置(默認(rèn)轉(zhuǎn)換器Service) ApplicationConversionService 單例初始化肩钠,不是重點(diǎn)泣港;

 b) configurePropertySources() 命令行參數(shù)的覆蓋一些屬性,默認(rèn)不傳价匠,什么都不做当纱,**不是重點(diǎn);**

 c) configureProfiles() 判斷是否有 spring.acitve.profiles踩窖,默認(rèn)沒(méi)有指定 坡氯,設(shè)置activeprifles(List)就是空。這個(gè)值得了解下,啟動(dòng)時(shí)的參數(shù)如果增加了這個(gè)箫柳,在這里就會(huì)生效的手形,如spring.acitve.profiles=dev,prod,這里會(huì)記錄下悯恍。activeprifles=dev,prod库糠。(作用的話,其實(shí)是用于之后會(huì)額外補(bǔ)充加載配置文件坪稽,也就是補(bǔ)充PropertySource曼玩,這個(gè)一會(huì)兒在listeners.environmentPrepared分析中會(huì)看到的。)

2)ConfigurationPropertySources.attach(environment) 添加 一套配置配置文件到List開頭窒百,也就是默認(rèn)添加了一個(gè)configurationProperties名字的PropertySource黍判,或者說(shuō)添加了一個(gè)Environment,因?yàn)镋nvironment封裝了PropertySource篙梢,這個(gè)配置從注釋上看顷帖,可能是為了動(dòng)態(tài)的添加和配置一些值用的,知道attach是補(bǔ)充了一個(gè)配置文件的對(duì)象封裝就可以了渤滞,其他的暫時(shí)不是重點(diǎn)贬墩;

3)listeners.environmentPrepared(environment),事件發(fā)布Listener 發(fā)布一個(gè)環(huán)境事件 ApplicationEnvironmentPreparedEvent妄呕。配置文件的處理的擴(kuò)展點(diǎn)核心做了些什么陶舞?按事件篩選過(guò)的listener,輪流進(jìn)行一堆事件處理(和之前發(fā)布ApplicationStartEvent事件一樣的機(jī)制),重要邏輯绪励,補(bǔ)充了配置文件肿孵。

ConfigFileApplicationListener加載配置的核心處理,很關(guān)鍵疏魏,通過(guò)Loader停做,propertySourceLoaders 解析property(properties,xml) yaml文件的(yml,yaml)從而補(bǔ)充了新的PropertySource大莫。

AnsiOutputApplicationListener ANSI字符編碼處理

LoggingApplicationListener 日志相關(guān)

ClasspathLoggingApplicationListener 什么都沒(méi)做

Backgroundpreinitializer 什么都沒(méi)做

DelegatingApplicationListener 什么都沒(méi)做

FileEncodingApplicationListener 編碼相關(guān)蛉腌,不重要

4)bindToSpringApplication(environment) 設(shè)置environment到SpringApplication (但是實(shí)際沒(méi)有設(shè)置上...比較奇怪,不重要的邏輯只厘,不深究了)烙丛,不是重點(diǎn);

5)EnvironmentConverter 如果environment不是webApplicationType指定環(huán)境配置羔味,這里轉(zhuǎn)換成StandardServletEnvironment(默認(rèn)SERVLET) 之前已經(jīng)是河咽,所以之類不轉(zhuǎn)換了,不是重點(diǎn)介评;

6)ConfigurationPropertySources.attach(environment) 再次attach 擔(dān)心轉(zhuǎn)換后,這個(gè)屬性不在了,不是重點(diǎn)们陆;

通過(guò)上面的圖和說(shuō)明寒瓦,基本解釋了ConfigurableEnvironment的各種添磚加瓦的操作。其實(shí)如果你抓大放小其實(shí)這些都不是算是關(guān)鍵坪仇,真正的關(guān)鍵就是創(chuàng)建和封裝了ConfigurableEnvironment杂腰,另一個(gè)比較重要的就是執(zhí)行擴(kuò)展點(diǎn)了。也就是執(zhí)行l(wèi)isteners.environmentPrepared(environment),補(bǔ)充了Spring在ClassPath下的相關(guān)配置文件對(duì)象

小結(jié)

今天我們主要分析了SpringBoot在啟動(dòng)過(guò)程中:

1)配置文件對(duì)象的抽象封裝如何設(shè)計(jì)的

2)listeners擴(kuò)展點(diǎn)椅文,對(duì)配置文件的處理做了擴(kuò)展喂很,補(bǔ)充了Spring在ClassPath下的相關(guān)配置文件對(duì)象

本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皆刺,一起剝皮案震驚了整個(gè)濱河市少辣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羡蛾,老刑警劉巖漓帅,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異痴怨,居然都是意外死亡忙干,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門浪藻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捐迫,“玉大人,你說(shuō)我怎么就攤上這事爱葵∈┐鳎” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵钧惧,是天一觀的道長(zhǎng)暇韧。 經(jīng)常有香客問(wèn)我,道長(zhǎng)浓瞪,這世上最難降的妖魔是什么懈玻? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮乾颁,結(jié)果婚禮上涂乌,老公的妹妹穿的比我還像新娘。我一直安慰自己英岭,他們只是感情好湾盒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诅妹,像睡著了一般罚勾。 火紅的嫁衣襯著肌膚如雪毅人。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天尖殃,我揣著相機(jī)與錄音丈莺,去河邊找鬼。 笑死送丰,一個(gè)胖子當(dāng)著我的面吹牛缔俄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播器躏,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼俐载,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了登失?” 一聲冷哼從身側(cè)響起遏佣,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎壁畸,沒(méi)想到半個(gè)月后贼急,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捏萍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年太抓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片令杈。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡走敌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逗噩,到底是詐尸還是另有隱情掉丽,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布异雁,位于F島的核電站捶障,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纲刀。R本人自食惡果不足惜项炼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望示绊。 院中可真熱鬧锭部,春花似錦、人聲如沸面褐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)展哭。三九已至湃窍,卻和暖如春闻蛀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背您市。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工循榆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墨坚。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像映挂,于是被迫代替她去往敵國(guó)和親泽篮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容