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