上一節(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ì)象中的涵妥。
我們接著往下繼續(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)盔憨。
即如下圖所示:
通過(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()
整體如下圖所示:
上面的邏輯看著還是比較多的檀何,但是核心就是創(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方法了概作,整體類圖如下所示:
了解了這個(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了儒喊。如下圖所示:
你可能有一個(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)建就完成了:
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è)圖概括下就好了,主要邏輯如下圖紅色箭頭所示:
通過(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ā)布!