大家好,我是三友~~
今天來扒一扒Spring在啟動過程中核心的12個步驟
之所以來寫這篇文章,主要是來填坑的
之前在三萬字盤點Spring 9大核心基礎功能這篇文章的末尾中給自己挖了一個坑,提了一嘴有機會要寫這麼一篇文章
但是由於Spring啟動過程並不複雜,所以後面就沒寫了
不過,好巧不巧,剛剛好有兄弟來催更了,那麼此時這個機會就來了,這篇文章也就有了
公眾號:三友的java日記
Spring啟動時候整個入口是這麼一個方法
AbstractApplicationContext#refresh
總共有12個方法,也就是啟動時的核心步驟
AbstractApplicationContext有眾多實現,這裡我選擇SpringBoot Web應用預設的實現來講
AnnotationConfigServletWebServerApplicationContext
對應的SpringBoot版本為 2.2.5.RELEASE
高版本refresh方法會多一些紀錄檔相關的程式碼,這裡為了方便講解,就使用這個版本
所以後面本文提到的所有子類的方法實現、重寫都是指AnnotationConfigServletWebServerApplicationContext及其父類別
本文主要是講一些啟動的步驟,具體的很多技術實現細節、技術點這裡就不過多贅述
如果有疑問的,可以檢視三萬字盤點Spring 9大核心基礎功能這篇文章或者公眾號選單欄中關於Spring的文章,基本上都能找到答案
prepareRefresh整個重新整理的一個步驟,這個步驟是做啟動的一個準備操作
ApplicationContext剛建立出來,什麼也沒有,所以需要做一些準備
首先是做一些狀態位的變更,表明開始啟動了或者重新整理了
後面一行
initPropertySources
initPropertySources是一個模板方法,本身是一個空實現,是給子類用的
我們的這個子類就重寫了initPropertySources方法
會將Servlet相關的設定加入到Environment中,這樣我們就能從Environment中獲取到Servlet相關的設定了
再後面一行
getEnvironment().validateRequiredProperties()
這行程式碼就是校驗一些必要的設定屬性,我們可以通過ConfigurableEnvironment來設定哪些屬性是必要的,預設是沒有必要的
所以prepareRefresh就是做了一些前置操作,準備好一些屬性設定相關的東西,後面的其它環節,比如說生成Bean時可能需要用到這些設定
這一步驟是重新整理BeanFactory並且獲取BeanFactory
refreshBeanFactory() 和 getBeanFactory() 都是抽象方法,由子類來實現的
而子類的實現其實很簡單,就是給beanFactory設定一個id和返回beanFactory
beanFactory就是下面這個玩意
並且建立物件的ApplicationContext物件的時候就建立了,型別為
DefaultListableBeanFactory
所以從這就可以看出來,雖然說BeanFactory是一個介面,有非常多的實現
但是實際情況下,真正使用的就是DefaultListableBeanFactory
並且DefaultListableBeanFactory其實算是BeanFactory唯一真正的實現
除此之外,還可以得出一個結論,ApplicationContext中有一個BeanFactory(DefaultListableBeanFactory)
上一步驟獲取到了BeanFactory,但是這個BeanFactory僅僅就是剛剛new出來的,什麼也沒有
所以當前步驟就是對BeanFactory做一些設定工作
先給BeanFactory設定了一個ClassLoader,因為BeanFactory是用來建立Bean,需要載入Bean class物件
然後設定了一個BeanExpressionResolver,這個是用來解析SpEL表示式的
然後新增了一個PropertyEditorRegistrar,也就是
ResourceEditorRegistrar
這個的作用就是為BeanFactory新增一堆跟資源相關的PropertyEditor
PropertyEditor之前說過,就是進行型別轉換的,將一個字串轉成對應的型別
這裡主要是新增了一個BeanPostProcessor,也就是
ApplicationContextAwareProcessor
BeanPostProcessor我們都知道會在Bean的生命週期階段進行回撥,是Bean的生命週期一個核心的環節
ApplicationContextAwareProcessor這個是用來處理Bean生命週期中的Aware回撥有關
當你的Bean實現這些介面的時候,在建立的時候Spring會回撥這些介面,傳入對應的物件
而後面的這行程式碼
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
意思是說,如果你的Bean想注入一個EnvironmentAware物件
@Resource
private EnvironmentAware environmentAware;
這是不允許的
因為很簡單,注入一個EnvironmentAware物件,沒有實際的意義
後面的其它幾行程式碼也都是這個意思
這跟上面的ignoreDependencyInterface作用相反
他是來設定依賴注入時Bean的型別所對應的物件
比如說這行程式碼
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
這行程式碼的意思就是當你需要注入一個Bean型別為BeanFactory.class型別的時候
@Resource
private BeanFactory beanFactory;
那麼實際注入的就是方法第二個引數beanFactory,也就是上面獲取的DefaultListableBeanFactory物件
同理,注入ResourceLoader、ApplicationEventPublisher、ApplicationContext時,其實注入的物件都是this,也就是當前的ApplicationContext物件
最開始又新增了一個BeanPostProcessor
ApplicationListenerDetector
這個BeanPostProcessor是跟ApplicationListener有關
他是將單例的ApplicationListener給新增到ApplicationContext中
再後面就是往BeanFactory裡面新增一些跟設定屬性相關的單例物件,如果有哪裡用到,就可以從BeanFactory中獲取到了
prepareBeanFactory就完了
正如方法名字的含義一樣,就是對BeanFactory做一些設定相關的東西
比如新增一些BeanPostProcessor,註冊一些PropertyEditor
為Bean的生成做準備操作
最後畫張圖來總結一下這個方法的作用
此時BeanFactory狀態就是這樣的
這個方法是一個模板方法
本身是空實現,是交給子類來擴充套件,子類可以根據不同的特性再對BeanFactory進行一些準備工作
比如我們用的這個Web實現就重寫了這個方法,對BeanFactory設定一些Web相關的設定
首先呼叫父類別ServletWebServerApplicationContext的postProcessBeanFactory
前兩行程式碼跟之前說的一樣,也是新增Aware介面的回撥對應的BeanPostProcessor,只不過這個Aware是跟Servlet相關的東西
接下來呼叫registerWebApplicationScopes方法,最終會調到下面這個方法
WebApplicationContextUtils#registerWebApplicationScopes
這個方法幹了兩件事
第一件事就是註冊一下Bean在Web環境下的作用域request、session,八股文中的東西
第二個就是註冊一些依賴注入時Bean型別和對應的物件,這在日常開發中還是有用的
比如可以直接注入一個ServletRequest
@Resource
private ServletRequest servletRequest;
所以,父類別的實現主要還是對BeanFactory進行一些設定,只不過設定的主要是跟Web環境相關的東西
現在來看看AnnotationConfigServletWebServerApplicationContext自身的實現
核心程式碼就是這兩行
this.scanner.scan(this.basePackages);
this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));
scanner和reader就是下面這兩個玩意
也就是說,如果這些設定都不是空的話,那麼此時就會掃描對應的包的下Bean,生成對應的BeanDenifition,再註冊到DefaultListableBeanFactory
至於為什麼會存到DefaultListableBeanFactory中,可以看看之前的文章
此時BeanFactory大概是這麼一個狀態
除此之外,還有一個賊重要的事
AnnotationConfigServletWebServerApplicationContext這個ApplicationContext建立時會去建立AnnotatedBeanDefinitionReader
而AnnotatedBeanDefinitionReader的構造方法最終會呼叫這麼一行程式碼
AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)
這個方法非常重要,他會去註冊一些BeanDefinition到BeanFactory中,這裡我稱為Spring內部的Bean
這裡我說幾個常見和重要的
所以除了掃描出來的一些Bean對應的BeanDefinition,還有一些Spring內部的Bean會註冊到BeanFactory中
此時BeanFactory的狀態就如下圖所示
不過,在SpringBoot預設情況下,不會指定包和設定類,也就不會掃描檔案,生成BeanDefinition
但是內部建立的BeanDefinition依然存在,並且在ApplicationContext建立的時候就註冊到BeanFactory中了
所以總結來說,postProcessBeanFactory這個方法是交給子類對BeanFactory做一些準備操作,並且可能會掃描Bean
從這個方法的名字可以看出,是呼叫BeanFactoryPostProcessor,這個步驟非常重要,而且過程有點繞
BeanFactoryPostProcessor是一個介面,有一個方法,方法引數就是BeanFactory
通過這個方法就可以拿到BeanFactory,然後對BeanFactory做一些自己的調整
比如說,你想關閉迴圈依賴,你就可以實現這個介面,然後進行調整
他還有一個子介面BeanDefinitionRegistryPostProcessor
這個介面是對BeanDefinitionRegistry進行調整,BeanDefinitionRegistry就是存BeanDefinition的地方,真實的實現就是DefaultListableBeanFactory
所以BeanDefinitionRegistryPostProcessor的作用就是往BeanDefinitionRegistry(DefaultListableBeanFactory)中新增BeanDefinition的
有了這兩個前置知識之後,我們來看看invokeBeanFactoryPostProcessors方法的實現
這個方法最終會呼叫下面方法來真正的處理
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors;
這個方法比較長,大致分為兩件事
首先第一步,先從BeanFactory中獲取到所有的BeanDefinitionRegistryPostProcessor物件,呼叫它的postProcessBeanDefinitionRegistry方法
還記得上一節在說註冊Spring內部的Bean時特地強調的一個類ConfigurationClassPostProcessor不?
他就實現了BeanDefinitionRegistryPostProcessor介面
所以此時獲取到的就是ConfigurationClassPostProcessor
獲取ConfigurationClassPostProcessor的時候會走Bean的生命週期,也就是會回撥前面新增的BeansPostProcessor,但是也沒幾個
之後會呼叫他的postProcessBeanDefinitionRegistry方法,來處理此時BeanFactory中的設定類
設定類從哪來,前面一直沒提到過
但是看一下ApplicationContext是如何使用的就知道了
比如說,下面這個demo
在建立一個ApplicationContext之後,在註冊一個Bean之後再refresh
此時這個註冊的Bean就是設定類。
如果你不註冊,那是真沒有設定類,此時也就沒什麼意義了。
所以,ApplicationContext一定會有一個設定類,不然沒有意義。
在SpringBoot條件下,SpringBoot在啟動時就會將啟動引導類當做設定類給扔到BeanFactory中。
所以ConfigurationClassPostProcessor最開始處理的時候,就是處理啟動引導類
我們可以在ConfigurationClassPostProcessor方法實現上打個斷點驗證一下
在處理之前可以看見,除了幾個spring內部的BeanDefinition之外,還有一個myApplication,就是我的啟動引導類
處理的時候它會解析啟動引導類的註解,進行自動裝配,掃描你寫的程式碼的操作,之後生成BeanDefinition
當處理完成之後我們再看看,DefaultListableBeanFactory有了非常多的BeanDefinition了
所以到第一步就完成了,此時BeanFactory就載入了很多Bean
接下來,由於又新註冊了很多BeanDefinition,而這些裡面就有可能有BeanDefinitionRegistryPostProcessor介面的實現
所以之後會重複從BeanFactory中獲取BeanDefinitionRegistryPostProcessor,呼叫postProcessBeanDefinitionRegistry
一直會迴圈下去,直到所有的BeanDefinitionRegistryPostProcessor都被呼叫為止
由於BeanDefinitionRegistryPostProcessor繼承BeanFactoryPostProcessor
所以之後也會呼叫BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法
當調完所有的BeanDefinitionRegistryPostProcessor實現方法
之後就會從BeanFactory獲取所有的BeanFactoryPostProcessor(除了BeanDefinitionRegistryPostProcessor實現之外),呼叫postProcessBeanFactory方法
此時就可以通過BeanFactoryPostProcessor再次對BeanFactory進位制擴充套件
總的來說,這一步驟的核心作用就是完成對BeanFactory自定義擴充套件,但是由於BeanFactoryPostProcessor都是Bean,所以要第一步先載入Bean,之後才能通過BeanFactoryPostProcessor來擴充套件
一張圖來總結上面主要乾的事
這裡簡化了一些前面提到東西
上面一個步驟已經完成了Bean的掃描和對BeanFactory的擴充套件
這一節通過方法名就可以看出,是跟BeanPostProcessor相關
不過在這個方法執行之前,我們先來看看此時BeanFactory中已經有了哪些BeanPostProcessor
此時只有4個,前3個前面都提到過,但是像我們熟知的處理@Autowired、@Resource註解的BeanPostProcessor都不在裡面
所以這裡就有一個非常重要的小細節
在當前這個步驟執行之前如果從BeanFactory中獲取Bean的話,雖然會走Bean生命週期的整個過程,但是@Autowired、@Resource註解都不會生效,因為此時BeanFactory中還沒有處理這些註解的BeanPostProcessor(CommonAnnotationBeanPostProcessor等)
什麼意思呢,舉個例子
比如上面一節,在當前步驟執行之前會從BeanFactory中獲取BeanFactoryPostProcessor
假設現在你實現了BeanFactoryPostProcessor,想注入一個ApplicationContext物件
此時是注入不成功的,@Resource註解不會生效,就是這個意思。
這時只能通過ApplicationContextAware方式獲取,因為有對應的BeanPostProcessor(ApplicationContextAwareProcessor)
接下來我們再來看看registerBeanPostProcessors實現
最終也是呼叫下面的方法
PostProcessorRegistrationDelegate#registerBeanPostProcessors
這個過程就沒上面那個步驟複雜了
其實就是從BeanFactory中獲取到所有的BeanPostProcessor,然後新增到BeanFactory中
不過值得注意的是,BeanPostProcessor建立會有優先順序,優先順序高的會先被建立和新增到BeanFactory中
到這一步其實BeanFactory就算是準備完成了,基本上跟建立Bean相關的前置操作幾乎都完成了
最後再來張圖總結一下這個方法乾的事
這個方法是處理國際化相關的操作
這個操作比較簡單,就是從BeanFactory中看看有沒有Bean名稱為messageSource的Bean
有的話就使用這個MessageSource,沒有的話就用預設的
不過SpringBoot專案下會自動裝配一個MessageSource,所以此時容器中是有的
這個方法跟上面的差不多,也是從BeanFactory找有沒有ApplicationEventMulticaster
有就用容器中的,沒有就自己建立一個
ApplicationEventMulticaster是真正用來發布事件的,ApplicationEventPublisher最終也是呼叫他來發布事件
ApplicationEventMulticaster內部會快取所有的監聽器
當通過ApplicationEventMulticaster釋出事件的時候,會去找到所有的監聽器,然後呼叫
onRefresh也是一個模板方法,本身也是空實現
子類重寫這個方法,會去建立一個Web伺服器
這個方法其實也比較簡單,就是將監聽器給新增到ApplicationEventMulticaster中
這個方法首先又是老套路,就是判斷容器中有沒有ConversionService
ConversionService也是用來做型別轉換的,跟前面提到的PropertyEditor作用差不多
如果有,就把ConversionService設定到BeanFactory中
到這一步,BeanFactory才算真的準備完成。。。
之後其實幹的事就不太重要了
但是最後一行比較重要
beanFactory.preInstantiateSingletons();
從方法的命名就可以看出,範例化所有的單例物件
因為對於BeanFactory的一些設定在前面都完成了,所以這裡就可以來範例化所有的單例物件了
這個方法會做兩件事
第一件事就是範例化所有的非懶載入的單例Bean
實際上就是通過getBean方法來的,因為獲取Bean,不存在的時候就會創,會走Bean的生命週期
第二件事就是一旦單例Bean實現了SmartInitializingSingleton介面,就會呼叫SmartInitializingSingleton的afterSingletonsInstantiated方法
這個其實也算是Bean生命週期的一部分。
這個方法是整個Spring容器重新整理的最後一個方法
這個方法就是收尾的操作
清理一下快取操作
之後就是初始化LifecycleProcessor
都是一樣的套路,優先用BeanFactory中的
後面就會呼叫LifecycleProcessor#onRefresh方法
這個方法的作用就是,如果你的Bean實現了SmartLifecycle的介面,會調start的方法
隨後就釋出一個ContextRefreshedEvent事件,表明容器已經重新整理完成了
在Web環境底下,這個finishRefresh方法被重寫了
主要是多幹了一件事,那就是啟動Web伺服器
並且會發布了一個ServletWebServerInitializedEvent事件
這個事件在SpringBoot中用的不多
但是在SpringCloud中卻非常重要
在SpringCloud環境底下會有一個類監聽這個事件
一旦監聽到這個事件,SpringCloud就會將當前的服務的資訊自動註冊到註冊中心上
這就是服務自動註冊的原理
這裡再來簡單回顧一下Spring啟動大致的幾個過程
最開始的準備操作,這部分就是準備一些設定屬性相關的
之後連續好幾個方法都是準備BeanFactory的,我把上面那張圖拿過來
整個準備BeanFactory過程大致如下:
當這些步驟完成之後,BeanFactory跟Bean建立相關的設定幾乎算是設定完成了
之後其實就是一些ApplicationContext內部的一些組價的初始化,比如MessageSource、ApplicationEventMulticaster等等
優先從BeanFactory中獲取,沒有再用預設的
到這ApplicationContext也算設定完成了,之後就可以範例化單例非懶載入的Bean了
再後面就是一些掃尾的操作,釋出一個ContextRefreshedEvent事件,表明容器已經重新整理完成了
這時Spring就就算是真正啟動完成了。
最後,如果本篇文章對你所有幫助,歡迎轉發、點贊、收藏、在看,非常感謝。
最後的最後,再來留個坑,有機會再來扒一扒SpringBoot在啟動時都做了哪些事
至於啥時候填,那就等一個有緣人吧。。
掃碼或者搜尋關注公眾號 三友的java日記 ,及時乾貨不錯過,公眾號致力於通過畫圖加上通俗易懂的語言講解技術,讓技術更加容易學習,回覆 面試 即可獲得一套面試真題。