Spring中Bean的創(chuàng)建流程詳細解讀
前言
文章詳細描述一個 Bean 誕生的過程,而文章的目錄結構,也清晰地反映了整個流程。
Spring 中創(chuàng)建 Bean ,是通過調用 GetBean 方法來觸發(fā)的。所以,我們會從這個方法開始。這篇文章不會粘貼源碼,我會嘗試以簡明流暢的語言來梳理整個過程。在最后我可能沒能達到這個目標,但讓你明白我并不是從一開始就有意將文章復雜化也是有意義的。
1. 轉換 bean 名稱
看見這一步,或許會有所疑惑,但這確實是第一步,因為我們需要一把“鑰匙”。這一步是將我們獲取 Bean 時所指定的名稱,轉換為 Spring 容器中所管理 Bean 的真實名稱。
例如,在我們通過別名獲取 Bean 時, 這一步則會將別名轉換為容器中 Bean 真實的名稱。
或者,在我們實現(xiàn) FactoryBean 接口以期待通過工廠獲取 Bean 時。在想要獲取真正的工廠類而不是所管理的 Bean ,這一步則會去掉前綴 &,因為在容器中存儲的工廠 Bean 的真實名稱是沒有前綴的。但如果不去掉前綴,則獲取到的是工廠所管理的 Bean,這在后文有案例。
如果單純地將容器看作一個鍵值對的話,這一步就是去獲取鍵的。
2. 從三級緩存中獲取
在獲取到真實的 beanName 后,會迫切地根據(jù)它去單例緩存中獲取已經(jīng)注冊過的單例。
這一步指的便是從三級緩存中獲取單例。在循環(huán)依賴的過程中,這一步能夠獲取到提前暴露的 Bean 實例。
3. 從父容器中獲取
如果在單例緩存中沒有獲取到,且該容器中不存在 beanName 對應的 BeanDefinition 的話。那么,在父容器存在的情況下,將嘗試從父容器中獲取。
常見的 SpringMvc ,便會包含父子容器,將不同層次的 Bean 置于不同層次的容器中,也利于管理。
4. 合并 BeanDefinition
如果上述過程都未能成功獲取到 Bean 的話,就要考慮去創(chuàng)建 Bean 了。
首先需要準備好 BeanDefinition 。如果一個 BeanDefinition 指定了另一個 BeanDefinition 作為 parent 的話,那么需要合并 BeanDefinition。
對應的應用場景是為一個 Bean 指定了 parent 。我們可以把這看作“繼承”關系,但這并不是真正意義上的繼承關系。我想你能明白我這里描述的是什么。
5. 檢查依賴
準備好 BeanDefinition 后,需要做依賴檢查。這指的是,如果我們對一個 Bean 使用 @DependsOn 注解來顯式表明依賴關系的話,那這一步就能夠確保 DependsOn 注解中所指定的 Bean 會先創(chuàng)建。
6. 創(chuàng)建
現(xiàn)在,一切就緒,可以開始創(chuàng)建 Bean 了。
在創(chuàng)建 Bean 之前,補充一個域的概念,這也是 Bean 的特點之一。不同域的 Bean 創(chuàng)建邏輯是否相同呢?
其實,真正創(chuàng)建 Bean 的代碼是相同的,只有一份。但基于不同的域在創(chuàng)建 Bean 的前后會有不同的前置和后置邏輯。下面我們將一一分析各域的不同,并將相同的 Bean 的創(chuàng)建過程放在最后一節(jié)。
6.1 單例 Bean
單例 Bean 創(chuàng)建前,仍然需要判斷緩存。盡管在一開始我們有判斷單例的緩存,但你想象這一場景:在創(chuàng)建 A 的過程中,上一步的依賴檢查發(fā)現(xiàn) A 顯示聲明依賴了 B,那么就會觸發(fā) B 的創(chuàng)建。但 B 又需要自動裝配 A。這時又會觸發(fā) A 的創(chuàng)建,并創(chuàng)建成功,在最終回到 A 的創(chuàng)建過程時,依賴檢查已經(jīng)結束,開始進入單例 Bean A 的創(chuàng)建。但此時 A 其實已經(jīng)被提前創(chuàng)建成功了。
所以,此處仍然需要檢查緩存。不過,這里檢測的只是一級緩存。若一級緩存singletonObjects 中存在該 Bean ,則直接返回。
6.2 原型 Bean
因為原型每次都會創(chuàng)建對象,所以不會存在從緩存中去獲取。但這里仍然需要做循環(huán)依賴的檢查,并且原型 Bean 間的循環(huán)依賴無論如何,都無法解決的。我們可以在腦海中仔細想一想,是不是這個道理。
6.3 其它域 Bean
常見的其它域有 request、session 和 application 域。這里的 application 在 web 應用中指的便是 servletContext 級別的,也就是在一個 web 應用中有效。request 域僅在一次請求中有效,session 域僅在一次會話中有效。失效后,下次將重新創(chuàng)建。也可以將它們理解為單例,不過它們的單例限制范圍更小。
這些域需要尊重它們的范圍限制,例如,request 域無法在一個 web 請求外部使用。并且,這些域創(chuàng)建的 Bean 會緩存在它們的范圍中,具體的可參考 Scope 接口對應的各實現(xiàn)類,這也是其它域的不同點所在。
這里可能會有一點疑惑,我們經(jīng)常在 controller 中自動裝配 ServletRequest,而 controller 作為單例對象,將在啟動時完成創(chuàng)建并初始化。那么,這時注入的 request 是從哪里來的呢?
其實,這是因為 WebApplicationContext 提前注冊了可以解析的依賴,然后將 ServletRequest 接口類型映射到 RequestObjectFactory 對象。這樣在填充 controller 的屬性時,可以發(fā)現(xiàn) ServletRequest 的裝配值是 RequestObjectFactory。你可以把這里理解為一個作弊過程,如果正常途徑無法幫助我們,那么我們只能尋求 boss 幫助了,類似于這里的 WebApplicationContext。
RequestObjectFactory 也不是ServletRequest 類型,而是一個 ObjectFactory 的實現(xiàn)類。所以最終會生成一個 ServletRequest 代理,執(zhí)行請求則會委托給 RequestObjectFactory對象中返回的 ServletRequest。這個 ServletRequest 就和我們真正的 Web 請求有關了。這種做法很巧妙的將啟動時便需要的對象和運行時才產(chǎn)生的對象通過代理關聯(lián)了起來。
6.4 真正的創(chuàng)建 Bean
前文說過,創(chuàng)建 Bean 的代碼是相同的,在這里,我們將統(tǒng)一介紹這一過程。
6.4.1 InstantiationAwareBeanPostProcessor
給 InstantiationAwareBeanPostProcessor 一個機會
首先,會給 InstantiationAwareBeanPostProcessor 一個機會創(chuàng)建 Bean。
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { return null; }
若在調用該方法時返回了一個對象,那么會將其用著參數(shù),接著調用 BeanPostProcessor 的 postProcessAfterInitialization 方法。
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }
最后,此處返回的 Object 將是最終產(chǎn)生的 Bean。 如果最終返回的 Object 不為 null 的話,那么整個創(chuàng)建 Bean 的過程就提前完成,不再執(zhí)行后面的流程。這是 bean 創(chuàng)建提前結束的機會,也是我們干預 bean 的創(chuàng)建機會。
6.4.2 真實地創(chuàng)建 Bean
現(xiàn)在,開始真實地創(chuàng)建 Bean 。主要過程分別是實例化,填充屬性,初始化,銷毀注冊,下面簡要總結這四個過程。
- 實例化:
- 從 BeanDefinition 提供的 Supplier 中返回;
- 從工廠方法中返回;
- 反射調用構造函數(shù)返回;
- 自動裝配的構造函數(shù)調用;
- 無參構造函數(shù)調用;
注:需要注意的是,如果這里是創(chuàng)建單例對象,那么在實例化后,屬性填充前,需要提前暴露實例化的對象到單例緩存中;
- 填充屬性:在填充屬性前,會給 InstantiationAwareBeanPostProcessor 一個機會來修改 Bean。
- 初始化
- 實現(xiàn) XXXAware 相關接口的方法回調;
- 回調 BeanPostProcessor.postProcessBeforeInitialization;
- 調用初始化方法
- InitializingBean.afterPropertiesSet
- init-method
- 回調 BeanPostProcessor.postProcessAfterInitialization ;
注:如果這里是創(chuàng)建單例對象,在完成初始化后,需要檢測對象修改狀態(tài),這是為了解決循環(huán)依賴的。
- 銷毀注冊
- 檢測是否需要銷毀: 實現(xiàn)了 DisposableBean,
AutoCloseable 接口,或者定義的內部方法:close 或者 shutdown, 或者 自定義 DestructionAwareBeanPostProcessor 接口的實現(xiàn)類來決定是否需要消毀。 - 單例 Bean 的銷毀:通過 DefaultSingletonBeanRegistry 中注冊 DisposableBeanAdapter,并在合適的時機調用銷毀方法即可;
- prototype 域 Bean 的銷毀:不支持,因為 Spring 不管理 prototype 域 Bean 的生命周期,自然也不會去銷毀它。
- request 域 Bean 的銷毀: 通過 ServletRequestListener 監(jiān)聽器實現(xiàn);
- session 域 Bean 的銷毀:通過 HttpSessionBindingListener 監(jiān)聽器實現(xiàn);
- application 域 Bean 的銷毀:通過 ServletContextListener 監(jiān)聽器實現(xiàn);
- 檢測是否需要銷毀: 實現(xiàn)了 DisposableBean,
關于如何通過監(jiān)聽器實現(xiàn),可以通過 Scope 接口的實現(xiàn)類來了解這個過程。
整個 Bean 的創(chuàng)建流程便完了,在這里也只是簡述,因為我一開始的目標是不想過于復雜。但現(xiàn)在,先讓我們跳出 Bean 的創(chuàng)建流程往下走,因為我們知道還有一些“善后措施”要做。
7. 獲取對象
為什么在創(chuàng)建完 Bean 后還要獲取對象呢,因為有 FactoryBean 的緣故,這也是我們在前文轉換名稱一節(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; } } }
上述代碼實現(xiàn)了 SmartFactoryBean 接口,它是一個 FactoryBean。 我們在通過名稱獲取對象時:
final Object sessionFactoryBean = annotationConfigApplicationContext.getBean("sessionFactoryBean");
實際上獲取到的會是 Session 對象 ,但在容器中管理的 Bean,其實是 SessionFactoryBean 。所以,容器內部在獲取到 Bean 后(無論是新創(chuàng)建的還是緩存的),需要在獲取對象這一步來計算出你真正想要的對象。當然,如果你要獲取的是真正的工廠對象,通過 “&sessionFactoryBean” 名稱來獲取就好了。
8. 寫在最后
現(xiàn)在,看看我們都經(jīng)歷了什么。
首先是找到 Bean 真實的名稱,然后再嘗試從緩存,從父容器中去獲取。如果都沒有取到的話,就開始合并 BeanDefinition,檢查顯式聲明的依賴。如果都沒有問題的話,就可以開始 Bean 的創(chuàng)建了。無論是什么域的 Bean,其實創(chuàng)建代碼都一樣,只是前置后置條件不同。而 Bean 的創(chuàng)建又有四個小的過程,也可稱之為生命周期:實例化,計算屬性,初始化,bean 銷毀時的邏輯注冊。最后,Bean 無論是 從緩存中獲取成功還是創(chuàng)建成功,都需要通過計算獲取對象,因為有可能我們獲取到的 Bean 是一個 FactoryBean,但我們實際上需要的卻是它所管理的對象。
完整的流程就在上面了。需要提醒的是在啟動時,只會完成單例 Bean (非延遲初始化)的創(chuàng)建。但如果單例 Bean 有屬性需要其它的 Bean,那么又會啟動這些相關 Bean 的創(chuàng)建過程。在顯式聲明依賴檢查時,也是如此。如果當前 Bean 有聲明依賴其它的 Bean,那么又會啟動其它 Bean 的創(chuàng)建過程。
Bean 就是在這樣一個循環(huán)往復的過程中被創(chuàng)建,并在 Spring 的管理下生生不息的。
到此這篇關于Spring中Bean的創(chuàng)建流程詳細解讀的文章就介紹到這了,更多相關Spring中Bean的創(chuàng)建流程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 淺析Spring容器原始Bean是如何創(chuàng)建的
- Spring?Bean創(chuàng)建的另一條捷徑
- SpringBoot中創(chuàng)建bean的7種方式總結
- Spring中Bean創(chuàng)建完后打印語句的兩種方法
- Spring Bean的定義及三種創(chuàng)建方式
- SpringBoot2基于重復創(chuàng)建bean的問題及解決
- Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式
- Spring創(chuàng)建bean的幾種方式及使用場景
- SpringBoot在容器中創(chuàng)建實例@Component和@bean有什么區(qū)別
相關文章
Java連接MySQL數(shù)據(jù)庫增刪改查的通用方法(推薦)
下面小編就為大家?guī)硪黄狫ava連接MySQL數(shù)據(jù)庫增刪改查的通用方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08