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