Spring中容器的創(chuàng)建流程詳細(xì)解讀
前言
在容器準(zhǔn)備階段,需要告知容器從哪里讀取 Bean 信息以及環(huán)境信息。盡管,有時(shí)我們并未提供這些信息,但容器依然能正確創(chuàng)建。這得益于一些默認(rèn)行為(約定大于配置),所以,也不需要由我們主動(dòng)告知。
1. Bean 的準(zhǔn)備
Bean 的準(zhǔn)備階段離不開 BeanDefinationRegistry 和 BeanFactory。
這里需要明白 BeanDefination 對(duì)象代表的是 Bean 的描述信息。Bean 并不是從一開始就實(shí)例化放入容器的。相反,最開始容器只是擁有它的描述信息,所以我們將此階段稱為 Bean 的準(zhǔn)備階段。根據(jù)這些描述信息,Spring 能夠創(chuàng)建 Bean,并使它更“完整”。
BeanDefination 在 Spring 中的地位類似于我們的 Class 對(duì)象在 JVM 中的地位。
1.1 BeanDefinationRegistry
容器是通過 BeanDefinationRegistry 來注冊 BeanDefination 的。 我們可以通過 xml 文件或者 Java 配置的方式來聲明 BeanDefination,并在創(chuàng)建容器的時(shí)候指定 xml 文件或者 Java 類,然后,容器會(huì)掃描并注冊 BeanDefination。
那么,此階段我們的機(jī)會(huì)在哪里呢?其實(shí),Spring 已經(jīng)包含了足夠完善的從定義的 Bean 信息到 BeanDefination 注冊。但如果你仍然想自定義 BeanDefination,或者修改注冊的 BeanDefination ,可以通過獲取 BeanDefinationRegistry 對(duì)象。
Spring 通過回調(diào)的方式,調(diào)用實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口的類,所以,此類交由 Spring 管理。該類將在后面介紹的刷新階段回調(diào),此為機(jī)會(huì)一。
Spring 自身也定義了實(shí)現(xiàn) BeanDefinitionRegistryPostProcessor 接口的類來完成和我們一樣想要在運(yùn)行時(shí)自定義加載 BeanDefination 的功能,像 ConfigurationClassPostProcessor,調(diào)用時(shí)機(jī)和我們自定義的一樣,被調(diào)用的順序依賴實(shí)現(xiàn) PriorityOrdered 或 Order 接口。
平等對(duì)待第三方,在 dubbo 中見過這種說法,或許對(duì)于框架開發(fā),這種做法一直很重要。
1.2 BeanFactory
那么,容器中持有的 Bean 一定需要對(duì)應(yīng)的 BeanDefination 嘛?這不一定,我們可以直接通過 BeanFactory 來注冊實(shí)例化對(duì)象。Spring 在將一些環(huán)境信息對(duì)象放入容器中,也是這樣做的。
此時(shí),我們的機(jī)會(huì)又在哪里呢?兩種方式:
- 實(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)在,我們知道了,我們可以通過 BeanDefinationRegistry 操作 BeanDefination,通過 BeanFactory 操作 Bean,那么,如何獲得這兩個(gè)對(duì)象便是我們的機(jī)會(huì)所在。
環(huán)境信息
環(huán)境信息包括 profile 和 property,前者可以看作對(duì)環(huán)境信息做的分類,因?yàn)槲覀冃枰诓煌瑫r(shí)候,激活不同的環(huán)境,像生產(chǎn)和開發(fā)環(huán)境。后者是屬性,屬性可來自不同的屬性源。并且,通過環(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 接口來。
2.2 注冊 BeanPostProcessor
BeanPostProcessor 作用于對(duì)象初始化前后,我們實(shí)現(xiàn)這個(gè)接口便可以在對(duì)象初始化前后做處理。同樣,實(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ī)會(huì)給了 InstantiationAwareBeanPostProcessor 接口,該接口的 postProcessBeforeInstantiation() 方法如果返回了一個(gè)對(duì)象,該對(duì)象將用于存入容器,此為機(jī)會(huì)四。
如果上面的接口未返回對(duì)象,則還允許我們在根據(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ì)象。
比如說 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í)例化了對(duì)象 A 放入容器,這就會(huì)導(dǎo)致容器中的 A 和 B 依賴的 A 不是同一個(gè)對(duì)象。這違背了單例原則,所以,我們可以通過上述描述的接口來保證。
緊接著的回調(diào)機(jī)會(huì)給了 InstantiationAwareBeanPostProcessor 接口的 postProcessAfterInstantiation() 方法,此時(shí)還未給屬性賦值,但我們可以操作實(shí)例化的對(duì)象了。
再后來,給了我們修改屬性值的機(jī)會(huì),回調(diào) InstantiationAwareBeanPostProcessor 接口的 postProcessProperties() 方法。如果此方法返回 null ,還可再回調(diào) postProcessPropertyValues 方法。
接下來的過程就是初始化了。初始化之前,如果 Bean 實(shí)現(xiàn)了以下接口,那么會(huì)回調(diào)接口對(duì)應(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)先級(jí)過高,那么它可能在 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)建過程的回調(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í)例化過程有很多實(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)容請(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)建過程之如何注冊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注解來實(shí)現(xiàn)動(dòng)態(tài)切換服務(wù)提供商的功能,通過配置點(diǎn)和代理對(duì)象,實(shí)現(xiàn)動(dòng)態(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à)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12
Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享
這篇文章主要介紹了Java編程刪除鏈表中重復(fù)的節(jié)點(diǎn)問題解決思路及源碼分享,具有一定參考價(jià)值,這里分享給大家,供需要的朋友了解。2017-10-10
詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法
這篇文章主要介紹了詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù)
這篇文章主要介紹了如何使用Spring Validation優(yōu)雅地校驗(yàn)參數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
SpringBoot+Response如何統(tǒng)一返回result結(jié)果集
這篇文章主要介紹了SpringBoot+Response如何統(tǒng)一返回result結(jié)果集,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
詳解SpringBoot2 使用Spring Session集群
這篇文章主要介紹了SpringBoot2 使用Spring Session集群,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-04-04

