當沒有代碼的時候,Spring之Bean實例化過程是什麼樣子的呢? 兩個階段 容器啓動階段 Bean實例化階段 Bean實例化階段

對於寫Java的程序員來說,Spring已經成爲了目前最流行的第三方開源框架之一,在我們充分享受Spring IOC容器帶來的紅利的同時,我們也應該考慮一下Spring這個大工廠是如何將一個個的Bean生產出來的,本期我們就一起來討論一下SpringBean的實例化過程。

這裏我們並不會詳細的分析源代碼,只是給出Spring在完成哪些工作的時候使用到了什麼類,這些類具體的職責都是什麼,如果我們要弄清楚Spring Bean實例化的內幕與詳細信息,那麼可以看哪些源代碼? 至於具體的詳細的代碼信息,大家可以查看Spring相關類的代碼。

兩個階段

這裏首先聲明一下,Spring將管理的一個個的依賴對象稱之爲Bean,這從xml配置文件中也可以看出。
Spring IOC容器就好像一個生產產品的流水線上的機器,Spring創建出來的Bean就好像是流水線的終點生產出來的一個個精美絕倫的產品。既然是機器,總要先啓動,Spring也不例外。因此Bean的一生從總體上來說可以分爲兩個階段:

容器啓動階段

Bean實例化階段

容器的啓動階段做了很多的預熱工作,爲後面Bean的實例化做好了充分的準備,我們首先看一下容器的啓動階段都做了哪些預熱工作。

容器啓動階段

1、配置元信息

我們說Spring IOC容器將對象實例的創建與對象實例的使用分離,我們的業務中需要依賴哪個對象不再依靠我們自己手動創建,只要向Spring要,Spring就會以注入的方式交給我們需要的依賴對象。但是,你不幹,我不幹,總要有人幹,既然我們將對象創建的任務交給了Spring,那麼Spring就需要知道創建一個對象所需要的一些必要的信息。而這些必要的信息可以是Spring過去支持最完善的xml配置文件,或者是其他形式的例如properties的磁盤文件,也可以是現在主流的註解,甚至是直接的代碼硬編碼。總之,這些創建對象所需要的必要信息稱爲配置元信息。

<bean id="role" class="com.wbg.springxmlbean.entity.Role">
    <!-- property元素是定義類的屬性,name屬性定義的是屬性名稱 value是值
    相當於:
    Role role=new Role();
    role.setId(1);//加入Java開發交流君樣:756584822一起吹水聊天
    role.setRoleName("高級工程師");
    role.setNote("重要人員");-->
    <property name="id" value="1"/>
    <property name="roleName" value="高級工程師"/>
    <property name="note" value="重要人員"/>
</bean>

2、BeanDefination

我們大家都知道,在Java世界中,萬物皆對象,散落於程序代碼各處的註解以及保存在磁盤上的xml或者其他文件等等配置元信息,在內存中總要以一種對象的形式表示,就好比我們活生生的人對應到Java世界中就是一個Person類,而Spring選擇在內存中表示這些配置元信息的方式就是BeanDefination,這裏我們不會去分析BeanDefination的代碼,感興趣的可以去看相關源碼,這裏我們只是需要知道配置元信息被加載到內存之後是以BeanDefination的形存在的即可。【參考文獻】

3、BeanDefinationReader

大家肯定很好奇,我們是看得懂Spring中xml配置文件中一個個的Bean定義,但是Spring是如何看懂這些配置元信息的呢?這個就要靠我們的BeanDefinationReader了。
不同的BeanDefinationReader就像葫蘆兄弟一樣,各自擁有各自的本領。如果我們要讀取xml配置元信息,那麼可以使用XmlBeanDefinationReader。如果我們要讀取properties配置文件,那麼可以使用PropertiesBeanDefinitionReader加載。而如果我們要讀取註解配置元信息,那麼可以使用AnnotatedBeanDefinitionReader加載。我們也可以很方便的自定義BeanDefinationReader來自己控制配置元信息的加載。例如我們的配置元信息存在於三界之外,那麼我們可以自定義From天界之外BeanDefinationReader
總的來說,BeanDefinationReader的作用就是加載配置元信息,並將其轉化爲內存形式的BeanDefination,存在某一個地方,至於這個地方在哪裏,不要着急,接着往下看!

