源碼版本
作者Spring Boot
是基于2.4.0
贵涵。每個(gè)版本有些變化列肢,讀者盡量和我保持一致,以防源碼有些出入宾茂。
從哪入手瓷马?
相信很多人嘗試讀過(guò)Spring Boot
的源碼,但是始終沒(méi)有找到合適的方法跨晴。那是因?yàn)槟銓?duì)Spring Boot
的各個(gè)組件欧聘、機(jī)制不是很了解,研究起來(lái)就像大海撈針端盆。
至于從哪入手不是很簡(jiǎn)單的問(wèn)題嗎怀骤,當(dāng)然主啟動(dòng)類了费封,即是標(biāo)注著@SpringBootApplication
注解并且有著main()
方法的類,如下一段代碼:
@SpringBootApplication
public class AnnotationDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AnnotationDemoApplication.class, args);
}
}`
話不多說(shuō)蒋伦,DEBUG
伺候弓摘,別怕,搞它........
源碼如何切分痕届?
SpringApplication
中的靜態(tài)run()
方法并不是一步完成的韧献,最終執(zhí)行的源碼如下:
//org.springframework.context.ConfigurableApplicationContext
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
很顯然分為兩個(gè)步驟,分別是創(chuàng)建SpringApplication
和執(zhí)行run()
方法爷抓,下面將分為這兩個(gè)部分介紹势决。
如何創(chuàng)建SpringApplication?
創(chuàng)建即是new
對(duì)象了蓝撇,DEBUG
跟進(jìn)代碼果复,最終執(zhí)行的SpringApplication
構(gòu)造方法如下圖:
如上圖中標(biāo)注的注釋,創(chuàng)建過(guò)程重用的其實(shí)分為②
渤昌、③
虽抄、④
這三個(gè)階段,下面將會(huì)一一介紹每個(gè)階段做了什么事独柑。
設(shè)置應(yīng)用類型
這個(gè)過(guò)程非常重要迈窟,直接決定了項(xiàng)目的類型,應(yīng)用類型分為三種忌栅,都在WebApplicationType
這個(gè)枚舉類中车酣,如下:
-
NONE
:顧名思義,什么都沒(méi)有索绪,正常流程走湖员,不額外的啟動(dòng)web容器
,比如Tomcat
瑞驱。 -
SERVLET
:基于servlet
的web程序娘摔,需要啟動(dòng)內(nèi)嵌的servlet
web容器,比如Tomcat
唤反。 -
REACTIVE
:基于reactive
的web程序凳寺,需要啟動(dòng)內(nèi)嵌reactive
web容器,作者不是很了解彤侍,不便多說(shuō)肠缨。
判斷的依據(jù)很簡(jiǎn)單,就是加載對(duì)應(yīng)的類拥刻,比如加載了DispatcherServlet
等則會(huì)判斷是Servlet
的web程序怜瞒。源碼如下:
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
這里我引入了spring-boot-starter-web
,肯定是Servlet
的web程序。
設(shè)置初始化器(Initializer)
初始化器ApplicationContextInitializer
是個(gè)好東西,用于IOC
容器刷新之前初始化一些組件吴汪,比如ServletContextApplicationContextInitializer
惠窄。
那么如何獲取初始化器呢?跟著上圖中的代碼進(jìn)入漾橙,在SpringApplication
中的如下圖中的方法:
相對(duì)重要的就是第一步獲取初始化器的名稱了杆融,這個(gè)肯定是全類名
了,詳細(xì)源碼肯定在loadFactoryNames()
方法中了霜运,跟著源碼進(jìn)入脾歇,最終調(diào)用的是#SpringFactoriesLoader.loadSpringFactories()
方法。
loadSpringFactories()
方法就不再詳細(xì)解釋了淘捡,其實(shí)就是從類路徑META-INF/spring.factories
中加載ApplicationContextInitializer
的值藕各。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下圖:
上圖中的只是一部分初始化器,因?yàn)?code>spring.factories文件不止一個(gè)焦除。
下圖中是我的demo
中注入的初始化器激况,現(xiàn)實(shí)項(xiàng)目中并不止這些。
這也告訴我們自定義一個(gè)
ApplicationContextInitializer
只需要實(shí)現(xiàn)接口膘魄,在spring.factories
文件中設(shè)置即可乌逐。
設(shè)置監(jiān)聽(tīng)器(Listener)
監(jiān)聽(tīng)器(ApplicationListener
)這個(gè)概念在Spring
中就已經(jīng)存在,主要用于監(jiān)聽(tīng)特定的事件(ApplicationEvent
)创葡,比如IOC容器刷新浙踢、容器關(guān)閉等。
Spring Boot
擴(kuò)展了ApplicationEvent
構(gòu)建了SpringApplicationEvent
這個(gè)抽象類灿渴,主要用于Spring Boot
啟動(dòng)過(guò)程中觸發(fā)的事件洛波,比如程序啟動(dòng)中、程序啟動(dòng)完成等骚露。如下圖:
監(jiān)聽(tīng)器如何獲确芩辍?從源碼中知道其實(shí)和初始化器(ApplicationContextInitializer
)執(zhí)行的是同一個(gè)方法荸百,同樣是從META-INF/spring.factories
文件中獲取。
在spring-boot-autoconfigure
的spring.factories
文件中的值如下圖:
spring.factories
文件不止一個(gè)滨攻,同樣監(jiān)聽(tīng)器也不止以上這些够话。
作者demo
中注入的一些監(jiān)聽(tīng)器如下圖:
總結(jié)
SpringApplication
的構(gòu)建都是為了run()
方法啟動(dòng)做鋪墊,構(gòu)造方法中總共就有幾行代碼光绕,最重要的部分就是設(shè)置應(yīng)用類型女嘲、設(shè)置初始化器、設(shè)置監(jiān)聽(tīng)器诞帐。
「注意」:初始化器和這里的監(jiān)聽(tīng)器都要放置在
spring.factories
文件中才能在這一步驟加載欣尼,否則不會(huì)生效,因此此時(shí)IOC容器
還未創(chuàng)建,即使將其注入到IOC容器
中也是不會(huì)生效的愕鼓。
作者簡(jiǎn)單的畫(huà)了張執(zhí)行流程圖钙态,僅供參考,如下:
執(zhí)行run()方法
上面分析了SpringApplication
的構(gòu)建過(guò)程菇晃,一切都做好了鋪墊册倒,現(xiàn)在到了啟動(dòng)的過(guò)程了。
作者根據(jù)源碼將啟動(dòng)過(guò)程分為了「8步」磺送,下面將會(huì)一一介紹驻子。
1. 獲取、啟動(dòng)運(yùn)行過(guò)程監(jiān)聽(tīng)器
SpringApplicationRunListener
這個(gè)監(jiān)聽(tīng)器和ApplicationListener
不同估灿,它是用來(lái)監(jiān)聽(tīng)?wèi)?yīng)用程序啟動(dòng)過(guò)程的崇呵,接口的各個(gè)方法含義如下:
public interface SpringApplicationRunListener {
// 在run()方法開(kāi)始執(zhí)行時(shí),該方法就立即被調(diào)用馅袁,可用于在初始化最早期時(shí)做一些工作
void starting();
// 當(dāng)environment構(gòu)建完成域慷,ApplicationContext創(chuàng)建之前,該方法被調(diào)用
void environmentPrepared(ConfigurableEnvironment environment);
// 當(dāng)ApplicationContext構(gòu)建完成時(shí)司顿,該方法被調(diào)用
void contextPrepared(ConfigurableApplicationContext context);
// 在ApplicationContext完成加載芒粹,但沒(méi)有被刷新前,該方法被調(diào)用
void contextLoaded(ConfigurableApplicationContext context);
// 在ApplicationContext刷新并啟動(dòng)后大溜,CommandLineRunners和ApplicationRunner未被調(diào)用前化漆,該方法被調(diào)用
void started(ConfigurableApplicationContext context);
// 在run()方法執(zhí)行完成前該方法被調(diào)用
void running(ConfigurableApplicationContext context);
// 當(dāng)應(yīng)用運(yùn)行出錯(cuò)時(shí)該方法被調(diào)用
void failed(ConfigurableApplicationContext context, Throwable exception);
}
如何獲取運(yùn)行監(jiān)聽(tīng)器?
在SpringApplication#run()
方法中钦奋,源碼如下:
//從spring.factories中獲取監(jiān)聽(tīng)器
SpringApplicationRunListeners listeners = getRunListeners(args);
跟進(jìn)getRunListeners()
方法座云,其實(shí)還是調(diào)用了loadFactoryNames()
方法從spring.factories
文件中獲取值,如下:
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
最終注入的是EventPublishingRunListener
這個(gè)實(shí)現(xiàn)類付材,創(chuàng)建實(shí)例過(guò)程肯定是通過(guò)反射了朦拖,因此我們看看它的構(gòu)造方法,如下圖:
這個(gè)運(yùn)行監(jiān)聽(tīng)器內(nèi)部有一個(gè)事件廣播器(SimpleApplicationEventMulticaster
)厌衔,主要用來(lái)廣播特定的事件(SpringApplicationEvent
)來(lái)觸發(fā)特定的監(jiān)聽(tīng)器ApplicationListener
璧帝。
EventPublishingRunListener
中的每個(gè)方法用來(lái)觸發(fā)SpringApplicationEvent
中的不同子類。
如何啟動(dòng)運(yùn)行監(jiān)聽(tīng)器富寿?
在SpringApplication#run()
方法中睬隶,源碼如下:
//執(zhí)行starting()方法
listeners.starting(bootstrapContext, this.mainApplicationClass);
執(zhí)行SpringApplicationRunListeners
的starting()
方法,跟進(jìn)去其實(shí)很簡(jiǎn)單页徐,遍歷執(zhí)行上面獲取的運(yùn)行監(jiān)聽(tīng)器苏潜,這里只有一個(gè)EventPublishingRunListener
。因此執(zhí)行的是它的starting()
方法变勇,源碼如下圖:
上述源碼中邏輯很簡(jiǎn)單恤左,其實(shí)只是執(zhí)行了multicastEvent()
方法,廣播了ApplicationStartingEvent
事件。至于multicastEvent()
內(nèi)部方法感興趣的可以看看飞袋,其實(shí)就是遍歷ApplicationListener
的實(shí)現(xiàn)類戳气,找到監(jiān)聽(tīng)ApplicationStartingEvent
這個(gè)事件的監(jiān)聽(tīng)器,執(zhí)行onApplicationEvent()
方法授嘀。
總結(jié)
這一步其實(shí)就是廣播了ApplicationStartingEvent
事件來(lái)觸發(fā)監(jiān)聽(tīng)這個(gè)事件的ApplicationListener
物咳。
因此如果自定義了
ApplicationListener
并且監(jiān)聽(tīng)了ApplicationStartingEvent
(應(yīng)用程序開(kāi)始啟動(dòng))事件,則這個(gè)監(jiān)聽(tīng)器將會(huì)被觸發(fā)蹄皱。
2. 環(huán)境構(gòu)建
這一步主要用于加載系統(tǒng)配置以及用戶的自定義配置(application.properties
)览闰,源碼如下,在run()
方法中:
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
prepareEnvironment
方法內(nèi)部廣播了ApplicationEnvironmentPreparedEvent
事件巷折,源碼如下圖:
環(huán)境構(gòu)建這一步加載了系統(tǒng)環(huán)境配置压鉴、用戶自定義配置并且廣播了
ApplicationEnvironmentPreparedEvent
事件,觸發(fā)監(jiān)聽(tīng)器锻拘。
3. 創(chuàng)建IOC容器
源碼在run()
方法中油吭,如下:
context = createApplicationContext();
跟進(jìn)代碼,真正執(zhí)行的是ApplicationContextFactory
方法署拟,如下圖:
根據(jù)webApplicationType
決定創(chuàng)建的類型婉宰,很顯然,我這里的是servlet
推穷,因此創(chuàng)建的是AnnotationConfigServletWebServerApplicationContext
心包。
這一步僅僅是創(chuàng)建了
IOC容器
,未有其他操作馒铃。
4. IOC容器的前置處理
這一步真是精華了蟹腾,在刷新容器之前做準(zhǔn)備,其中有一個(gè)非常關(guān)鍵的操作:將啟動(dòng)類注入容器区宇,為后續(xù)的自動(dòng)化配置奠定基礎(chǔ)娃殖。源碼如下:
prepareContext(context, environment, listeners, applicationArguments,printedBanner);
prepareContext()
源碼解析如下圖,內(nèi)容還是挺多的:
從上圖可以看出步驟很多议谷,下面將會(huì)詳細(xì)介紹幾個(gè)重點(diǎn)的內(nèi)容炉爆。
調(diào)用初始化器
在SpringApplication
構(gòu)建過(guò)程中設(shè)置的初始化器,從spring.factories
取值的卧晓。執(zhí)行的流程很簡(jiǎn)單叶洞,遍歷執(zhí)行,源碼如下圖:
將自定義的
ApplicationContextInitializer
放在META-INF/spring.factories
中禀崖,在此時(shí)也是會(huì)被調(diào)用。
加載啟動(dòng)類螟炫,注入容器
這一步是將主啟動(dòng)類加載到IOC容器
中波附,作為后續(xù)自動(dòng)配置的入口。
在SpringApplication
構(gòu)建過(guò)程中將主啟動(dòng)類放置在primarySources
這個(gè)集合中,此時(shí)的getAllSources()
即是從其中取值掸屡,如下圖:
這里取出的就是主啟動(dòng)類封寞,當(dāng)然你的項(xiàng)目中可能不止一個(gè),接下來(lái)就是將其加載到IOC容器中了仅财,源碼如下:
load(context, sources.toArray(new Object[0]));
跟著代碼進(jìn)去狈究,其實(shí)主要邏輯都在BeanDefinitionLoader.load()
方法,如下圖:
將主啟動(dòng)類加載到
beanDefinitionMap
盏求,后續(xù)該啟動(dòng)類將作為開(kāi)啟自動(dòng)配置化的入口抖锥,后續(xù)章節(jié)詳細(xì)介紹。
兩次廣播事件
這一步涉及到了兩次事件廣播碎罚,分別是ApplicationContextInitializedEvent
和ApplicationPreparedEvent
磅废,對(duì)應(yīng)的源碼如下:
listeners.contextPrepared(context);
load(context, sources.toArray(new Object[0]));
5. 刷新容器
刷新容器完全是Spring
的功能了,比如初始化資源荆烈,初始化上下文廣播器等拯勉,這個(gè)就不再詳細(xì)介紹,有興趣可以看看Spring
的源碼憔购。
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//調(diào)用創(chuàng)建的容器applicationContext中的refresh()方法
((AbstractApplicationContext)applicationContext).refresh();
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/**
* 刷新上下文環(huán)境
*/
prepareRefresh();
/**
* 初始化BeanFactory宫峦,解析XML,相當(dāng)于之前的XmlBeanFactory的操作玫鸟,
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
/**
* 為上下文準(zhǔn)備BeanFactory导绷,即對(duì)BeanFactory的各種功能進(jìn)行填充,如常用的注解@Autowired @Qualifier等
* 添加ApplicationContextAwareProcessor處理器
* 在依賴注入忽略實(shí)現(xiàn)*Aware的接口鞋邑,如EnvironmentAware诵次、ApplicationEventPublisherAware等
* 注冊(cè)依賴,如一個(gè)bean的屬性中含有ApplicationEventPublisher(beanFactory)枚碗,則會(huì)將beanFactory的實(shí)例注入進(jìn)去
*/
prepareBeanFactory(beanFactory);
try {
/**
* 提供子類覆蓋的額外處理逾一,即子類處理自定義的BeanFactoryPostProcess
*/
postProcessBeanFactory(beanFactory);
/**
* 激活各種BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
* 執(zhí)行對(duì)應(yīng)的postProcessBeanDefinitionRegistry方法 和 postProcessBeanFactory方法
*/
invokeBeanFactoryPostProcessors(beanFactory);
/**
* 注冊(cè)攔截Bean創(chuàng)建的Bean處理器,即注冊(cè)BeanPostProcessor肮雨,不是BeanFactoryPostProcessor遵堵,注意兩者的區(qū)別
* 注意,這里僅僅是注冊(cè)怨规,并不會(huì)執(zhí)行對(duì)應(yīng)的方法陌宿,將在bean的實(shí)例化時(shí)執(zhí)行對(duì)應(yīng)的方法
*/
registerBeanPostProcessors(beanFactory);
/**
* 初始化上下文中的資源文件,如國(guó)際化文件的處理等
*/
initMessageSource();
/**
* 初始化上下文事件廣播器波丰,并放入applicatioEventMulticaster,如ApplicationEventPublisher
*/
initApplicationEventMulticaster();
/**
* 給子類擴(kuò)展初始化其他Bean
*/
onRefresh();
/**
* 在所有bean中查找listener bean壳坪,然后注冊(cè)到廣播器中
*/
registerListeners();
/**
* 設(shè)置轉(zhuǎn)換器
* 注冊(cè)一個(gè)默認(rèn)的屬性值解析器
* 凍結(jié)所有的bean定義,說(shuō)明注冊(cè)的bean定義將不能被修改或進(jìn)一步的處理
* 初始化剩余的非惰性的bean掰烟,即初始化非延遲加載的bean
*/
finishBeanFactoryInitialization(beanFactory);
/**
* 通過(guò)spring的事件發(fā)布機(jī)制發(fā)布ContextRefreshedEvent事件爽蝴,以保證對(duì)應(yīng)的監(jiān)聽(tīng)器做進(jìn)一步的處理
* 即對(duì)那種在spring啟動(dòng)后需要處理的一些類沐批,這些類實(shí)現(xiàn)了ApplicationListener<ContextRefreshedEvent>,
* 這里就是要觸發(fā)這些類的執(zhí)行(執(zhí)行onApplicationEvent方法)
* 另外蝎亚,spring的內(nèi)置Event有ContextClosedEvent九孩、ContextRefreshedEvent、ContextStartedEvent发框、ContextStoppedEvent躺彬、RequestHandleEvent
* 完成初始化,通知生命周期處理器lifeCycleProcessor刷新過(guò)程梅惯,同時(shí)發(fā)出ContextRefreshEvent通知其他人
*/
finishRefresh();
}
finally {
resetCommonCaches();
}
}
}
6. IOC容器的后置處理
一個(gè)擴(kuò)展方法宪拥,源碼如下:
afterRefresh(context, applicationArguments);
默認(rèn)為空,如果有自定義需求可以重寫(xiě)个唧,比如打印一些啟動(dòng)結(jié)束日志等江解。
7. 發(fā)出結(jié)束執(zhí)行的事件
同樣是EventPublishingRunListener
這個(gè)監(jiān)聽(tīng)器,廣播ApplicationStartedEvent
事件徙歼。
但是這里廣播事件和前幾次不同犁河,并不是廣播給
SpringApplication
中的監(jiān)聽(tīng)器(在構(gòu)建過(guò)程中從spring.factories
文件獲取的監(jiān)聽(tīng)器)。因此在IOC容器
中注入的監(jiān)聽(tīng)器(使用@Component
等方式注入的)也能夠生效魄梯。前面幾個(gè)事件只有在spring.factories
文件中設(shè)置的監(jiān)聽(tīng)器才會(huì)生效桨螺。
跟著代碼進(jìn)入,可以看到started()
方法源碼如下:
這里并沒(méi)有用事件廣播器SimpleApplicationEventMulticaster
廣播事件酿秸,而是使用ConfigurableApplicationContext
直接在IOC容器
中發(fā)布事件豆胸。
8. 執(zhí)行Runners
Spring Boot
提供了兩種Runner
讓我們定制一些額外的操作瘾英,分別是CommandLineRunner
和ApplicationRunner
,關(guān)于這兩個(gè)的區(qū)別,后面文章詳細(xì)介紹擅编。
調(diào)用的源碼如下:
callRunners(context, applicationArguments);
跟進(jìn)代碼藏否,其實(shí)真正調(diào)執(zhí)行的是如下方法:
邏輯很簡(jiǎn)單期吓,從IOC容器
中獲取苛白,遍歷調(diào)用。
總結(jié)
Spring Boot
啟動(dòng)流程相對(duì)簡(jiǎn)單些退客,作者將其細(xì)分了以上八個(gè)步驟骏融,希望能夠幫助讀者理解,流程圖如下:
總結(jié)
Spring Boot
啟動(dòng)流程就介紹到這里了萌狂,需要重點(diǎn)理解run()
方法執(zhí)行的八個(gè)步驟以及事件档玻、初始化器、監(jiān)聽(tīng)器等組件的執(zhí)行時(shí)間點(diǎn)茫藏。
作者:熬夜不加班
鏈接:http://www.reibang.com/p/2acb10bab12b
來(lái)源:簡(jiǎn)書(shū)
著作權(quán)歸作者所有误趴。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處务傲。