您好,登錄后才能下訂單哦!
這篇文章主要講解了“Spring Boot啟動流程是什么及怎么實現自動配置”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Spring Boot啟動流程是什么及怎么實現自動配置”吧!
拋磚引玉:探索 Spring IOC 容器
如果有看過 SpringApplication.run() 方法的源碼,Spring Boot 冗長無比的啟動流程一定會讓你抓狂。
透過現象看本質,SpringApplication 只是將一個典型的Spring應用的啟動流程進行了擴展,因此,透徹理解 Spring 容器是打開 Spring Boot 大門的一把鑰匙。
Spring IOC 容器
可以把 Spring IOC 容器比作一間餐館,當你來到餐館,通常會直接招呼服務員:點菜!至于菜的原料是什么?如何用原料把菜做出來?可能你根本就不關心。
IOC 容器也是一樣,你只需要告訴它需要某個 bean,它就把對應的實例(instance)扔給你,至于這個 bean 是否依賴其他組件,怎樣完成它的初始化,根本就不需要你關心。
作為餐館,想要做出菜肴,得知道菜的原料和菜譜,同樣地,IOC 容器想要管理各個業務對象以及它們之間的依賴關系,需要通過某種途徑來記錄和管理這些信息。
BeanDefinition 對象就承擔了這個責任:容器中的每一個 bean 都會有一個對應的 BeanDefinition 實例。
該實例負責保存 bean 對象的所有必要信息,包括 bean 對象的 class 類型、是否是抽象類、構造方法和參數、其他屬性等等。
當客戶端向容器請求相應對象時,容器就會通過這些信息為客戶端返回一個完整可用的 bean 實例。
原材料已經準備好(把 BeanDefinition 看做原料),開始做菜吧,等等,你還需要一份菜譜。
BeanDefinitionRegistry 和 BeanFactory 就是這份菜譜,BeanDefinitionRegistry 抽象出 bean 的注冊邏輯。
而 BeanFactory 則抽象出了 bean 的管理邏輯,而各個 BeanFactory 的實現類就具體承擔了 bean 的注冊以及管理工作。
它們之間的關系就如下圖:
BeanFactory、BeanDefinitionRegistry 關系圖(來自:Spring 揭秘)
DefaultListableBeanFactory 作為一個比較通用的 BeanFactory 實現,它同時也實現了 BeanDefinitionRegistry 接口,因此它就承擔了 bean 的注冊管理工作。
從圖中也可以看出,BeanFactory 接口中主要包含 getBean、containBean、getType、getAliases 等管理 bean 的方法。
而 BeanDefinitionRegistry 接口則包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注冊管理 BeanDefinition 的方法。
下面通過一段簡單的代碼來模擬 BeanFactory 底層是如何工作的:
這段代碼僅為了說明 BeanFactory 底層的大致工作流程,實際情況會更加復雜。
比如 bean 之間的依賴關系可能定義在外部配置文件(XML/Properties)中、也可能是注解方式。
Spring IoC 容器的整個工作流程大致可以分為兩個階段:
①容器啟動階段
容器啟動時,會通過某種途徑加載 ConfigurationMetaData。除了代碼方式比較直接外,在大部分情況下,容器需要依賴某些工具類。
比如:BeanDefinitionReader,它會對加載的 ConfigurationMetaData 進行解析和分析,并將分析后的信息組裝為相應的 BeanDefinition。
***把這些保存了 bean 定義的 BeanDefinition,注冊到相應的 BeanDefinitionRegistry,這樣容器的啟動工作就完成了。
這個階段主要完成一些準備性工作,更側重于 bean 對象管理信息的收集,當然一些驗證性或者輔助性的工作也在這一階段完成。
來看一個簡單的例子吧,過往,所有的 bean 都定義在 XML 配置文件中,下面的代碼將模擬 BeanFactory 如何從配置文件中加載 bean 的定義以及依賴關系:
②Bean 的實例化階段
經過***階段,所有 bean 定義都通過 BeanDefinition 的方式注冊到 BeanDefinitionRegistry 中。
當某個請求通過容器的 getBean 方法請求某個對象,或者因為依賴關系容器需要隱式的調用 getBean 時,就會觸發第二階段的活動:容器會首先檢查所請求的對象之前是否已經實例化完成。
如果沒有,則會根據注冊的 BeanDefinition 所提供的信息實例化被請求對象,并為其注入依賴。當該對象裝配完畢后,容器會立即將其返回給請求方使用。
BeanFactory 只是 Spring IoC 容器的一種實現,如果沒有特殊指定,它采用延遲初始化策略:只有當訪問容器中的某個對象時,才對該對象進行初始化和依賴注入操作。
而在實際場景下,我們更多的使用另外一種類型的容器: ApplicationContext,它構建在 BeanFactory 之上,屬于更高級的容器。
除了具有 BeanFactory 的所有能力之外,還提供對事件監聽機制以及國際化的支持等。它管理的 bean,在容器啟動時全部完成初始化和依賴注入操作。
Spring 容器擴展機制
IoC 容器負責管理容器中所有 bean 的生命周期,而在 bean 生命周期的不同階段,Spring 提供了不同的擴展點來改變 bean 的命運。
在容器的啟動階段, BeanFactoryPostProcessor 允許我們在容器實例化相應對象之前,對注冊到容器的 BeanDefinition 所保存的信息做一些額外的操作,比如修改bean定義的某些屬性或者增加其他信息等。
如果要自定義擴展類,通常需要實現 org.springframework.beans.factory.config.BeanFactoryPostProcessor 接口。
與此同時,因為容器中可能有多個 BeanFactoryPostProcessor,可能還需要實現 org.springframework.core.Ordered 接口,以保證 BeanFactoryPostProcessor 按照順序執行。
Spring 提供了為數不多的 BeanFactoryPostProcessor 實現,我們以 PropertyPlaceholderConfigurer 來說明其大致的工作流程。
在 Spring 項目的 XML 配置文件中,經常可以看到許多配置項的值使用占位符,而將占位符所代表的值單獨配置到獨立的 properties 文件。
這樣可以將散落在不同 XML 文件中的配置集中管理,而且也方便運維根據不同的環境進行配置不同的值。這個非常實用的功能就是由 PropertyPlaceholderConfigurer 負責實現的。
根據前文,當 BeanFactory 在***階段加載完所有配置信息時,BeanFactory 中保存的對象的屬性還是以占位符方式存在的,比如 ${jdbc.mysql.url}。
當 PropertyPlaceholderConfigurer 作為 BeanFactoryPostProcessor 被應用時,它會使用 properties 配置文件中的值來替換相應的 BeanDefinition 中占位符所表示的屬性值。
當需要實例化 bean 時,bean 定義中的屬性值就已經被替換成我們配置的值。
當然其實現比上面描述的要復雜一些,這里僅說明其大致工作原理,更詳細的實現可以參考其源碼。
與之相似的,還有 BeanPostProcessor,其存在于對象實例化階段。跟 BeanFactoryPostProcessor 類似,它會處理容器內所有符合條件并且已經實例化后的對象。
簡單的對比,BeanFactoryPostProcessor 處理 bean 的定義,而 BeanPostProcessor 則處理 bean 完成實例化后的對象。
BeanPostProcessor 定義了兩個接口:
為了理解這兩個方法執行的時機,簡單的了解下 bean 的整個生命周期:
Bean 的實例化過程(來自:Spring 揭秘)
postProcessBeforeInitialization()方法與 postProcessAfterInitialization() 分別對應圖中前置處理和后置處理兩個步驟將執行的方法。
這兩個方法中都傳入了 bean 對象實例的引用,為擴展容器的對象實例化過程提供了很大便利,在這兒幾乎可以對傳入的實例執行任何操作。
注解、AOP 等功能的實現均大量使用了 BeanPostProcessor,比如有一個自定義注解,你完全可以實現 BeanPostProcessor 的接口,在其中判斷 bean 對象的腦袋上是否有該注解。
如果有,你可以對這個 bean 實例執行任何操作,想想是不是非常的簡單?
再來看一個更常見的例子,在 Spring 中經常能夠看到各種各樣的 Aware 接口,其作用就是在對象實例化完成以后將 Aware 接口定義中規定的依賴注入到當前實例中。
比如最常見的 ApplicationContextAware 接口,實現了這個接口的類都可以獲取到一個 ApplicationContext 對象。
當容器中每個對象的實例化過程走到 BeanPostProcessor 前置處理這一步時,容器會檢測到之前注冊到容器的 ApplicationContextAwareProcessor。
然后就會調用其 postProcessBeforeInitialization() 方法,檢查并設置 Aware 相關依賴。
看看代碼吧,是不是很簡單:
***總結一下,本小節內容和你一起回顧了 Spring 容器的部分核心內容,限于篇幅不能寫更多,但理解這部分內容,足以讓您輕松理解 Spring Boot 的啟動原理。
如果在后續的學習過程中遇到一些晦澀難懂的知識,再回過頭來看看 Spring 的核心知識,也許有意想不到的效果。
也許 Spring Boot 的中文資料很少,但 Spring 的中文資料和書籍有太多太多,總有東西能給你啟發。
夯實基礎:JavaConfig 與常見 Annotation
JavaConfig
我們知道 bean 是 Spring IOC 中非常核心的概念,Spring 容器負責 bean 的生命周期的管理。
在最初,Spring 使用 XML 配置文件的方式來描述 bean 的定義以及相互間的依賴關系,但隨著 Spring 的發展,越來越多的人對這種方式表示不滿。
因為 Spring 項目的所有業務類均以 bean 的形式配置在 XML 文件中,造成了大量的 XML 文件,使項目變得復雜且難以管理。
后來,基于純 Java Annotation 依賴注入框架 Guice 出世,其性能明顯優于采用 XML 方式的 Spring。
甚至有部分人認為, Guice 可以完全取代 Spring( Guice 僅是一個輕量級 IOC 框架,取代 Spring 還差的挺遠)。
正是這樣的危機感,促使 Spring 及社區推出并持續完善了 JavaConfig 子項目,它基于 Java 代碼和 Annotation 注解來描述 bean 之間的依賴綁定關系。
比如,下面是使用 XML 配置方式來描述 bean 的定義:
而基于 JavaConfig 的配置形式是這樣的:
如果兩個 bean 之間有依賴關系的話,在 XML 配置中應該是這樣:
而在 JavaConfig 中則是這樣:
你可能注意到這個示例中,有兩個 bean 都依賴于 dependencyService,也就是說當初始化 bookService 時會調用 dependencyService(),在初始化 otherService 時也會調用 dependencyService(),那么問題來了?
這時候 IOC 容器中是有一個 dependencyService 實例還是兩個?這個問題留著大家思考吧,這里不再贅述。
@ComponentScan
@ComponentScan 注解對應XML配置形式中的
我們可以通過 basePackages 等屬性來指定 @ComponentScan 自動掃描的范圍,如果不指定,默認從聲明 @ComponentScan 所在類的 package 進行掃描。正因為如此,SpringBoot 的啟動類都默認在 src/main/java 下。
@Import
@Import 注解用于導入配置類,舉個簡單的例子:
現在有另外一個配置類,比如:MoonUserConfiguration,這個配置類中有一個 bean 依賴于 MoonBookConfiguration 中的 bookService,如何將這兩個 bean 組合在一起?
借助 @Import 即可:
需要注意的是,在 4.2 之前, @Import 注解只支持導入配置類,但是在 4.2 之后,它支持導入普通類,并將這個類作為一個 bean 的定義注冊到 IOC 容器中。
@Conditional
@Conditional 注解表示在滿足某種條件后才初始化一個 bean 或者啟用某些配置。
它一般用在由 @Component、@Service、@Configuration 等注解標識的類上面,或者由 @Bean 標記的方法上。
如果一個 @Configuration 類標記了 @Conditional,則該類中所有標識了 @Bean 的方法和 @Import 注解導入的相關類將遵從這些條件。
在 Spring 里可以很方便的編寫你自己的條件類,所要做的就是實現 Condition 接口,并覆蓋它的 matches()方法。
舉個例子,下面的簡單條件類表示只有在 Classpath 里存在 JdbcTemplate 類時才生效:
當你用 Java 來聲明 bean 的時候,可以使用這個自定義條件類:
這個例子中只有當 JdbcTemplateCondition 類的條件成立時才會創建 MyService 這個 bean。
也就是說 MyService 這 bean 的創建條件是 classpath 里面包含 JdbcTemplate,否則這個 bean 的聲明就會被忽略掉。
Spring Boot 定義了很多有趣的條件,并把他們運用到了配置類上,這些配置類構成了 Spring Boot 的自動配置的基礎。
Spring Boot 運用條件化配置的方法是:定義多個特殊的條件化注解,并將它們用到配置類上。
下面列出了 Spring Boot 提供的部分條件化注解:
@ConfigurationProperties與@EnableConfigurationProperties
當某些屬性的值需要配置的時候,我們一般會在 application.properties 文件中新建配置項,然后在 bean 中使用 @Value 注解來獲取配置的值。
比如下面配置數據源的代碼:
使用 @Value 注解注入的屬性通常都比較簡單,如果同一個配置在多個地方使用,也存在不方便維護的問題(考慮下,如果有幾十個地方在使用某個配置,而現在你想改下名字,你該怎么做?)。
對于更為復雜的配置,Spring Boot 提供了更優雅的實現方式,那就是 @ConfigurationProperties 注解。
我們可以通過下面的方式來改寫上面的代碼:
@ConfigurationProperties 對于更為復雜的配置,處理起來也是得心應手,比如有如下配置文件:
可以定義如下配置類來接收這些屬性:
@EnableConfigurationProperties 注解表示對 @ConfigurationProperties 的內嵌支持,默認會將對應 Properties Class 作為 bean 注入的 IOC 容器中,即在相應的 Properties 類上不用加 @Component 注解。
削鐵如泥:SpringFactoriesLoader 詳解
JVM 提供了 3 種類加載器: BootstrapClassLoader、 ExtClassLoader、 AppClassLoader 分別加載 Java 核心類庫、擴展類庫以及應用的類路徑( CLASSPATH)下的類庫。
JVM 通過雙親委派模型進行類的加載,我們也可以通過繼承 java.lang.classloader 實現自己的類加載器。
何為雙親委派模型?當一個類加載器收到類加載任務時,會先交給自己的父加載器去完成。
因此最終加載任務都會傳遞到最頂層的 BootstrapClassLoader,只有當父加載器無法完成加載任務時,才會嘗試自己來加載。
采用雙親委派模型的一個好處是保證使用不同類加載器最終得到的都是同一個對象,這樣就可以保證 Java 核心庫的類型安全。
比如,加載位于 rt.jar 包中的 java.lang.Object 類,不管是哪個加載器加載這個類,最終都是委托給頂層的 BootstrapClassLoader 來加載的,這樣就可以保證任何的類加載器最終得到的都是同樣一個 Object 對象。
查看 ClassLoader 的源碼,對雙親委派模型會有更直觀的認識:
但雙親委派模型并不能解決所有的類加載器問題,比如,Java 提供了很多服務提供者接口( ServiceProviderInterface,SPI),允許第三方為這些接口提供實現。
常見的 SPI 有 JDBC、JNDI、JAXP 等,這些 SPI 的接口由核心類庫提供,卻由第三方實現。
這樣就存在一個問題:SPI 的接口是 Java 核心庫的一部分,是由 BootstrapClassLoader 加載的;SPI 實現的 Java 類一般是由 AppClassLoader 來加載的。
BootstrapClassLoader 是無法找到 SPI 的實現類的,因為它只加載 Java 的核心庫。
它也不能代理給 AppClassLoader,因為它是最頂層的類加載器。也就是說,雙親委派模型并不能解決這個問題。
線程上下文類加載器( ContextClassLoader)正好解決了這個問題。從名稱上看,可能會誤解為它是一種新的類加載器,實際上,它僅僅是 Thread 類的一個變量而已。
可以通過 setContextClassLoader(ClassLoadercl)和 getContextClassLoader()來設置和獲取該對象。
如果不做任何的設置,Java 應用的線程的上下文類加載器默認就是 AppClassLoader。
在核心類庫使用 SPI 接口時,傳遞的類加載器使用線程上下文類加載器,就可以成功的加載到 SPI 實現的類。
線程上下文類加載器在很多 SPI 的實現中都會用到。但在 JDBC 中,你可能會看到一種更直接的實現方式。
比如,JDBC 驅動管理 java.sql.Driver 中的 loadInitialDrivers() 方法中,你可以直接看到 JDK 是如何加載驅動的:
其實講解線程上下文類加載器,最主要是讓大家在看到 Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()時不會一臉懵逼。
這兩者除了在許多底層框架中取得的 ClassLoader 可能會有所不同外,其他大多數業務場景下都是一樣的,大家只要知道它是為了解決什么問題而存在的即可。
類加載器除了加載 class 外,還有一個非常重要功能,就是加載資源,它可以從 jar 包中讀取任何資源文件。
比如, ClassLoader.getResources(Stringname) 方法就是用于讀取 jar 包中的資源文件,其代碼如下:
是不是覺得有點眼熟,沒錯,它的邏輯其實跟類加載的邏輯是一樣的,首先判斷父類加載器是否為空,不為空則委托父類加載器執行資源查找任務,直到 BootstrapClassLoader,***才輪到自己查找。
而不同的類加載器負責掃描不同路徑下的 jar 包,就如同加載 class 一樣,***會掃描所有的 jar 包,找到符合條件的資源文件。
類加載器的 findResources(name) 方法會遍歷其負責加載的所有 jar 包,找到 jar 包中名稱為 name 的資源文件,這里的資源可以是任何文件,甚至是 .class 文件。
比如下面的示例,用于查找 Array.class 文件:
運行后可以得到如下結果:
根據資源文件的 URL,可以構造相應的文件來讀取資源內容。
看到這里,你可能會感到挺奇怪的,你不是要詳解 SpringFactoriesLoader 嗎?上來講了一堆 ClassLoader 是幾個意思?
看下它的源碼你就知道了:
有了前面關于 ClassLoader 的知識,再來理解這段代碼,是不是感覺豁然開朗:從 CLASSPATH 下的每個 jar 包中搜尋所有 META-INF/spring.factories 配置文件,然后將解析 properties 文件,找到指定名稱的配置后返回。
需要注意的是,其實這里不僅僅是會去 ClassPath 路徑下查找,會掃描所有路徑下的 jar 包,只不過這個文件只會在 ClassPath 下的 jar 包中。
來簡單看下 spring.factories 文件的內容吧:
執行 loadFactoryNames(EnableAutoConfiguration.class,classLoader)后,得到對應的一組 @Configuration 類。
我們就可以通過反射實例化這些類然后注入到 IOC 容器中,***容器里就有了一系列標注了 @Configuration 的 JavaConfig 形式的配置類。
這就是 SpringFactoriesLoader,它本質上屬于 Spring 框架私有的一種擴展方案,類似于 SPI,Spring Boot 在 Spring 基礎上的很多核心功能都是基于此,希望大家可以理解。
另一件武器:Spring 容器的事件監聽機制
過去,事件監聽機制多用于圖形界面編程,比如:點擊按鈕、在文本框輸入內容等操作被稱為事件。
而當事件觸發時,應用程序作出一定的響應則表示應用監聽了這個事件,而在服務器端,事件的監聽機制更多的用于異步通知以及監控和異常處理。
Java 提供了實現事件監聽機制的兩個基礎類:自定義事件類型擴展自 java.util.EventObject、事件的監聽器擴展自 java.util.EventListener。
來看一個簡單的實例:簡單的監控一個方法的耗時。
首先定義事件類型,通常的做法是擴展 EventObject,隨著事件的發生,相應的狀態通常都封裝在此類中:
事件發布之后,相應的監聽器即可對該類型的事件進行處理,我們可以在方法開始執行之前發布一個 begin 事件。
在方法執行結束之后發布一個 end 事件,相應地,事件監聽器需要提供方法對這兩種情況下接收到的事件進行處理:
事件監聽器接口針對不同的事件發布實際提供相應的處理方法定義,最重要的是,其方法只接收 MethodMonitorEvent 參數,說明這個監聽器類只負責監聽器對應的事件并進行處理。
有了事件和監聽器,剩下的就是發布事件,然后讓相應的監聽器監聽并處理。
通常情況,我們會有一個事件發布者,它本身作為事件源,在合適的時機,將相應的事件發布給對應的事件監聽器:
對于事件發布者(事件源)通常需要關注兩點:
在合適的時機發布事件。此例中的 methodMonitor() 方法是事件發布的源頭,其在方法執行之前和結束之后兩個時間點發布 MethodMonitorEvent 事件,每個時間點發布的事件都會傳給相應的監聽器進行處理。
在具體實現時需要注意的是,事件發布是順序執行,為了不影響處理性能,事件監聽器的處理邏輯應盡量簡單。
事件監聽器的管理。publisher 類中提供了事件監聽器的注冊與移除方法,這樣客戶端可以根據實際情況決定是否需要注冊新的監聽器或者移除某個監聽器。
如果這里沒有提供 remove 方法,那么注冊的監聽器示例將一直被 MethodMonitorEventPublisher 引用,即使已經廢棄不用了,也依然在發布者的監聽器列表中,這會導致隱性的內存泄漏。
Spring 容器內的事件監聽機制
Spring 的 ApplicationContext 容器內部中的所有事件類型均繼承自 org.springframework.context.AppliationEvent。
容器中的所有監聽器都實現 org.springframework.context.ApplicationListener 接口,并且以 bean 的形式注冊在容器中。
一旦在容器內發布 ApplicationEvent 及其子類型的事件,注冊到容器的 ApplicationListener 就會對這些事件進行處理。
你應該已經猜到是怎么回事了。
ApplicationEvent 繼承自 EventObject,Spring 提供了一些默認的實現,比如:ContextClosedEvent 表示容器在即將關閉時發布的事件類型, ContextRefreshedEvent 表示容器在初始化或者刷新的時候發布的事件類型......
容器內部使用 ApplicationListener 作為事件監聽器接口定義,它繼承自 EventListener。
ApplicationContext 容器在啟動時,會自動識別并加載 EventListener 類型的bean,一旦容器內有事件發布,將通知這些注冊到容器的 EventListener。
ApplicationContext 接口繼承了 ApplicationEventPublisher 接口,該接口提供了 voidpublishEvent(ApplicationEventevent) 方法定義,不難看出,ApplicationContext 容器擔當的就是事件發布者的角色。
如果有興趣可以查看 AbstractApplicationContext.publishEvent(ApplicationEventevent) 方法的源碼:ApplicationContext 將事件的發布以及監聽器的管理工作委托給 ApplicationEventMulticaster 接口的實現類。
在容器啟動時,會檢查容器內是否存在名為 applicationEventMulticaster 的 ApplicationEventMulticaster 對象實例。
如果有就使用其提供的實現,沒有就默認初始化一個 SimpleApplicationEventMulticaster 作為實現。
***,如果我們業務需要在容器內部發布事件,只需要為其注入 ApplicationEventPublisher 依賴即可實現 ApplicationEventPublisherAware 接口或者 ApplicationContextAware 接口(Aware 接口相關內容請回顧上文)。
出神入化:揭秘自動配置原理
典型的 Spring Boot 應用的啟動類一般均位于 src/main/java 根路徑下,比如 MoonApplication 類:
其中 @SpringBootApplication 開啟組件掃描和自動配置,而 SpringApplication.run 則負責啟動引導應用程序。
@SpringBootApplication 是一個復合 Annotation,它將三個有用的注解組合在一起:
@SpringBootConfiguration 就是 @Configuration,它是 Spring 框架的注解,標明該類是一個 JavaConfig 配置類。
而 @ComponentScan 啟用組件掃描,前文已經詳細講解過,這里著重關注 @EnableAutoConfiguration。
@EnableAutoConfiguration 注解表示開啟 Spring Boot 自動配置功能,Spring Boot 會根據應用的依賴、自定義的 bean、Classpath 下有沒有某個類等等因素來猜測你需要的 bean,然后注冊到 IOC 容器中。
那 @EnableAutoConfiguration 是如何推算出你的需求?首先看下它的定義:
你的關注點應該在 @Import(EnableAutoConfigurationImportSelector.class)上了。
前文說過,@Import 注解用于導入類,并將這個類作為一個 bean 的定義注冊到容器中,這里它將把 EnableAutoConfigurationImportSelector 作為 bean 注入到容器中。
而這個類會將所有符合條件的 @Configuration 配置都加載到容器中,看看它的代碼:
這個類會掃描所有的 Jar 包,將所有符合條件的 @Configuration 配置類注入到容器中,何為符合條件,看看 META-INF/spring.factories 的文件內容:
以 DataSourceAutoConfiguration 為例,看看 Spring Boot 是如何自動配置的:
分別說一說:
@ConditionalOnClass({DataSource.class,EmbeddedDatabaseType.class}):當 Classpath 中存在 DataSource 或者 EmbeddedDatabaseType 類時才啟用這個配置,否則這個配置將被忽略。
@EnableConfigurationProperties(DataSourceProperties.class):將 DataSource 的默認配置類注入到 IOC 容器中,DataSourceproperties 定義為:
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }):導入其他額外的配置,就以 DataSourcePoolMetadataProvidersConfiguration 為例吧:
DataSourcePoolMetadataProvidersConfiguration 是數據庫連接池提供者的一個配置類。
即 Classpath 中存在 org.apache.tomcat.jdbc.pool.DataSource.class,則使用 tomcat-jdbc 連接池,如果 Classpath 中存在 HikariDataSource.class 則使用 Hikari 連接池。
這里僅描述了 DataSourceAutoConfiguration 的冰山一角,但足以說明 Spring Boot 如何利用條件化配置來實現自動配置的。
回顧一下,@EnableAutoConfiguration 中導入了 EnableAutoConfigurationImportSelector 類。
而這個類的 selectImports() 通過 SpringFactoriesLoader 得到了大量的配置類,而每一個配置類則根據條件化配置來做出決策,以實現自動配置。
整個流程很清晰,但漏了一個大問題: EnableAutoConfigurationImportSelector.selectImports() 是何時執行的?
其實這個方法會在容器啟動過程中執行: AbstractApplicationContext.refresh(),更多的細節在下一小節中說明。
啟動引導:Spring Boot 應用啟動的秘密
SpringApplication 初始化
Spring Boot 整個啟動流程分為兩個步驟:初始化一個 SpringApplication 對象、執行該對象的 run 方法。
看下 SpringApplication 的初始化流程,SpringApplication 的構造方法中調用 initialize(Object[] sources) 方法,其代碼如下:
初始化流程中最重要的就是通過 SpringFactoriesLoader 找到 spring.factories 文件中配置的 ApplicationContextInitializer 和 ApplicationListener 兩個接口的實現類名稱,以便后期構造相應的實例。
ApplicationContextInitializer 的主要目的是在 ConfigurableApplicationContext 做 refresh 之前,對 ConfigurableApplicationContext 實例做進一步的設置或處理。
ConfigurableApplicationContext 繼承自 ApplicationContext,其主要提供了對 ApplicationContext 進行設置的能力。
實現一個 ApplicationContextInitializer 非常簡單,因為它只有一個方法,但大多數情況下我們沒有必要自定義一個 ApplicationContextInitializer。
即便是 Spring Boot 框架,它默認也只是注冊了兩個實現,畢竟 Spring 的容器已經非常成熟和穩定,你沒有必要來改變它。
而 ApplicationListener 的目的就沒什么好說的了,它是 Spring 框架對 Java 事件監聽機制的一種框架實現,具體內容在前文 Spring 事件監聽機制這個小節有詳細講解。
這里主要說說,如果你想為 Spring Boot 應用添加監聽器,該如何實現?
Spring Boot 提供兩種方式來添加自定義監聽器:
通過 SpringApplication.addListeners(ApplicationListener...listeners)或者 SpringApplication.setListeners(Collection>listeners)兩個方法來添加一個或者多個自定義監聽器。
既然 SpringApplication 的初始化流程中已經從 spring.factories 中獲取到 ApplicationListener 的實現類,那么我們直接在自己的 jar 包的 META-INF/spring.factories 文件中新增配置即可:
關于 SpringApplication 的初始化,我們就說這么多。
Spring Boot 啟動流程
Spring Boot 應用的整個啟動流程都封裝在 SpringApplication.run 方法中,其整個流程真的是太長太長了,但本質上就是在 Spring 容器啟動的基礎上做了大量的擴展,按照這個思路來看看源碼:
①通過 SpringFactoriesLoader 查找并加載所有的 SpringApplicationRunListeners。
通過調用 starting() 方法通知所有的 SpringApplicationRunListeners:應用開始啟動了。
SpringApplicationRunListeners 其本質上就是一個事件發布者,它在 SpringBoot 應用啟動的不同時間點發布不同應用事件類型(ApplicationEvent)。
如果有哪些事件監聽者(ApplicationListener)對這些事件感興趣,則可以接收并且處理。
還記得初始化流程中,SpringApplication 加載了一系列 ApplicationListener 嗎?
這個啟動流程中沒有發現有發布事件的代碼,其實都已經在 SpringApplicationRunListeners 這兒實現了。
簡單的分析一下其實現流程,首先看下 SpringApplicationRunListener 的源碼:
SpringApplicationRunListener 只有一個實現類: EventPublishingRunListener。
①處的代碼只會獲取到一個 EventPublishingRunListener 的實例,我們來看看 starting() 方法的內容:
順著這個邏輯,你可以在 ② 處的 prepareEnvironment() 方法的源碼中找到 listeners.environmentPrepared(environment)。
即 SpringApplicationRunListener 接口的第二個方法,那不出你所料, environmentPrepared()又發布了另外一個事件 ApplicationEnvironmentPreparedEvent。接下來會發生什么,就不用我多說了吧。
②創建并配置當前應用將要使用的 Environment。
Environment 用于描述應用程序當前的運行環境,其抽象了兩個方面的內容:配置文件(profile)和屬性(properties)。
開發經驗豐富的同學對這兩個東西一定不會陌生:不同的環境(eg:生產環境、預發布環境)可以使用不同的配置文件,而屬性則可以從配置文件、環境變量、命令行參數等來源獲取。
因此,當 Environment 準備好后,在整個應用的任何時候,都可以從 Environment 中獲取資源。
總結起來,②處的兩句代碼,主要完成以下幾件事:
判斷 Environment 是否存在,不存在就創建(如果是 Web 項目就創建 StandardServletEnvironment,否則創建 StandardEnvironment)。
配置 Environment:配置 profile 以及 properties。
調用 SpringApplicationRunListener 的 environmentPrepared() 方法,通知事件監聽者:應用的 Environment 已經準備好。
③Spring Boot 應用在啟動時會輸出這樣的東西:
如果想把這個東西改成自己的涂鴉,你可以研究一下 Banner 的實現,這個任務就留給你們吧。
④根據是否是 Web 項目,來創建不同的 ApplicationContext 容器。
⑤創建一系列 FailureAnalyzer。
創建流程依然是通過 SpringFactoriesLoader 獲取到所有實現 FailureAnalyzer 接口的 class,然后再創建對應的實例。FailureAnalyzer 用于分析故障并提供相關診斷信息。
⑥初始化 ApplicationContext。
主要完成以下工作:
將準備好的 Environment 設置給 ApplicationContext。
遍歷調用所有的 ApplicationContextInitializer 的 initialize() 方法來對已經創建好的 ApplicationContext 進行進一步的處理。
調用 SpringApplicationRunListener 的 contextPrepared() 方法,通知所有的監聽者:ApplicationContext 已經準備完畢。
將所有的 bean 加載到容器中。
調用 SpringApplicationRunListener 的 contextLoaded() 方法,通知所有的監聽者:ApplicationContext 已經裝載完畢。
⑦調用 ApplicationContext 的 refresh() 方法,完成 IOC 容器可用的***一道工序。
從名字上理解為刷新容器,那何為刷新?就是插手容器的啟動,聯系一下***小節的內容。那如何刷新呢?
且看下面代碼:
看看這個方法的實現:
獲取到所有的 BeanFactoryPostProcessor 來對容器做一些額外的操作。BeanFactoryPostProcessor 允許我們在容器實例化相應對象之前,對注冊到容器的 BeanDefinition 所保存的信息做一些額外的操作。
這里的 getBeanFactoryPostProcessors() 方法可以獲取到 3 個 Processor:
不是有那么多 BeanFactoryPostProcessor 的實現類,為什么這兒只有這 3 個?
因為在初始化流程獲取到的各種 ApplicationContextInitializer 和 ApplicationListener 中,只有上文 3 個做了類似于如下操作:
然后你就可以進入到 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors() 方法了。
這個方法除了會遍歷上面的 3 個 BeanFactoryPostProcessor 處理外,還會獲取類型為 BeanDefinitionRegistryPostProcessor 的 bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor,對應的 Class 為 ConfigurationClassPostProcessor。
ConfigurationClassPostProcessor 用于解析處理各種注解,包括:
@Configuration
@ComponentScan
@Import
@PropertySource
@ImportResource
@Bean
當處理 @import 注解的時候,就會調用<自動配置>這一小節中的 EnableAutoConfigurationImportSelector.selectImports() 來完成自動配置功能。其他的這里不再多講,如果你有興趣,可以查閱參考資料 6。
⑧查找當前 context 中是否注冊有 CommandLineRunner 和 ApplicationRunner,如果有則遍歷執行它們。
⑨執行所有 SpringApplicationRunListener 的 finished() 方法。
這就是 Spring Boot 的整個啟動流程,其核心就是在 Spring 容器初始化并啟動的基礎上加入各種擴展點。
這些擴展點包括:ApplicationContextInitializer、ApplicationListener 以及各種 BeanFactoryPostProcessor 等等。
感謝各位的閱讀,以上就是“Spring Boot啟動流程是什么及怎么實現自動配置”的內容了,經過本文的學習后,相信大家對Spring Boot啟動流程是什么及怎么實現自動配置這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。