4、BeanDefinationRegistry

執行到這裏,總算不遺餘力的將存在於各處的配置元信息加載到內存,並轉化爲BeanDefination的形式,這樣我們需要創建某一個對象實例的時候,找到相應的BeanDefination然後創建對象即可。那麼我們需要某一個對象的時候,去哪裏找到對應的BeanDefination呢?這種通過Bean定義的id找到對象的BeanDefination的對應關係或者說映射關係又是如何保存的呢?這就引出了BeanDefinationRegistry了。
Spring通過BeanDefinationReader將配置元信息加載到內存生成相應的BeanDefination之後,就將其註冊到BeanDefinationRegistry中,BeanDefinationRegistry就是一個存放BeanDefination的大籃子,它也是一種鍵值對的形式,通過特定的Bean定義的id,映射到相應的BeanDefination

5、BeanFactoryPostProcessor

BeanFactoryPostProcessor是容器啓動階段Spring提供的一個擴展點,主要負責對註冊到BeanDefinationRegistry中的一個個的BeanDefination進行一定程度上的修改與替換。例如我們的配置元信息中有些可能會修改的配置信息散落到各處,不夠靈活,修改相應配置的時候比較麻煩,這時我們可以使用佔位符的方式來配置。例如配置Jdbc的DataSource連接的時候可以這樣配置:

<bean id="dataSource"  
    class="org.apache.commons.dbcp.BasicDataSource"  
    destroy-method="close">  
    <property name="maxIdle" value="${jdbc.maxIdle}"></property>  
    <property name="maxActive" value="${jdbc.maxActive}"></property>  
    <property name="maxWait" value="${jdbc.maxWait}"></property>  
    <property name="minIdle" value="${jdbc.minIdle}"></property>  
  
    <property name="driverClassName"  
        value="${jdbc.driverClassName}">  
    </property>  
    <property name="url" value="${jdbc.url}"></property>  
  
    <property name="username" value="${jdbc.username}"></property>  
    <property name="password" value="${jdbc.password}"></property>  
</bean> //加入Java開發交流君樣:756584822一起吹水聊天

BeanFactoryPostProcessor就會對註冊到BeanDefinationRegistry中的BeanDefination做最後的修改,替換$佔位符爲配置文件中的真實的數據。
至此,整個容器啓動階段就算完成了,容器的啓動階段的最終產物就是註冊到BeanDefinationRegistry中的一個個BeanDefination了,這就是Spring爲Bean實例化所做的預熱的工作。讓我們再通過一張圖的形式回顧一下容器啓動階段都是搞了什麼事吧。

[圖片上傳失敗...(image-786f42-1622904256520)]

Bean實例化階段

需要指出,容器啓動階段與Bean實例化階段存在多少時間差,Spring把這個決定權交給了我們程序員(是不是瞬間開心了一點點!)。如果我們選擇懶加載的方式,那麼直到我們伸手向Spring要依賴對象實例之前,其都是以BeanDefinationRegistry中的一個個的BeanDefination的形式存在,也就是Spring只有在我們需要依賴對象的時候纔開啓相應對象的實例化階段。而如果我們不是選擇懶加載的方式,容器啓動階段完成之後,將立即啓動Bean實例化階段,通過隱式的調用所有依賴對象的getBean方法來實例化所有配置的Bean並保存起來。
接下來我們就聊一聊Bean實例化過程的那些事兒~

1、對象創建策略

