SpringApplication.run()的實現(xiàn)原理

SpringApplication.run()的實現(xiàn)原理。

return new SpringApplication(sources).run(args);


一,SpringApplication初始化


return new SpringApplication(sources)


第一步:設置系統(tǒng)參數(shù)默認值。

this.bannerMode = Mode.CONSOLE;

this.logStartupInfo = true;

this.addCommandLineProperties = true;

this.headless = true;

this.registerShutdownHook = true;


第二步:設置初始化器。一共6個。

this.setInitializers();


第三步:設置監(jiān)聽器。一共13個。

this.setLisreners();

這些監(jiān)聽器會一直監(jiān)聽Spring項目的啟動流程,每當Spring容器狀態(tài)發(fā)生改變,就會出發(fā)響應的事件。

這里重點說一下這些監(jiān)聽器的加載邏輯。

首先,拿到當前類的類加載器。其實就是AppClassLoader。

然后,去對應的文件中加載類。路徑是META-INFO/spring.factories,這個路徑相信大家不會陌生,如果我們想在Spring啟動時加載我們自定義的Bean,那么我們就需要把Bean配置到這個文件中。

具體的加載實現(xiàn)可以參考SpringFactoryLoader,這個類實現(xiàn)的功能就是從META-INFO/spring.factories加載配置。


第四步:設置微服務啟動類。啟動入口。

this.mainApplicationClass = this.deduceMainApplicationClass();

加載啟動類,會根據(jù)代碼的堆棧調(diào)用關(guān)系,獲取到StackTraceElement數(shù)組,然后根據(jù)main方法,找到main方法所在的class,這個class就是啟動類。然后使用java反射機制加載啟動類。

通常我們的微服務啟動類是這樣命名的:

XxxApplication.java

這是我們微服務的入口,就是在這個入口中調(diào)用SpringApplication.run來啟動微服務的。


二,環(huán)境準備階段


思考:bootstrap.properties如何加載到內(nèi)存?


SpringApplication.run()執(zhí)行的過程中會加載bootstrap.properties的配置項到內(nèi)存。具體是執(zhí)行下面這行代碼來實現(xiàn)的:

ConfigurableEnvironment environment = this. prepareEnvironment(listeners,applicationArguments);

接下來,我們就分析一下這個環(huán)境變量的初始化流程。由于比較復雜,分步驟來說明。

prepareEnvironment(listeners,applicationArguments)方法進入以后。


第一步:this.getOrCreateEnvironment();

這行代碼的功能就像它的名字,如果當前存在環(huán)境變量ConfigurableEnvironment,則直接返回。如果不存在,則新建,這里新建的是StandardServletEnvironment,然后強轉(zhuǎn)成ConfigurableEnvironment返回。

StandardServletEnvironment繼承了StandardEnvironment,主要包含4個系統(tǒng)屬性:

servletConfigInitParams:斷點調(diào)試無具體參數(shù)。

servletContextInitParams:斷點調(diào)試無具體參數(shù)。

systemProperties:系統(tǒng)參數(shù),59個。注意這59個參數(shù),可以通過Java提供的System類直接獲取到。如下:

System.getProperties("os.name");

systemEnvironment:系統(tǒng)參數(shù),46個。


第二步:this.configEnvironment(environment,applicationArguments.getSourceArus())

配置環(huán)境變量,主要配置了2個屬性,一個是defaultProperties,另一個是當前環(huán)境使用的配置文件activeProfiles。


第三步:listeners.environmentPrepared(environment)

這個步驟主要初始化5個屬性:

bootstrap:這個屬性主要是初始化spring.config.name,默認值bootstrap。

random:生成隨機數(shù),目前不知道干啥的。

applicationConfigurationProperties:解析微服務系統(tǒng)配置文件bootstrap.properties,解析以后生成的鍵值對就存放在這個對象中。

defaultProperties:這個屬性有2個配置,

一個是spring. aop. proxyTargetClass,默認值為true,另一個是logging.pattern.level。

springCloudClientHostInfo:這個屬性包含主機名和主機IP地址,對應的key分別為:spring.cloud.client.hostname和spring.cloud.client.ipAddress。


這一步的邏輯還是很復雜的,關(guān)鍵類是這個

SimpleApplicationEventMulticaster。使用的是事件機制,通過定時任務監(jiān)聽服務端的事件。


OK,環(huán)境準備階段的工作基本就是這些。

最后我們來梳理一下環(huán)境準備階段,初始化以后得到的環(huán)境對象,這個對象主要包含9個屬性,就是上面分析的那10個屬性:

bootstrapProperties:這個屬性包含了配置中心配置的某個微服務下的xxx_sale.properties配置文件中的所有配置項。

servletConfigInitParams

servletContextInitParams

systemProperties

systemEnvironment

bootstrap

random

applicationConfigurationProperties:這個屬性包含了application.properties配置文件中的所有配置項。

applicationConfig:屬性包含了bootstrap.properties配置文件中的所有配置項。

defaultProperties

springCloudClientHostInfo



初始化系統(tǒng)環(huán)境的本質(zhì),其實就是對這些系統(tǒng)變量,系統(tǒng)參數(shù)進行初始化。


三,打印橫幅


代碼如下:

