一丶前言
在 《SpringBoot原始碼學習1——SpringBoot自動裝配原始碼解析+Spring如何處理配置類的》中我們學習了SpringBoot自動裝配如何實現的,在 《Spring原始碼學習筆記12——總結篇IOC,Bean的生命週期,三大擴充套件點》我們總結了Spring IOC的底層原理。
但是我們還是不知道SpringApplication.run(主類.class, args)
到底做了哪些事情。本文將和大家一起看看SpringBoot啟動的大致流程,探討SpringBoot留給我們的擴充套件介面
二丶SpringBoot啟動流程分析
上面是SpringBoot呼叫SpringApplication.run(主類.class, args)
啟動的原始碼,原始碼並不複雜,整體流程大概如下
下面我們依據此圖,看看這些步驟SpringBoot底層原始碼
1.獲取SpringApplicationRunListener
實現類,包裝成SpringApplicationRunListeners
-
SpringApplicationRunListener
是SpringBoot框架中的監聽器,在SpringBoot啟動到達對應階段的時候,會回撥starting
,started
等方法。為什麼SpringBoot不適應Spring 裡面的
ApplicationListener
暱,因為ApplicationListener
依賴於Spring容器,@EventListener
註解需要EventListenerMethodProcessor
這個BeanFactoryPostProcessor
掃描,將對應的bean和方法包裝成ApplicationListener
註冊到ApplicationContext
中(最終註冊到ApplicationEventMulticaster
事件多播器中)對於ApplicationListener
型別bean則直接走註冊到ApplicationContext
的流程,整個流程只有Spring 容器啟動後才能進行,如果沒有SpringApplicationRunListener
則開發者無法在SpringBoot啟動對應階段進行一些擴充套件邏輯的回撥。 -
SpringApplicationRunListeners
可以看成是SpringApplicationRunListener
的門面(門面設計模式)
其使用List<SpringApplicationRunListener>
持有所有的SpringApplicationRunListener
,然後starting
等方法都是迴圈呼叫,集合中SpringApplicationRunListener
對應的方法
-
SpringBoot如何獲取所有的
SpringApplciationListener
這裡將從
META-INF/spring.factories
獲取org.springframework.boot.SpringApplicationRunListener
定義的實現類全限定類名,然後反射呼叫構造方法(SpringApplication application, String[] args)
進行例項化。隨後將根據@Order
或者Ordered
介面定義的順序進行排序,然後包裝成SpringApplicationRunListeners注意無法使用@Component註解 標註在
SpringApplciationListener
註解上,來實現事件的監聽,必須在META-INF/spring.factories
中定義,並且必須具備構造方法(SpringApplication application, String[] args)
。 -
EventPublishingRunListener
SpringApplication#addListeners
允許我們註冊ApplicationListener
到SpringBoot中,然後EventPublishingRunListener
其內部會new 一個簡單的事件多播器SimpleApplicationEventMulticaster
,在對應的SpringBoot啟動階段,推送事件。下面式如何註冊ApplicationListener注意這些ApplicationListener不會被註冊到Spring上下文中,意味著不會響應Spring上下文推送的事件,除非這個ApplicationListener是一個Spring Bean 並且被Spring管理。
下圖是
EventPublishingRunListener
在SpringBoot啟動的不同階段,推送事件
2.SpringApplicationListeners#starting
沒啥好說的,迴圈回撥SpringApplicationRunListener#starting
方法
3.prepareEnvironment 根據專案選擇Environment實現類,並例項化
在這一步,SpringBoot會根據類路徑中的類選擇一個Environment
並例項化,並且根據當前啟用的配置,選擇對應的配置檔案,進行解析,並儲存到Environment
中。下面是SpringBoot選擇Environment的原始碼
那麼SpringBoot是如何判斷當前專案是什麼應用型別暱?
其實根據類路徑下是否具備指定的類,然後得到指定型別,一般我們都是servlet應用,會選擇StandardServletEnvironment
4.SpringApplicationListeners#environmentPrepared
同2.SpringApplicationListeners#starting
5.createApplicationContext
根據類路徑指定類推斷使用什麼ConfigurableApplicationContext
(一般servlet應用使用AnnotationConfigServletWebServerApplicationContext)然後例項化AnnotationConfigServletWebServerApplicationContext
AnnotationConfigServletWebServerApplicationContext#onRefresh
方法在Spring容器重新整理後會被呼叫,這個方法將啟動Tomcat內嵌伺服器
6.prepareContext
這個方法主要會做以下操作
- 回撥
ApplicationContextInitializer#initialize
- 回撥所有
SpringApplicationRunListener#contextPrepared
- 將主類包裝成
BeanDefinition
,註冊到Spring容器上下文中 - 回撥所有
SpringApplicationRunListener#contextLoaded
利用SpringApplicationRunListeners
回撥SpringApplicationRunListener
,同2,不在贅述
6.1從META-INFO/spring.factories中拿所有ApplicationContextInitializer
然後回撥initialize方法
在spring上下文refresh方法呼叫前,會回撥initialize
方法
這裡呼叫前還會判斷ApplicationContextInitializer
定義的泛型,保證5這一步建立的上下文,符合泛型的要求
6.2 將主類包裝成BeanDefinition
,註冊到Spring容器上下文中
這一步非常重要,主類上的註解@SpringBootApplication
需要ConfigurationClassPostProcessor
解析,才能發揮@Import,@ComponentScan的作用,想要ConfigurationClassPostProcessor
處理主類的前提是主類的BeanDefinition需要在Spring容器中。
也就是說SpringBoot的自動裝配,和掃描包路徑下的Spring 元件的前提是,主類的BeanDefinition在Spring容器中
這裡的BeanDefinitionRegistry,其實就是來自5這一步的ApplicationContext,一般來說AnnotationConfigServletWebServerApplicationContext
內部持有了一個DefaultListableBeanFactory
,DefaultListableBeanFactory
是BeanDefinitionRegistry
的實現類,其底層使用一個ConcurrentHashMap
維護,key是bean的名稱,value是對應的BeanDefinition
當資源是一個Class
的時候,會使用AnnotatedBeanDefinitionReader
讀取Class
物件,生成BeanDefinition
這一步還支援xml的方式
7.回撥SpringApplicationRunListener#contextLoaded
同2
8.重新整理Spring容器上下文
《Spring原始碼學習筆記12——總結篇IOC,Bean的生命週期,三大擴充套件點》這篇部落格做了詳細的分析
這裡會進行自動裝配和包路徑掃描註冊BeanDefinition,然後例項化單例bean
9.回撥SpringApplicationRunListener#started
同2
10.callRunners
從spring容器中拿到ApplicationRunner,和CommandLineRunner呼叫run方法
三丶SpringApplication,ApplicationContext,BeanFactory 三平面
我們將SpringApplication看作是SpringBoot平面,ApplicationContext看作是Spring平面,BeanFactory看作是Bean工廠平面,SpringBoot啟動到觸發spring容器重新整理,然後觸發BeanFactory例項化所有單例,非懶載入bean的流程如下