到了這個時候,Spring就開始真刀真槍的幹了,對象的創建採用了策略模式,藉助我們前面BeanDefinationRegistry中的BeanDefination,我們可以使用反射的方式創建對象,也可以使用CGlib字節碼生成創建對象。同時我們可以靈活的配置來告訴Spring採用什麼樣的策略創建指定的依賴對象。Spring中Bean的創建是策略設計模式的經典應用。這個時候,內存中應該已經有一個我們想要的具體的依賴對象的實例了,但是故事的發展還沒有我們想象中的那麼簡單。

關於策略模式有不瞭解的可以查閱相關書籍,或者網上相關資料,這是設計模式相關的內容,本文主要關注Bean實例化的整體流程,設計模式相關知識不在討論。

2、BeanWrapper——對象的外衣

Spring中的Bean並不是以一個個的本來模樣存在的,由於Spring IOC容器中要管理多種類型的對象,因此爲了統一對不同類型對象的訪問,Spring給所有創建的Bean實例穿上了一層外套,這個外套就是BeanWrapper(關於BeanWrapper的具體內容感興趣的請查閱相關源碼)。BeanWrapper實際上是對反射相關API的簡單封裝,使得上層使用反射完成相關的業務邏輯大大的簡化,我們要獲取某個對象的屬性,調用某個對象的方法,現在不需要在寫繁雜的反射API了以及處理一堆麻煩的異常,直接通過BeanWrapper就可以完成相關操作,簡直不要太爽了。

3、設置對象屬性

上一步包裹在BeanWrapper中的對象還是一個少不經事的孩子,需要爲其設置屬性以及依賴對象。

對於基本類型的屬性,如果配置元信息中有配置,那麼將直接使用配置元信息中的設置值賦值即可,即使基本類型的屬性沒有設置值,那麼得益於JVM對象實例化過程,屬性依然可以被賦予默認的初始化零值。

對於引用類型的屬性,Spring會將所有已經創建好的對象放入一個Map結構中,此時Spring會檢查所依賴的對象是否已經被納入容器的管理範圍之內,也就是Map中是否已經有對應對象的實例了。如果有,那麼直接注入,如果沒有,那麼Spring會暫時放下該對象的實例化過程,轉而先去實例化依賴對象,再回過頭來完成該對象的實例化過程。【參考文獻】

這裏有一個Spring中的經典問題,那就是Spring是如何解決循環依賴的?

這裏簡單提一下,Spring是通過三級緩存解決循環依賴,並且只能解決Setter注入的循環依賴,請大家思考一下如何解決?爲何只能是Setter注入?詳細內容可以查閱相關博客,文檔,書籍。

4、檢查Aware相關接口

我們知道,我們如果想要依賴Spring中的相關對象,使用Spring的相關API,那麼可以實現相應的Aware接口,Spring IOC容器就會爲我們自動注入相關依賴對象實例。Spring IOC容器大體可以分爲兩種,BeanFactory提供IOC思想所設想所有的功能,同時也融入AOP等相關功能模塊,可以說BeanFactory是Spring提供的一個基本的IOC容器。ApplicationContext構建於BeanFactory之上,同時提供了諸如容器內的時間發佈、統一的資源加載策略、國際化的支持等功能,是Spring提供的更爲高級的IOC容器。//加入Java開發交流君樣:756584822一起吹水聊天
講了這麼多,其實就是想表達對於BeanFactory來說,這一步的實現是先檢查相關的Aware接口,然後去Spring的對象池(也就是容器,也就是那個Map結構)中去查找相關的實例(例如對於ApplicationContextAware接口,就去找ApplicationContext實例),也就是說我們必須要在配置文件中或者使用註解的方式,將相關實例註冊容器中,BeanFactory纔可以爲我們自動注入。【參考文獻】

