Spring中容器的創(chuàng)建流程詳細(xì)解讀
前言
在容器準(zhǔn)備階段,需要告知容器從哪里讀取 Bean 信息以及環(huán)境信息。盡管,有時(shí)我們并未提供這些信息,但容器依然能正確創(chuàng)建。這得益于一些默認(rèn)行為(約定大于配置),所以,也不需要由我們主動告知。
1. Bean 的準(zhǔn)備
Bean 的準(zhǔn)備階段離不開 BeanDefinationRegistry 和 BeanFactory。
這里需要明白 BeanDefination 對象代表的是 Bean 的描述信息。Bean 并不是從一開始就實(shí)例化放入容器的。相反,最開始容器只是擁有它的描述信息,所以我們將此階段稱為 Bean 的準(zhǔn)備階段。根據(jù)這些描述信息,Spring 能夠創(chuàng)建 Bean,并使它更“完整”。
BeanDefination 在 Spring 中的地位類似于我們的 Class 對象在 JVM 中的地位。
1.1 BeanDefinationRegistry
容器是通過 BeanDefinationRegistry 來注冊 BeanDefination 的。 我們可以通過 xml 文件或者 Java 配置的方式來聲明 BeanDefination,并在創(chuàng)建容器的時(shí)候指定 xml 文件或者 Java 類,然后,容器會掃描并注冊 BeanDefination。
那么,此階段我們的機(jī)會在哪里呢?其實(shí),Spring 已經(jīng)包含了足夠完善的從定義的 Bean 信息到 BeanDefination 注冊。但如果你仍然想自定義 BeanDefination,或者修改注冊的 BeanDefination ,可以通過獲取 BeanDefinationRegistry 對象。
Spring 通過回調(diào)的方式,調(diào)用實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口的類,所以,此類交由 Spring 管理。該類將在后面介紹的刷新階段回調(diào),此為機(jī)會一。
Spring 自身也定義了實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口的類來完成和我們一樣想要在運(yùn)行時(shí)自定義加載 BeanDefination 的功能,像 ConfigurationClassPostProcessor,調(diào)用時(shí)機(jī)和我們自定義的一樣,被調(diào)用的順序依賴實(shí)現(xiàn) PriorityOrdered 或 Order 接口。
平等對待第三方,在 dubbo 中見過這種說法,或許對于框架開發(fā),這種做法一直很重要。
1.2 BeanFactory
那么,容器中持有的 Bean 一定需要對應(yīng)的 BeanDefination 嘛?這不一定,我們可以直接通過 BeanFactory 來注冊實(shí)例化對象。Spring 在將一些環(huán)境信息對象放入容器中,也是這樣做的。
此時(shí),我們的機(jī)會又在哪里呢?兩種方式:
- 實(shí)現(xiàn) BeanFactoryAware 接口。
- 實(shí)現(xiàn) BeanFactoryPostProcessor 接口。
實(shí)現(xiàn)用于拓展的接口的類都必須交由 Spring 管理才能觸發(fā)回調(diào),此為機(jī)會二。上述兩個(gè)回調(diào)的時(shí)機(jī)也都在刷新階段,后續(xù)將詳細(xì)敘述。
先做個(gè)小結(jié),現(xiàn)在,我們知道了,我們可以通過 BeanDefinationRegistry 操作 BeanDefination,通過 BeanFactory 操作 Bean,那么,如何獲得這兩個(gè)對象便是我們的機(jī)會所在。
環(huán)境信息
環(huán)境信息包括 profile 和 property,前者可以看作對環(huán)境信息做的分類,因?yàn)槲覀冃枰诓煌瑫r(shí)候,激活不同的環(huán)境,像生產(chǎn)和開發(fā)環(huán)境。后者是屬性,屬性可來自不同的屬性源。并且,通過環(huán)境信息,我們可以將 ${} 占位符替換為具體的屬性值。
那么,此時(shí)的機(jī)會在哪里呢?我們可以自定義一個(gè)實(shí)現(xiàn) EnvironmentAware 接口的類,此為機(jī)會三。
2. 刷新
刷新階段涉及到很多回調(diào),我們將一一分析。
2.1 調(diào)用 BeanFactoryPostProcessors
此階段包含了 BeanDefinitionRegistryPostProcessor 接口的回調(diào),因?yàn)?BeanDefinitionRegistryPostProcessor 接口繼承自 BeanFactoryPostProcessor。
調(diào)用順序?yàn)椋?/p>
- 調(diào)用 BeanDefinitionRegistryPostProcessor;
- 調(diào)用 BeanFactoryPostProcessor;實(shí)現(xiàn)相同接口的對象之間調(diào)用順序?qū)凑?nbsp;PriorityOrdered 和 Ordered 接口來。
2.2 注冊 BeanPostProcessor
BeanPostProcessor 作用于對象初始化前后,我們實(shí)現(xiàn)這個(gè)接口便可以在對象初始化前后做處理。同樣,實(shí)現(xiàn)了該接口后,需要交由 Spring 管理就好。 此階段僅僅是將 BeanPostProcessor 注冊到 BeanFactory 中,并不調(diào)用。注冊順序?yàn)椋?/p>
- 實(shí)現(xiàn)了
PriorityOrdered
的BeanPostProcessor
; - 實(shí)現(xiàn)了
Ordered
的BeanPostProcessor
; - 僅實(shí)現(xiàn)
BeanPostProcessor
; - 實(shí)現(xiàn)了
MergedBeanDefinitionPostProcessor
;
MergedBeanDefinitionPostProcessor 繼承自 BeanPostProcessor。
2.3 實(shí)例化所有非延遲實(shí)例化的單例
在實(shí)例化單例 Bean 時(shí),第一個(gè)回調(diào)機(jī)會給了 InstantiationAwareBeanPostProcessor 接口,該接口的 postProcessBeforeInstantiation() 方法如果返回了一個(gè)對象,該對象將用于存入容器,此為機(jī)會四。
如果上面的接口未返回對象,則還允許我們在根據(jù) BeanDefination 創(chuàng)建對象前,再修改 BeanDefination。第二個(gè)回調(diào)機(jī)會便給了 MergedBeanDefinitionPostProcessor 接口的 postProcessMergedBeanDefinition() 方法。此為機(jī)會五。
還有,在存在循環(huán)依賴的時(shí)候, 如果你想修改你已經(jīng)放入容器的對象,可以實(shí)現(xiàn) SmartInstantiationAwareBeanPostProcessor 接口,該接口的 getEarlyBeanReference() 方法將被調(diào)用。此接口的使用能夠保證其它對象依賴注入的是你修改后的對象,容器中也是你修改后的對象。
比如說 A 依賴了 B,B 依賴了 A。在檢測到 A 依賴 B 的時(shí)候,A 已經(jīng)實(shí)例化完成,這時(shí)候該去走創(chuàng)建B 的過程,然后在創(chuàng)建 B 的過程,發(fā)現(xiàn) A 已經(jīng)創(chuàng)建完成,可以依賴注入,B 的過程正常結(jié)束?;氐絼?chuàng)建 A 的過程,這時(shí),A 也正常注入 B,循環(huán)依賴過程完成了。若此時(shí),你再通過 BeanPostProcessor 重新實(shí)例化了對象 A 放入容器,這就會導(dǎo)致容器中的 A 和 B 依賴的 A 不是同一個(gè)對象。這違背了單例原則,所以,我們可以通過上述描述的接口來保證。
緊接著的回調(diào)機(jī)會給了 InstantiationAwareBeanPostProcessor 接口的 postProcessAfterInstantiation() 方法,此時(shí)還未給屬性賦值,但我們可以操作實(shí)例化的對象了。
再后來,給了我們修改屬性值的機(jī)會,回調(diào) InstantiationAwareBeanPostProcessor 接口的 postProcessProperties() 方法。如果此方法返回 null ,還可再回調(diào) postProcessPropertyValues 方法。
接下來的過程就是初始化了。初始化之前,如果 Bean 實(shí)現(xiàn)了以下接口,那么會回調(diào)接口對應(yīng)的方法:
- BeanNameAware;
- BeanClassLoaderAware;
- BeanFactoryAware;
然后,再回調(diào) BeanPostProcessor 的 postProcessBeforeInitialization 方法。 其實(shí),關(guān)于其它 Aware 接口的回調(diào),便是通過 ApplicationContextAwareProcessor 來做的,它是一個(gè) BeanPostProcessor。所以,如果你自定義了一個(gè) Bean,并且實(shí)現(xiàn)了 PriorityOrdered 接口,如果它的優(yōu)先級過高,那么它可能在 ApplicationContextAwareProcessor 之前調(diào)用,這樣你某些實(shí)現(xiàn)了 Aware 接口的 Bean,可能還尚未獲取 aware 注入的對象。
緊接著,就是調(diào)用 Bean 的初始化方法,實(shí)現(xiàn)了 InitializingBean 的 afterPropertiesSet() 方法或者指定的 init-method 方法將被調(diào)用。
初始化完后,便是 BeanPostProcessor 的 postProcessAfterInitialization() 的方法調(diào)用。
至此,容器創(chuàng)建過程的回調(diào)就介紹完了。
3. 寫在最后
我們的機(jī)會在于 BeanDefinationRegistry、BeanFactory 以及處理 Bean。需要注意各個(gè)回調(diào)接口的調(diào)用時(shí)機(jī)以及條件,這里整理下具體的回調(diào)順序如下:
- 前期(BeanFactoryPostProcessor)
- 處理 BeanDefination: BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry();
- 處理 BeanFactory: BeanFactoryPostProcessor.postProcessBeanFactory();
- 創(chuàng)建 Bean(BeanPostProcessor)
- 自定義實(shí)例化:InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(); 自定義實(shí)例化過程有很多實(shí)現(xiàn)方式,比如 FactoryBean 或者提供一個(gè) Supplier;
- 實(shí)例化前修改 BeanDefination: MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition() ;
- 實(shí)例化后,屬性賦值前:InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()
- 修改屬性(PropertyValue):InstantiationAwareBeanPostProcessor.postProcessProperties(),如果該方法返回 null,還有可以通過 postProcessPropertyValues() 方法來修改。
- 屬性賦值。
- 初始化前:BeanNameAware,BeanClassLoaderAware,BeanFactoryAware,BeanPostProcessor.postProcessBeforeInitialization()。
- 初始化:InitializingBean.afterPropertiesSet() 或者 init-method。
- 初始化后:BeanPostProcessor.postProcessAfterInitialization()。
梳理完上述過程,再結(jié)合我們自己的需要,就知道我們?nèi)绾瓮卣沽恕?/p>
一個(gè)容器的誕生,這個(gè)名字聽起來很舒服,這讓我想到一部電影-傳奇的誕生。盡管我所寫的是再平凡不過,但我仍愿為此賦予一個(gè)美好的寓意。
到此這篇關(guān)于Spring中容器的創(chuàng)建流程詳細(xì)解讀的文章就介紹到這了,更多相關(guān)Spring容器的創(chuàng)建流程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring-Smart-DI 動態(tài)切換實(shí)現(xiàn)類的步驟
文章介紹了如何使用spring-smart-di的@AutowiredProxySPI注解來實(shí)現(xiàn)動態(tài)切換服務(wù)提供商的功能,通過配置點(diǎn)和代理對象,實(shí)現(xiàn)動態(tài)切換而無需重啟服務(wù),感興趣的朋友一起看看吧2025-03-03在SpringBoot項(xiàng)目中使用Java8函數(shù)式接口的方法示例
在Spring Boot項(xiàng)目中,Java 8 的函數(shù)式接口廣泛用于實(shí)現(xiàn)各種功能,如自定義配置、數(shù)據(jù)處理等,函數(shù)式接口在Spring Boot中非常有用,本文展示了在SpringBoot項(xiàng)目中使用Java8的函數(shù)式接口的方法示例,需要的朋友可以參考下2024-03-03基于Java數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列的兩種方法小結(jié)
下面小編就為大家分享一篇基于Java數(shù)組實(shí)現(xiàn)循環(huán)隊(duì)列的兩種方法小結(jié),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享,具有一定參考價(jià)值,這里分享給大家,供需要的朋友了解。2017-10-10如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù)
這篇文章主要介紹了如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07SpringBoot+Response如何統(tǒng)一返回result結(jié)果集
這篇文章主要介紹了SpringBoot+Response如何統(tǒng)一返回result結(jié)果集,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05詳解SpringBoot2 使用Spring Session集群
這篇文章主要介紹了SpringBoot2 使用Spring Session集群,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-04-04