Banner printedBanner = printBanner(environment);

這行代碼的功能就是打印橫幅。什么是橫幅呢?就是我們啟動微服務時,控制臺那個經(jīng)典的“spring”。這個橫幅是支持自定義修改的,方法也很簡單,只需要在resource目錄下添加banner.txt即可,控制臺會打印banner.txt中的內(nèi)容。


四,創(chuàng)建IOC容器


// 依據(jù)是否為web環(huán)境創(chuàng)建web容器或者普通的IOC容器

context = createApplicationContext();

analyzers = new FailureAnalyzers(context);

這里說明一下,web容器指的是AnnotationConfigEmbeddedWebApplicationContext,IOC容器指的是AnnotationConfigApplicationContext。我們微服務是web環(huán)境,所以創(chuàng)建的是IOC容器。AnnotationConfigApplicationContext容器的主要功能就是Bean的注冊,所有的Bean都在這里完成注冊。

這里加載AnnotationConfigApplicationContext類,使用的是反射機制,最后生成對象后強轉(zhuǎn)成ConfigurableApplicationContext返回。

這里我們也來看看最后生成的這個context的結(jié)構(gòu)。

讀取器:AnnotatedBeanDefinitionReader

掃描器:ClassPathBeanDefinitionScanner

Bean工廠:DefaultListableBeanFactory

其他的屬性,這里不再贅述,我們只關(guān)注這3個重要的屬性。讀取器使用的依然是默認的AnnotatedBeanDefinitionReader,但是掃描器變成了ClassPathBeanDefinitionScanner,這個和注解掃描器AnnotatedBeanDefinitionScanner有所不同。Bean工廠依然是我們熟悉的DefaultListableBeanFactory,這里面有存放Bean的容器beanDefinitionMap及其他所有必要的容器和參數(shù)。

經(jīng)過創(chuàng)建環(huán)境階段以后,Spring完成了以下內(nèi)容:

1,創(chuàng)建讀取器。

2,創(chuàng)建掃描器。

3,創(chuàng)建Bean工廠。

4,創(chuàng)建其他IOC容器的必要參數(shù)和屬性。

總之,我們的Spring容器啟動了。


五,準備上下文階段


prepareContext(context, environment, listeners, applicationArguments,

printedBanner);

這一步概括來說,就是把一些系統(tǒng)啟動相關(guān)的Bean注冊到IOC容器中。


第一步:

context. setEnvironment(environment);

在ConfigurableApplicationContext中設置環(huán)境environment,這里設置的environment就是我們環(huán)境準備階段初始化的environment。


第二步:

this. postProcessApplicationContext(context);

注冊Bean:internalConfigurationBeanNameGenerstor。

設置資源加載器resourceLoader和類加載器classLoader。

// 執(zhí)行Spring配置的初始化器

this.applyInitializers(context);

初始化器會根據(jù)配置,執(zhí)行一些列的初始化操作,比如加載遠程配置文件,調(diào)整配置項優(yōu)先級等等。


第三步:

listeners.contextPrepared(context);

通知監(jiān)聽器,上下文準備就緒。


第四步:注冊Bean

springApplicationArguments

springBootBanner


第五步:加載系統(tǒng)Bean和項目XML配置文件。


第六步:

listeners.contextLoaded(context);

通知監(jiān)聽器,上下文加載完成。


六,刷新上下文


this.refreshContext(context);

刷新容器,完成組件的掃描,創(chuàng)建,加載等。刷新以后還會注冊一個鉤子registerShutdownHook,作用是在Spring容器關(guān)閉后執(zhí)行一些操作。


七,注冊一些定時任務、監(jiān)聽器狀態(tài)同步、關(guān)閉計時器等


afterRefresh(context, applicationArguments);

listeners.finished(context, null);

通知監(jiān)聽器,Spring上下文啟動完成。

stopWatch.stop();

關(guān)閉計時器。

return context;

返回Spring上下文,至此,SpringApplication.run()方法執(zhí)行結(jié)束。


OK,啰啰嗦嗦一大堆,相信大家也暈了,下面總結(jié)一下SpringApplication.run()的實現(xiàn)原理。

1,初始化SpringApplication。

設置一些系統(tǒng)參數(shù),6個初始化器,13個監(jiān)聽器,設置main方法入口。

2,環(huán)境準備階段。

初始化環(huán)境變量及其他系統(tǒng)參數(shù),為啟動做準備。這些參數(shù)有些是從jvm獲取,有些是從項目的properties文件獲取。

3,打印橫幅。

4,創(chuàng)建IOC容器。

創(chuàng)建讀取器。

創(chuàng)建掃描器。

創(chuàng)建Bean工廠。

創(chuàng)建其他IOC容器的必要參數(shù)和屬性。

總之,Spring的IOC容器創(chuàng)建好了。

5,準備上下文階段。

把之前準備的環(huán)境參數(shù)設置到IOC容器,并注冊系統(tǒng)啟動相關(guān)Bean到IOC容器。設置IOC容器的其他必要參數(shù)。

6,刷新上下文。

7,啟動定時任務,監(jiān)聽器狀態(tài)同步,關(guān)閉計時器。


純手工斷點調(diào)試寫的,難免有疏漏,請大家多多指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容