而對於ApplicationContext,由於其本身繼承了一系列的相關接口,所以當檢測到Aware相關接口,需要相關依賴對象的時候,ApplicationContext完全可以將自身注入到其中,ApplicationContext實現這一步是通過下面要講到的東東——BeanPostProcessor`。

例如ApplicationContext繼承自ResourceLoader和MessageSource,那麼當我們實現ResourceLoaderAwareMessageSourceAware相關接口時,就將其自身注入到業務對象中即可。

5、BeanPostProcessor前置處理

唉?剛纔那個是什麼Processor來?相信剛看這兩個東西的人肯定有點暈乎了,我當初也是,不過其實也好區分,只要記住BeanFactoryPostProcessor存在於容器啓動階段而BeanPostProcessor存在於對象實例化階段,BeanFactoryPostProcessor關注對象被創建之前 那些配置的修修改改,縫縫補補,而BeanPostProcessor階段關注對象已經被創建之後 的功能增強,替換等操作,這樣就很容易區分了。

BeanPostProcessor與BeanFactoryPostProcessor都是Spring在Bean生產過程中強有力的擴展點。如果你還對它感到很陌生,那麼你肯定知道Spring中著名的AOP(面向切面編程),其實就是依賴BeanPostProcessor對Bean對象功能增強的。
BeanPostProcessor前置處理就是在要生產的Bean實例放到容器之前,允許我們程序員對Bean實例進行一定程度的修改,替換等操作。【參考文獻】
前面講到的ApplicationContext對於Aware接口的檢查與自動注入就是通過BeanPostProcessor實現的,在這一步Spring將檢查Bean中是否實現了相關的Aware接口,如果是的話,那麼就將其自身注入Bean中即可。Spring中AOP就是在這一步實現的偷樑換柱,產生對於原生對象的代理對象,然後將對源對象上的方法調用,轉而使用代理對象的相同方法調用實現的。

6、自定義初始化邏輯

在所有的準備工作完成之後,如果我們的Bean還有一定的初始化邏輯,那麼Spring將允許我們通過兩種方式配置我們的初始化邏輯:(1)InitializingBean (2)配置init-method參數
一般通過配置init-method方法比較靈活。

7、BeanPostProcess後置處理

與前置處理類似,這裏是在Bean自定義邏輯也執行完成之後,Spring又留給我們的最後一個擴展點。我們可以在這裏在做一些我們想要的擴展。

8、自定義銷燬邏輯

這一步對應自定義初始化邏輯,同樣有兩種方式:(1)實現DisposableBean接口 (2)配置destory-method參數。
這裏一個比較典型的應用就是配置dataSource的時候destory-method爲數據庫連接的close()`方法。

9、使用

經過了以上道道工序,我們終於可以享受Spring爲我們帶來的便捷了,這個時候我們像對待平常的對象一樣對待Spring爲我們產生的Bean實例,如果你覺得還不錯的話,動手試一下吧!

10、調用回調銷燬接口

Spring的Bean在爲我們服務完之後,馬上就要消亡了(通常是在容器關閉的時候),別忘了我們的自定義銷燬邏輯,這時候Spring將以回調的方式調用我們自定義的銷燬邏輯,然後Bean就這樣走完了光榮的一生!
我們再通過一張圖來一起看一看Bean實例化階段的執行順序是如何的?【參考文獻】


需要指出,容器啓動階段與Bean實例化階段之間的橋樑就是我們可以選擇自定義配置的延遲加載策略,如果我們配置了Bean的延遲加載策略,那麼只有我們在真實的使用依賴對象的時候,Spring纔會開始Bean的實例化階段。而如果我們沒有開啓Bean的延遲加載,那麼在容器啓動階段之後,就會緊接着進入Bean實例化階段,通過隱式的調用getBean方法,來實例化相關Bean。【參考文獻】

作者:厚朴
鏈接:https://juejin.cn/post/6929672218322731022
來源:掘金

最新2021整理收集的一些高頻面試題(都整理成文檔),有很多幹貨,包含mysql,netty,spring,線程,spring cloud、jvm、源碼、算法等詳細講解,也有詳細的學習規劃圖,面試題整理等,需要獲取這些內容的朋友請加Q君樣:756584822

[圖片上傳失敗...(image-494bc6-1622904256520)]

[圖片上傳失敗...(image-959410-1622904256520)]

【參考文獻】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章