20張圖剖析Spring啟動時核心的12個步驟
今天來扒一扒Spring在啟動過程中核心的12個步驟
之所以來寫這篇文章,主要是來填坑的
之前在三萬字盤點Spring 9大核心基礎(chǔ)功能這篇文章的末尾中給自己挖了一個坑,提了一嘴有機會要寫這么一篇文章
但是由于Spring啟動過程并不復雜,所以后面就沒寫了
不過,好巧不巧,剛剛好有兄弟來催更了,那么此時這個機會就來了,這篇文章也就有了
前言
Spring啟動時候整個入口是這么一個方法
AbstractApplicationContext#refresh
總共有12個方法,也就是啟動時的核心步驟
AbstractApplicationContext有眾多實現(xiàn),這里我選擇SpringBoot Web應(yīng)用默認的實現(xiàn)來講
AnnotationConfigServletWebServerApplicationContext
AnnotationConfigServletWebServerApplicationContext類圖
對應(yīng)的SpringBoot版本為 2.2.5.RELEASE
高版本refresh方法會多一些日志相關(guān)的代碼,這里為了方便講解,就使用這個版本
所以后面本文提到的所有子類的方法實現(xiàn)、重寫都是指AnnotationConfigServletWebServerApplicationContext及其父類
本文主要是講一些啟動的步驟,具體的很多技術(shù)實現(xiàn)細節(jié)、技術(shù)點這里就不過多贅述
如果有疑問的,可以查看三萬字盤點Spring 9大核心基礎(chǔ)功能這篇文章或者公眾號菜單欄中關(guān)于Spring的文章,基本上都能找到答案
prepareRefresh
prepareRefresh整個刷新的一個步驟,這個步驟是做啟動的一個準備操作
ApplicationContext剛創(chuàng)建出來,什么也沒有,所以需要做一些準備
首先是做一些狀態(tài)位的變更,表明開始啟動了或者刷新了
后面一行
initPropertySources
initPropertySources是一個模板方法,本身是一個空實現(xiàn),是給子類用的
我們的這個子類就重寫了initPropertySources方法
圖片
會將Servlet相關(guān)的配置加入到Environment中,這樣我們就能從Environment中獲取到Servlet相關(guān)的配置了
再后面一行
getEnvironment().validateRequiredProperties()
這行代碼就是校驗一些必要的配置屬性,我們可以通過ConfigurableEnvironment來設(shè)置哪些屬性是必要的,默認是沒有必要的
所以prepareRefresh就是做了一些前置操作,準備好一些屬性配置相關(guān)的東西,后面的其它環(huán)節(jié),比如說生成Bean時可能需要用到這些配置
obtainFreshBeanFactory
這一步驟是刷新BeanFactory并且獲取BeanFactory
refreshBeanFactory() 和 getBeanFactory() 都是抽象方法,由子類來實現(xiàn)的
而子類的實現(xiàn)其實很簡單,就是給beanFactory設(shè)置一個id和返回beanFactory
beanFactory就是下面這個玩意
并且創(chuàng)建對象的ApplicationContext對象的時候就創(chuàng)建了,類型為
DefaultListableBeanFactory
所以從這就可以看出來,雖然說BeanFactory是一個接口,有非常多的實現(xiàn)
但是實際情況下,真正使用的就是DefaultListableBeanFactory
并且DefaultListableBeanFactory其實算是BeanFactory唯一真正的實現(xiàn)
除此之外,還可以得出一個結(jié)論,ApplicationContext中有一個BeanFactory(DefaultListableBeanFactory)
圖片
prepareBeanFactory
上一步驟獲取到了BeanFactory,但是這個BeanFactory僅僅就是剛剛new出來的,什么也沒有
所以當前步驟就是對BeanFactory做一些配置工作
前三行代碼
先給BeanFactory設(shè)置了一個ClassLoader,因為BeanFactory是用來創(chuàng)建Bean,需要加載Bean class對象
然后設(shè)置了一個BeanExpressionResolver,這個是用來解析SpEL表達式的
然后添加了一個PropertyEditorRegistrar,也就是
ResourceEditorRegistrar
這個的作用就是為BeanFactory添加一堆跟資源相關(guān)的PropertyEditor
ResourceEditorRegistrar核心實現(xiàn)方法
PropertyEditor之前說過,就是進行類型轉(zhuǎn)換的,將一個字符串轉(zhuǎn)成對應(yīng)的類型
接下來這幾行
這里主要是添加了一個BeanPostProcessor,也就是
ApplicationContextAwareProcessor
BeanPostProcessor我們都知道會在Bean的生命周期階段進行回調(diào),是Bean的生命周期一個核心的環(huán)節(jié)
ApplicationContextAwareProcessor這個是用來處理Bean生命周期中的Aware回調(diào)有關(guān)
當你的Bean實現(xiàn)這些接口的時候,在創(chuàng)建的時候Spring會回調(diào)這些接口,傳入對應(yīng)的對象
而后面的這行代碼
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
意思是說,如果你的Bean想注入一個EnvironmentAware對象
@Resource
private EnvironmentAware environmentAware;
這是不允許的
因為很簡單,注入一個EnvironmentAware對象,沒有實際的意義
后面的其它幾行代碼也都是這個意思
再接下來這幾行
圖片
這跟上面的ignoreDependencyInterface作用相反
他是來設(shè)置依賴注入時Bean的類型所對應(yīng)的對象
比如說這行代碼
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
這行代碼的意思就是當你需要注入一個Bean類型為BeanFactory.class類型的時候
@Resource
private BeanFactory beanFactory;
那么實際注入的就是方法第二個參數(shù)beanFactory,也就是上面獲取的DefaultListableBeanFactory對象
同理,注入ResourceLoader、ApplicationEventPublisher、ApplicationContext時,其實注入的對象都是this,也就是當前的ApplicationContext對象
再再接下來這幾行
最開始又添加了一個BeanPostProcessor
ApplicationListenerDetector
這個BeanPostProcessor是跟ApplicationListener有關(guān)
他是將單例的ApplicationListener給添加到ApplicationContext中
再后面就是往BeanFactory里面添加一些跟配置屬性相關(guān)的單例對象,如果有哪里用到,就可以從BeanFactory中獲取到了
prepareBeanFactory就完了
正如方法名字的含義一樣,就是對BeanFactory做一些配置相關(guān)的東西
比如添加一些BeanPostProcessor,注冊一些PropertyEditor
為Bean的生成做準備操作
最后畫張圖來總結(jié)一下這個方法的作用
圖片
此時BeanFactory狀態(tài)就是這樣的
圖片
postProcessBeanFactory
這個方法是一個模板方法
圖片
本身是空實現(xiàn),是交給子類來擴展,子類可以根據(jù)不同的特性再對BeanFactory進行一些準備工作
比如我們用的這個Web實現(xiàn)就重寫了這個方法,對BeanFactory設(shè)置一些Web相關(guān)的配置
圖片
AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory
首先調(diào)用父類ServletWebServerApplicationContext的postProcessBeanFactory
圖片
ServletWebServerApplicationContext#postProcessBeanFactory
前兩行代碼跟之前說的一樣,也是添加Aware接口的回調(diào)對應(yīng)的BeanPostProcessor,只不過這個Aware是跟Servlet相關(guān)的東西
接下來調(diào)用registerWebApplicationScopes方法,最終會調(diào)到下面這個方法
WebApplicationContextUtils#registerWebApplicationScopes
圖片
這個方法干了兩件事
第一件事就是注冊一下Bean在Web環(huán)境下的作用域request、session,八股文中的東西
第二個就是注冊一些依賴注入時Bean類型和對應(yīng)的對象,這在日常開發(fā)中還是有用的
比如可以直接注入一個ServletRequest
@Resource
private ServletRequest servletRequest;
所以,父類的實現(xiàn)主要還是對BeanFactory進行一些配置,只不過配置的主要是跟Web環(huán)境相關(guān)的東西
現(xiàn)在來看看AnnotationConfigServletWebServerApplicationContext自身的實現(xiàn)
圖片
核心代碼就是這兩行
this.scanner.scan(this.basePackages);
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
scanner和reader就是下面這兩個玩意
圖片
也就是說,如果這些配置都不是空的話,那么此時就會掃描對應(yīng)的包的下Bean,生成對應(yīng)的BeanDenifition,再注冊到DefaultListableBeanFactory
至于為什么會存到DefaultListableBeanFactory中,可以看看之前的文章
此時BeanFactory大概是這么一個狀態(tài)
圖片
除此之外,還有一個賊重要的事
AnnotationConfigServletWebServerApplicationContext這個ApplicationContext創(chuàng)建時會去創(chuàng)建AnnotatedBeanDefinitionReader
而AnnotatedBeanDefinitionReader的構(gòu)造方法最終會調(diào)用這么一行代碼
AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)
圖片
這個方法非常重要,他會去注冊一些BeanDefinition到BeanFactory中,這里我稱為Spring內(nèi)部的Bean
這里我說幾個常見和重要的
- ConfigurationClassPostProcessor:這個是用來處理配置類的,非常重要,記住這個類,后面有大用
- AutowiredAnnotationBeanPostProcessor:處理@Autowired、@Value注解
- CommonAnnotationBeanPostProcessor:處理@Resource、@PostConstruct等注解
所以除了掃描出來的一些Bean對應(yīng)的BeanDefinition,還有一些Spring內(nèi)部的Bean會注冊到BeanFactory中
此時BeanFactory的狀態(tài)就如下圖所示
不過,在SpringBoot默認情況下,不會指定包和配置類,也就不會掃描文件,生成BeanDefinition
但是內(nèi)部創(chuàng)建的BeanDefinition依然存在,并且在ApplicationContext創(chuàng)建的時候就注冊到BeanFactory中了
所以總結(jié)來說,postProcessBeanFactory這個方法是交給子類對BeanFactory做一些準備操作,并且可能會掃描Bean
invokeBeanFactoryPostProcessors
從這個方法的名字可以看出,是調(diào)用BeanFactoryPostProcessor,這個步驟非常重要,而且過程有點繞
前置知識:BeanFactoryPostProcessor及其子接口
BeanFactoryPostProcessor是一個接口,有一個方法,方法參數(shù)就是BeanFactory
通過這個方法就可以拿到BeanFactory,然后對BeanFactory做一些自己的調(diào)整
比如說,你想關(guān)閉循環(huán)依賴,你就可以實現(xiàn)這個接口,然后進行調(diào)整
他還有一個子接口BeanDefinitionRegistryPostProcessor
這個接口是對BeanDefinitionRegistry進行調(diào)整,BeanDefinitionRegistry就是存BeanDefinition的地方,真實的實現(xiàn)就是DefaultListableBeanFactory
所以BeanDefinitionRegistryPostProcessor的作用就是往BeanDefinitionRegistry(DefaultListableBeanFactory)中添加BeanDefinition的
再看invokeBeanFactoryPostProcessors
有了這兩個前置知識之后,我們來看看invokeBeanFactoryPostProcessors方法的實現(xiàn)
這個方法最終會調(diào)用下面方法來真正的處理
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors;
這個方法比較長,大致分為兩件事
- 調(diào)用所有的BeanDefinitionRegistryPostProcessor,解析配置類,注冊BeanDefinition到DefaultListableBeanFactory中
- 從BeanFactory中獲取所有的BeanFactoryPostProcessor進行調(diào)用,完成對BeanFactory一些其它的擴展
調(diào)用BeanDefinitionRegistryPostProcessor
首先第一步,先從BeanFactory中獲取到所有的BeanDefinitionRegistryPostProcessor對象,調(diào)用它的postProcessBeanDefinitionRegistry方法
還記得上一節(jié)在說注冊Spring內(nèi)部的Bean時特地強調(diào)的一個類ConfigurationClassPostProcessor不?
他就實現(xiàn)了BeanDefinitionRegistryPostProcessor接口
所以此時獲取到的就是ConfigurationClassPostProcessor
獲取ConfigurationClassPostProcessor的時候會走Bean的生命周期,也就是會回調(diào)前面添加的BeansPostProcessor,但是也沒幾個
之后會調(diào)用他的postProcessBeanDefinitionRegistry方法,來處理此時BeanFactory中的配置類
配置類從哪來,前面一直沒提到過
但是看一下ApplicationContext是如何使用的就知道了
比如說,下面這個demo
在創(chuàng)建一個ApplicationContext之后,在注冊一個Bean之后再refresh
此時這個注冊的Bean就是配置類。
如果你不注冊,那是真沒有配置類,此時也就沒什么意義了。
所以,ApplicationContext一定會有一個配置類,不然沒有意義。
在SpringBoot條件下,SpringBoot在啟動時就會將啟動引導類當做配置類給扔到BeanFactory中。
所以ConfigurationClassPostProcessor最開始處理的時候,就是處理啟動引導類
我們可以在ConfigurationClassPostProcessor方法實現(xiàn)上打個斷點驗證一下
在處理之前可以看見,除了幾個spring內(nèi)部的BeanDefinition之外,還有一個myApplication,就是我的啟動引導類
處理的時候它會解析啟動引導類的注解,進行自動裝配,掃描你寫的代碼的操作,之后生成BeanDefinition
當處理完成之后我們再看看,DefaultListableBeanFactory有了非常多的BeanDefinition了
所以到第一步就完成了,此時BeanFactory就加載了很多Bean
圖片
接下來,由于又新注冊了很多BeanDefinition,而這些里面就有可能有BeanDefinitionRegistryPostProcessor接口的實現(xiàn)
所以之后會重復從BeanFactory中獲取BeanDefinitionRegistryPostProcessor,調(diào)用postProcessBeanDefinitionRegistry
一直會循環(huán)下去,直到所有的BeanDefinitionRegistryPostProcessor都被調(diào)用為止
圖片
由于BeanDefinitionRegistryPostProcessor繼承BeanFactoryPostProcessor
所以之后也會調(diào)用BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法
調(diào)用BeanFactoryPostProcessor
當調(diào)完所有的BeanDefinitionRegistryPostProcessor實現(xiàn)方法
之后就會從BeanFactory獲取所有的BeanFactoryPostProcessor(除了BeanDefinitionRegistryPostProcessor實現(xiàn)之外),調(diào)用postProcessBeanFactory方法
此時就可以通過BeanFactoryPostProcessor再次對BeanFactory進制擴展
總的來說,這一步驟的核心作用就是完成對BeanFactory自定義擴展,但是由于BeanFactoryPostProcessor都是Bean,所以要第一步先加載Bean,之后才能通過BeanFactoryPostProcessor來擴展
一張圖來總結(jié)上面主要干的事
這里簡化了一些前面提到東西
registerBeanPostProcessors
上面一個步驟已經(jīng)完成了Bean的掃描和對BeanFactory的擴展
這一節(jié)通過方法名就可以看出,是跟BeanPostProcessor相關(guān)
不過在這個方法執(zhí)行之前,我們先來看看此時BeanFactory中已經(jīng)有了哪些BeanPostProcessor
此時只有4個,前3個前面都提到過,但是像我們熟知的處理@Autowired、@Resource注解的BeanPostProcessor都不在里面
所以這里就有一個非常重要的小細節(jié)
在當前這個步驟執(zhí)行之前如果從BeanFactory中獲取Bean的話,雖然會走Bean生命周期的整個過程,但是@Autowired、@Resource注解都不會生效,因為此時BeanFactory中還沒有處理這些注解的BeanPostProcessor(CommonAnnotationBeanPostProcessor等)
什么意思呢,舉個例子
比如上面一節(jié),在當前步驟執(zhí)行之前會從BeanFactory中獲取BeanFactoryPostProcessor
假設(shè)現(xiàn)在你實現(xiàn)了BeanFactoryPostProcessor,想注入一個ApplicationContext對象
圖片
此時是注入不成功的,@Resource注解不會生效,就是這個意思。
這時只能通過ApplicationContextAware方式獲取,因為有對應(yīng)的BeanPostProcessor(ApplicationContextAwareProcessor)
接下來我們再來看看registerBeanPostProcessors實現(xiàn)
最終也是調(diào)用下面的方法
PostProcessorRegistrationDelegate#registerBeanPostProcessors
這個過程就沒上面那個步驟復雜了
其實就是從BeanFactory中獲取到所有的BeanPostProcessor,然后添加到BeanFactory中
不過值得注意的是,BeanPostProcessor創(chuàng)建會有優(yōu)先級,優(yōu)先級高的會先被創(chuàng)建和添加到BeanFactory中
到這一步其實BeanFactory就算是準備完成了,基本上跟創(chuàng)建Bean相關(guān)的前置操作幾乎都完成了
最后再來張圖總結(jié)一下這個方法干的事
initMessageSource
這個方法是處理國際化相關(guān)的操作
這個操作比較簡單,就是從BeanFactory中看看有沒有Bean名稱為messageSource的Bean
有的話就使用這個MessageSource,沒有的話就用默認的
不過SpringBoot項目下會自動裝配一個MessageSource,所以此時容器中是有的
initApplicationEventMulticaster
這個方法跟上面的差不多,也是從BeanFactory找有沒有ApplicationEventMulticaster
有就用容器中的,沒有就自己創(chuàng)建一個
ApplicationEventMulticaster是真正用來發(fā)布事件的,ApplicationEventPublisher最終也是調(diào)用他來發(fā)布事件
ApplicationEventMulticaster內(nèi)部會緩存所有的監(jiān)聽器
當通過ApplicationEventMulticaster發(fā)布事件的時候,會去找到所有的監(jiān)聽器,然后調(diào)用
onRefresh
onRefresh也是一個模板方法,本身也是空實現(xiàn)
子類重寫這個方法,會去創(chuàng)建一個Web服務(wù)器
registerListeners
這個方法其實也比較簡單,就是將監(jiān)聽器給添加到ApplicationEventMulticaster中
finishBeanFactoryInitialization
這個方法首先又是老套路,就是判斷容器中有沒有ConversionService
ConversionService也是用來做類型轉(zhuǎn)換的,跟前面提到的PropertyEditor作用差不多
如果有,就把ConversionService設(shè)置到BeanFactory中
到這一步,BeanFactory才算真的準備完成。。。
之后其實干的事就不太重要了
但是最后一行比較重要
beanFactory.preInstantiateSingletons();
從方法的命名就可以看出,實例化所有的單例對象
因為對于BeanFactory的一些配置在前面都完成了,所以這里就可以來實例化所有的單例對象了
這個方法會做兩件事
第一件事就是實例化所有的非懶加載的單例Bean
實際上就是通過getBean方法來的,因為獲取Bean,不存在的時候就會創(chuàng),會走Bean的生命周期
第二件事就是一旦單例Bean實現(xiàn)了SmartInitializingSingleton接口,就會調(diào)用SmartInitializingSingleton的afterSingletonsInstantiated方法
這個其實也算是Bean生命周期的一部分。
finishRefresh
這個方法是整個Spring容器刷新的最后一個方法
這個方法就是收尾的操作
清理一下緩存操作
之后就是初始化LifecycleProcessor
都是一樣的套路,優(yōu)先用BeanFactory中的
后面就會調(diào)用LifecycleProcessor#onRefresh方法
這個方法的作用就是,如果你的Bean實現(xiàn)了SmartLifecycle的接口,會調(diào)start的方法
隨后就發(fā)布一個ContextRefreshedEvent事件,表明容器已經(jīng)刷新完成了
在Web環(huán)境底下,這個finishRefresh方法被重寫了
主要是多干了一件事,那就是啟動Web服務(wù)器
并且會發(fā)布了一個ServletWebServerInitializedEvent事件
這個事件在SpringBoot中用的不多
但是在SpringCloud中卻非常重要
在SpringCloud環(huán)境底下會有一個類監(jiān)聽這個事件
一旦監(jiān)聽到這個事件,SpringCloud就會將當前的服務(wù)的信息自動注冊到注冊中心上
這就是服務(wù)自動注冊的原理
總結(jié)
這里再來簡單回顧一下Spring啟動大致的幾個過程
最開始的準備操作,這部分就是準備一些配置屬性相關(guān)的
之后連續(xù)好幾個方法都是準備BeanFactory的,我把上面那張圖拿過來
整個準備BeanFactory過程大致如下:
- 先配置BeanFactory
- 通過ConfigurationClassPostProcessor加載Bean到BeanFactory中
- 從上一步加載的Bean中獲取BeanFactoryPostProcessor,完成對BeanFactory做自定義處理
- 從上一步加載的Bean中獲取BeanPostProcessor,添加到BeanFactory中
當這些步驟完成之后,BeanFactory跟Bean創(chuàng)建相關(guān)的配置幾乎算是配置完成了
之后其實就是一些ApplicationContext內(nèi)部的一些組價的初始化,比如MessageSource、ApplicationEventMulticaster等等
優(yōu)先從BeanFactory中獲取,沒有再用默認的
到這ApplicationContext也算配置完成了,之后就可以實例化單例非懶加載的Bean了
再后面就是一些掃尾的操作,發(fā)布一個ContextRefreshedEvent事件,表明容器已經(jīng)刷新完成了
這時Spring就就算是真正啟動完成了。
最后,如果本篇文章對你所有幫助,歡迎轉(zhuǎn)發(fā)、點贊、收藏、在看,非常感謝。
最后的最后,再來留個坑,有機會再來扒一扒SpringBoot在啟動時都做了哪些事
至于啥時候填,那就等一個有緣人吧。。