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)試寫的,難免有疏漏,請大家多多指正。