Spring中Bean的創(chuàng)建流程詳細(xì)解讀
前言
文章詳細(xì)描述一個(gè) Bean 誕生的過(guò)程,而文章的目錄結(jié)構(gòu),也清晰地反映了整個(gè)流程。
Spring 中創(chuàng)建 Bean ,是通過(guò)調(diào)用 GetBean 方法來(lái)觸發(fā)的。所以,我們會(huì)從這個(gè)方法開(kāi)始。這篇文章不會(huì)粘貼源碼,我會(huì)嘗試以簡(jiǎn)明流暢的語(yǔ)言來(lái)梳理整個(gè)過(guò)程。在最后我可能沒(méi)能達(dá)到這個(gè)目標(biāo),但讓你明白我并不是從一開(kāi)始就有意將文章復(fù)雜化也是有意義的。
1. 轉(zhuǎn)換 bean 名稱(chēng)
看見(jiàn)這一步,或許會(huì)有所疑惑,但這確實(shí)是第一步,因?yàn)槲覀冃枰话?ldquo;鑰匙”。這一步是將我們獲取 Bean 時(shí)所指定的名稱(chēng),轉(zhuǎn)換為 Spring 容器中所管理 Bean 的真實(shí)名稱(chēng)。
例如,在我們通過(guò)別名獲取 Bean 時(shí), 這一步則會(huì)將別名轉(zhuǎn)換為容器中 Bean 真實(shí)的名稱(chēng)。
或者,在我們實(shí)現(xiàn) FactoryBean 接口以期待通過(guò)工廠獲取 Bean 時(shí)。在想要獲取真正的工廠類(lèi)而不是所管理的 Bean ,這一步則會(huì)去掉前綴 &,因?yàn)樵谌萜髦写鎯?chǔ)的工廠 Bean 的真實(shí)名稱(chēng)是沒(méi)有前綴的。但如果不去掉前綴,則獲取到的是工廠所管理的 Bean,這在后文有案例。
如果單純地將容器看作一個(gè)鍵值對(duì)的話,這一步就是去獲取鍵的。
2. 從三級(jí)緩存中獲取
在獲取到真實(shí)的 beanName 后,會(huì)迫切地根據(jù)它去單例緩存中獲取已經(jīng)注冊(cè)過(guò)的單例。
這一步指的便是從三級(jí)緩存中獲取單例。在循環(huán)依賴(lài)的過(guò)程中,這一步能夠獲取到提前暴露的 Bean 實(shí)例。
3. 從父容器中獲取
如果在單例緩存中沒(méi)有獲取到,且該容器中不存在 beanName 對(duì)應(yīng)的 BeanDefinition 的話。那么,在父容器存在的情況下,將嘗試從父容器中獲取。
常見(jiàn)的 SpringMvc ,便會(huì)包含父子容器,將不同層次的 Bean 置于不同層次的容器中,也利于管理。
4. 合并 BeanDefinition
如果上述過(guò)程都未能成功獲取到 Bean 的話,就要考慮去創(chuàng)建 Bean 了。
首先需要準(zhǔn)備好 BeanDefinition 。如果一個(gè) BeanDefinition 指定了另一個(gè) BeanDefinition 作為 parent 的話,那么需要合并 BeanDefinition。
對(duì)應(yīng)的應(yīng)用場(chǎng)景是為一個(gè) Bean 指定了 parent 。我們可以把這看作“繼承”關(guān)系,但這并不是真正意義上的繼承關(guān)系。我想你能明白我這里描述的是什么。
5. 檢查依賴(lài)
準(zhǔn)備好 BeanDefinition 后,需要做依賴(lài)檢查。這指的是,如果我們對(duì)一個(gè) Bean 使用 @DependsOn 注解來(lái)顯式表明依賴(lài)關(guān)系的話,那這一步就能夠確保 DependsOn 注解中所指定的 Bean 會(huì)先創(chuàng)建。
6. 創(chuàng)建
現(xiàn)在,一切就緒,可以開(kāi)始創(chuàng)建 Bean 了。
在創(chuàng)建 Bean 之前,補(bǔ)充一個(gè)域的概念,這也是 Bean 的特點(diǎn)之一。不同域的 Bean 創(chuàng)建邏輯是否相同呢?
其實(shí),真正創(chuàng)建 Bean 的代碼是相同的,只有一份。但基于不同的域在創(chuàng)建 Bean 的前后會(huì)有不同的前置和后置邏輯。下面我們將一一分析各域的不同,并將相同的 Bean 的創(chuàng)建過(guò)程放在最后一節(jié)。
6.1 單例 Bean
單例 Bean 創(chuàng)建前,仍然需要判斷緩存。盡管在一開(kāi)始我們有判斷單例的緩存,但你想象這一場(chǎng)景:在創(chuàng)建 A 的過(guò)程中,上一步的依賴(lài)檢查發(fā)現(xiàn) A 顯示聲明依賴(lài)了 B,那么就會(huì)觸發(fā) B 的創(chuàng)建。但 B 又需要自動(dòng)裝配 A。這時(shí)又會(huì)觸發(fā) A 的創(chuàng)建,并創(chuàng)建成功,在最終回到 A 的創(chuàng)建過(guò)程時(shí),依賴(lài)檢查已經(jīng)結(jié)束,開(kāi)始進(jìn)入單例 Bean A 的創(chuàng)建。但此時(shí) A 其實(shí)已經(jīng)被提前創(chuàng)建成功了。
所以,此處仍然需要檢查緩存。不過(guò),這里檢測(cè)的只是一級(jí)緩存。若一級(jí)緩存singletonObjects 中存在該 Bean ,則直接返回。
6.2 原型 Bean
因?yàn)樵兔看味紩?huì)創(chuàng)建對(duì)象,所以不會(huì)存在從緩存中去獲取。但這里仍然需要做循環(huán)依賴(lài)的檢查,并且原型 Bean 間的循環(huán)依賴(lài)無(wú)論如何,都無(wú)法解決的。我們可以在腦海中仔細(xì)想一想,是不是這個(gè)道理。
6.3 其它域 Bean
常見(jiàn)的其它域有 request、session 和 application 域。這里的 application 在 web 應(yīng)用中指的便是 servletContext 級(jí)別的,也就是在一個(gè) web 應(yīng)用中有效。request 域僅在一次請(qǐng)求中有效,session 域僅在一次會(huì)話中有效。失效后,下次將重新創(chuàng)建。也可以將它們理解為單例,不過(guò)它們的單例限制范圍更小。
這些域需要尊重它們的范圍限制,例如,request 域無(wú)法在一個(gè) web 請(qǐng)求外部使用。并且,這些域創(chuàng)建的 Bean 會(huì)緩存在它們的范圍中,具體的可參考 Scope 接口對(duì)應(yīng)的各實(shí)現(xiàn)類(lèi),這也是其它域的不同點(diǎn)所在。
這里可能會(huì)有一點(diǎn)疑惑,我們經(jīng)常在 controller 中自動(dòng)裝配 ServletRequest,而 controller 作為單例對(duì)象,將在啟動(dòng)時(shí)完成創(chuàng)建并初始化。那么,這時(shí)注入的 request 是從哪里來(lái)的呢?
其實(shí),這是因?yàn)?WebApplicationContext 提前注冊(cè)了可以解析的依賴(lài),然后將 ServletRequest 接口類(lèi)型映射到 RequestObjectFactory 對(duì)象。這樣在填充 controller 的屬性時(shí),可以發(fā)現(xiàn) ServletRequest 的裝配值是 RequestObjectFactory。你可以把這里理解為一個(gè)作弊過(guò)程,如果正常途徑無(wú)法幫助我們,那么我們只能尋求 boss 幫助了,類(lèi)似于這里的 WebApplicationContext。
RequestObjectFactory 也不是ServletRequest 類(lèi)型,而是一個(gè) ObjectFactory 的實(shí)現(xiàn)類(lèi)。所以最終會(huì)生成一個(gè) ServletRequest 代理,執(zhí)行請(qǐng)求則會(huì)委托給 RequestObjectFactory對(duì)象中返回的 ServletRequest。這個(gè) ServletRequest 就和我們真正的 Web 請(qǐng)求有關(guān)了。這種做法很巧妙的將啟動(dòng)時(shí)便需要的對(duì)象和運(yùn)行時(shí)才產(chǎn)生的對(duì)象通過(guò)代理關(guān)聯(lián)了起來(lái)。
6.4 真正的創(chuàng)建 Bean
前文說(shuō)過(guò),創(chuàng)建 Bean 的代碼是相同的,在這里,我們將統(tǒng)一介紹這一過(guò)程。
6.4.1 InstantiationAwareBeanPostProcessor
給 InstantiationAwareBeanPostProcessor 一個(gè)機(jī)會(huì)
首先,會(huì)給 InstantiationAwareBeanPostProcessor 一個(gè)機(jī)會(huì)創(chuàng)建 Bean。
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { return null; }
若在調(diào)用該方法時(shí)返回了一個(gè)對(duì)象,那么會(huì)將其用著參數(shù),接著調(diào)用 BeanPostProcessor 的 postProcessAfterInitialization 方法。
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }
最后,此處返回的 Object 將是最終產(chǎn)生的 Bean。 如果最終返回的 Object 不為 null 的話,那么整個(gè)創(chuàng)建 Bean 的過(guò)程就提前完成,不再執(zhí)行后面的流程。這是 bean 創(chuàng)建提前結(jié)束的機(jī)會(huì),也是我們干預(yù) bean 的創(chuàng)建機(jī)會(huì)。
6.4.2 真實(shí)地創(chuàng)建 Bean
現(xiàn)在,開(kāi)始真實(shí)地創(chuàng)建 Bean 。主要過(guò)程分別是實(shí)例化,填充屬性,初始化,銷(xiāo)毀注冊(cè),下面簡(jiǎn)要總結(jié)這四個(gè)過(guò)程。
- 實(shí)例化:
- 從 BeanDefinition 提供的 Supplier 中返回;
- 從工廠方法中返回;
- 反射調(diào)用構(gòu)造函數(shù)返回;
- 自動(dòng)裝配的構(gòu)造函數(shù)調(diào)用;
- 無(wú)參構(gòu)造函數(shù)調(diào)用;
注:需要注意的是,如果這里是創(chuàng)建單例對(duì)象,那么在實(shí)例化后,屬性填充前,需要提前暴露實(shí)例化的對(duì)象到單例緩存中;
- 填充屬性:在填充屬性前,會(huì)給 InstantiationAwareBeanPostProcessor 一個(gè)機(jī)會(huì)來(lái)修改 Bean。
- 初始化
- 實(shí)現(xiàn) XXXAware 相關(guān)接口的方法回調(diào);
- 回調(diào) BeanPostProcessor.postProcessBeforeInitialization;
- 調(diào)用初始化方法
- InitializingBean.afterPropertiesSet
- init-method
- 回調(diào) BeanPostProcessor.postProcessAfterInitialization ;
注:如果這里是創(chuàng)建單例對(duì)象,在完成初始化后,需要檢測(cè)對(duì)象修改狀態(tài),這是為了解決循環(huán)依賴(lài)的。
- 銷(xiāo)毀注冊(cè)
- 檢測(cè)是否需要銷(xiāo)毀: 實(shí)現(xiàn)了 DisposableBean,
AutoCloseable 接口,或者定義的內(nèi)部方法:close 或者 shutdown, 或者 自定義 DestructionAwareBeanPostProcessor 接口的實(shí)現(xiàn)類(lèi)來(lái)決定是否需要消毀。 - 單例 Bean 的銷(xiāo)毀:通過(guò) DefaultSingletonBeanRegistry 中注冊(cè) DisposableBeanAdapter,并在合適的時(shí)機(jī)調(diào)用銷(xiāo)毀方法即可;
- prototype 域 Bean 的銷(xiāo)毀:不支持,因?yàn)?Spring 不管理 prototype 域 Bean 的生命周期,自然也不會(huì)去銷(xiāo)毀它。
- request 域 Bean 的銷(xiāo)毀: 通過(guò) ServletRequestListener 監(jiān)聽(tīng)器實(shí)現(xiàn);
- session 域 Bean 的銷(xiāo)毀:通過(guò) HttpSessionBindingListener 監(jiān)聽(tīng)器實(shí)現(xiàn);
- application 域 Bean 的銷(xiāo)毀:通過(guò) ServletContextListener 監(jiān)聽(tīng)器實(shí)現(xiàn);
- 檢測(cè)是否需要銷(xiāo)毀: 實(shí)現(xiàn)了 DisposableBean,
關(guān)于如何通過(guò)監(jiān)聽(tīng)器實(shí)現(xiàn),可以通過(guò) Scope 接口的實(shí)現(xiàn)類(lèi)來(lái)了解這個(gè)過(guò)程。
整個(gè) Bean 的創(chuàng)建流程便完了,在這里也只是簡(jiǎn)述,因?yàn)槲乙婚_(kāi)始的目標(biāo)是不想過(guò)于復(fù)雜。但現(xiàn)在,先讓我們跳出 Bean 的創(chuàng)建流程往下走,因?yàn)槲覀冎肋€有一些“善后措施”要做。
7. 獲取對(duì)象
為什么在創(chuàng)建完 Bean 后還要獲取對(duì)象呢,因?yàn)橛?FactoryBean 的緣故,這也是我們?cè)谇拔霓D(zhuǎn)換名稱(chēng)一節(jié)所提到的。
@Component public class SessionFactoryBean implements SmartFactoryBean<SessionFactoryBean.Session> { @Override public Session getObject() throws Exception { return new Session("first"); } @Override public Class<?> getObjectType() { return Session.class; } @Override public boolean isSingleton() { return true; } @Override public boolean isEagerInit() { return true; } public static class Session{ private String name; protected Session(String name){ this.name = name; } public String getName(){ return name; } } }
上述代碼實(shí)現(xiàn)了 SmartFactoryBean 接口,它是一個(gè) FactoryBean。 我們?cè)谕ㄟ^(guò)名稱(chēng)獲取對(duì)象時(shí):
final Object sessionFactoryBean = annotationConfigApplicationContext.getBean("sessionFactoryBean");
實(shí)際上獲取到的會(huì)是 Session 對(duì)象 ,但在容器中管理的 Bean,其實(shí)是 SessionFactoryBean 。所以,容器內(nèi)部在獲取到 Bean 后(無(wú)論是新創(chuàng)建的還是緩存的),需要在獲取對(duì)象這一步來(lái)計(jì)算出你真正想要的對(duì)象。當(dāng)然,如果你要獲取的是真正的工廠對(duì)象,通過(guò) “&sessionFactoryBean” 名稱(chēng)來(lái)獲取就好了。
8. 寫(xiě)在最后
現(xiàn)在,看看我們都經(jīng)歷了什么。
首先是找到 Bean 真實(shí)的名稱(chēng),然后再?lài)L試從緩存,從父容器中去獲取。如果都沒(méi)有取到的話,就開(kāi)始合并 BeanDefinition,檢查顯式聲明的依賴(lài)。如果都沒(méi)有問(wèn)題的話,就可以開(kāi)始 Bean 的創(chuàng)建了。無(wú)論是什么域的 Bean,其實(shí)創(chuàng)建代碼都一樣,只是前置后置條件不同。而 Bean 的創(chuàng)建又有四個(gè)小的過(guò)程,也可稱(chēng)之為生命周期:實(shí)例化,計(jì)算屬性,初始化,bean 銷(xiāo)毀時(shí)的邏輯注冊(cè)。最后,Bean 無(wú)論是 從緩存中獲取成功還是創(chuàng)建成功,都需要通過(guò)計(jì)算獲取對(duì)象,因?yàn)橛锌赡芪覀儷@取到的 Bean 是一個(gè) FactoryBean,但我們實(shí)際上需要的卻是它所管理的對(duì)象。
完整的流程就在上面了。需要提醒的是在啟動(dòng)時(shí),只會(huì)完成單例 Bean (非延遲初始化)的創(chuàng)建。但如果單例 Bean 有屬性需要其它的 Bean,那么又會(huì)啟動(dòng)這些相關(guān) Bean 的創(chuàng)建過(guò)程。在顯式聲明依賴(lài)檢查時(shí),也是如此。如果當(dāng)前 Bean 有聲明依賴(lài)其它的 Bean,那么又會(huì)啟動(dòng)其它 Bean 的創(chuàng)建過(guò)程。
Bean 就是在這樣一個(gè)循環(huán)往復(fù)的過(guò)程中被創(chuàng)建,并在 Spring 的管理下生生不息的。
到此這篇關(guān)于Spring中Bean的創(chuàng)建流程詳細(xì)解讀的文章就介紹到這了,更多相關(guān)Spring中Bean的創(chuàng)建流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 淺析Spring容器原始Bean是如何創(chuàng)建的
- Spring?Bean創(chuàng)建的另一條捷徑
- SpringBoot中創(chuàng)建bean的7種方式總結(jié)
- Spring中Bean創(chuàng)建完后打印語(yǔ)句的兩種方法
- Spring Bean的定義及三種創(chuàng)建方式
- SpringBoot2基于重復(fù)創(chuàng)建bean的問(wèn)題及解決
- Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式
- Spring創(chuàng)建bean的幾種方式及使用場(chǎng)景
- SpringBoot在容器中創(chuàng)建實(shí)例@Component和@bean有什么區(qū)別
相關(guān)文章
Java連接MySQL數(shù)據(jù)庫(kù)增刪改查的通用方法(推薦)
下面小編就為大家?guī)?lái)一篇Java連接MySQL數(shù)據(jù)庫(kù)增刪改查的通用方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08java 算法之希爾排序詳解及實(shí)現(xiàn)代碼
這篇文章主要介紹了java 算法之希爾排序詳解及實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03非常實(shí)用的java自動(dòng)答題計(jì)時(shí)計(jì)分器
這篇文章主要為大家詳細(xì)介紹了非常實(shí)用的java自動(dòng)答題計(jì)時(shí)計(jì)分器的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Java動(dòng)態(tài)代理機(jī)制的實(shí)例詳解
這篇文章主要介紹了 Java動(dòng)態(tài)代理機(jī)制的實(shí)例詳解的相關(guān)資料,希望通過(guò)本文大家能夠掌握動(dòng)態(tài)代理機(jī)制,需要的朋友可以參考下2017-09-09JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn)
JVM啟動(dòng)時(shí)分配的內(nèi)存稱(chēng)為堆內(nèi)存,與之相對(duì)的,在代碼中還可以使用堆外內(nèi)存,比如Netty,廣泛使用了堆外內(nèi)存,下面這篇文章主要給大家介紹了關(guān)于JVM分配和回收堆外內(nèi)存的方式與注意點(diǎn),需要的朋友可以參考下2022-07-07解決IDEA中Maven下載依賴(lài)包過(guò)慢或報(bào)錯(cuò)的問(wèn)題
由于公司項(xiàng)目迭代,越來(lái)越多的項(xiàng)目開(kāi)始轉(zhuǎn)型新版本,由于我對(duì)Java一直不感冒,但要順應(yīng)公司項(xiàng)目要求,遂自己要逐步開(kāi)始完善Java相關(guān)的知識(shí)層面,此篇是我在學(xué)習(xí)SpringBoot時(shí)對(duì)一些不懂地方及遇到問(wèn)題時(shí)的記錄,需要的朋友可以參考下2024-02-02