Spring Bean的實例化過程

一攀芯、前言

對于寫Java的程序員來說,Spring已經(jīng)成為了目前最流行的第三方開源框架之一,在我們充分享受Spring IOC容器帶來的紅利的同時艇潭,我們也應(yīng)該考慮一下Spring這個大工廠是如何將一個個的Bean生產(chǎn)出來的,本期我們就一起來討論一下Spring中Bean的實例化過程戏蔑。

1.1 Spring Bean 生命周期步驟偽代碼

1蹋凝、類
2、推斷選擇構(gòu)造方法(默認(rèn)調(diào)用類無參構(gòu)造方法)
3辛臊、普通對象(new出來)
4仙粱、為對象的屬性(加了注解的,如@Autowired)進(jìn)行依賴注入
5彻舰、初始化前:判斷方法上是否加了@PostConstruct注解
6伐割、初始化:判斷是否實現(xiàn)了InitializingBean接口,反射調(diào)用afterPropertiesSet方法
7刃唤、初始化后:進(jìn)行AOP
8隔心、代理對象
9、放入單例池Map
10尚胞、成為Bean

1.1.1 Spring調(diào)用類的構(gòu)造方法

Spring調(diào)用類的構(gòu)造方法有以下情況:

  • Spring默認(rèn)會調(diào)用類的無參構(gòu)造方法創(chuàng)建類硬霍。

  • 加了僅有的一個有參構(gòu)造方法(無參構(gòu)造方法就沒有了),Spring會調(diào)用該有參構(gòu)造方法創(chuàng)建類笼裳。

  • 加了兩個以上的有參構(gòu)造方法唯卖,Spring報錯粱玲,因為Spring也不清楚要調(diào)用哪個構(gòu)造方法來創(chuàng)建對象。

  • 加了兩個以上的有參構(gòu)造方法拜轨,并在一個指定的構(gòu)造方法上加了@Autowired注解抽减,Spring則調(diào)用該構(gòu)造方法來創(chuàng)建對象。

二橄碾、兩個階段

這里首先聲明一下卵沉,Spring將管理的一個個的依賴對象稱之為Bean,這從xml配置文件中也可以看出法牲。

Spring IOC容器就好像一個生產(chǎn)產(chǎn)品的流水線上的機(jī)器史汗,Spring創(chuàng)建出來的Bean就好像是流水線的終點生產(chǎn)出來的一個個精美絕倫的產(chǎn)品。既然是機(jī)器拒垃,總要先啟動停撞,Spring也不例外。因此Bean的一生從總體上來說可以分為兩個階段:

  • 容器啟動階段
  • Bean實例化階段
1

容器的啟動階段做了很多的預(yù)熱工作恶复,為后面Bean的實例化做好了充分的準(zhǔn)備怜森,我們首先看一下容器的啟動階段都做了哪些預(yù)熱工作。

2.1 容器啟動階段
2.1.1 配置元信息

我們說Spring IOC容器將對象實例的創(chuàng)建與對象實例的使用分離谤牡,我們的業(yè)務(wù)中需要依賴哪個對象不再依靠我們自己手動創(chuàng)建副硅,只要向Spring要,Spring就會以注入的方式交給我們需要的依賴對象翅萤。但是恐疲,你不干,我不干套么,總要有人干培己,既然我們將對象創(chuàng)建的任務(wù)交給了Spring,那么Spring就需要知道創(chuàng)建一個對象所需要的一些必要的信息胚泌。而這些必要的信息可以是Spring過去支持最完善的xml配置文件省咨,或者是其他形式的例如properties的磁盤文件,也可以是現(xiàn)在主流的注解玷室,甚至是直接的代碼硬編碼零蓉。總之穷缤,這些創(chuàng)建對象所需要的必要信息稱為配置元信息敌蜂。

<bean id="role" class="com.wbg.springxmlbean.entity.Role">
    <!-- property元素是定義類的屬性,name屬性定義的是屬性名稱 value是值
    相當(dāng)于:
    Role role=new Role();
    role.setId(1);
    role.setRoleName("高級工程師");
    role.setNote("重要人員");-->
    <property name="id" value="1"/>
    <property name="roleName" value="高級工程師"/>
    <property name="note" value="重要人員"/>
</bean>
2.1.2 BeanDefination

我們大家都知道津肛,在Java世界中章喉,萬物皆對象,散落于程序代碼各處的注解以及保存在磁盤上的xml或者其他文件等等配置元信息,在內(nèi)存中總要以一種對象的形式表示秸脱,就好比我們活生生的人對應(yīng)到Java世界中就是一個Person類落包,而Spring選擇在內(nèi)存中表示這些配置元信息的方式就是BeanDefination,這里我們只是需要知道配置元信息被加載到內(nèi)存之后是以BeanDefination的形存在的即可撞反。

2.1.3 BeanDefinationReader

大家肯定很好奇妥色,我們是看得懂Spring中xml配置文件中一個個的Bean定義,但是Spring是如何看懂這些配置元信息的呢遏片?這個就要靠我們的BeanDefinationReader了。

不同的BeanDefinationReader就像葫蘆兄弟一樣撮竿,各自擁有各自的本領(lǐng)吮便。如果我們要讀取xml配置元信息,那么可以使用XmlBeanDefinationReader幢踏。如果我們要讀取properties配置文件髓需,那么可以使用PropertiesBeanDefinitionReader加載。而如果我們要讀取注解配置元信息房蝉,那么可以使用 AnnotatedBeanDefinitionReader加載僚匆。我們也可以很方便的自定義BeanDefinationReader來自己控制配置元信息的加載。例如我們的配置元信息存在于三界之外搭幻,那么我們可以自定義From天界之外BeanDefinationReader咧擂。

總的來說,BeanDefinationReader的作用就是加載配置元信息檀蹋,并將其轉(zhuǎn)化為內(nèi)存形式的BeanDefination松申,存在某一個地方,至于這個地方在哪里俯逾,不要著急贸桶,接著往下看。

2.1.4 BeanDefinationRegistry

執(zhí)行到這里桌肴,總算不遺余力的將存在于各處的配置元信息加載到內(nèi)存皇筛,并轉(zhuǎn)化為BeanDefination的形式,這樣我們需要創(chuàng)建某一個對象實例的時候坠七,找到相應(yīng)的BeanDefination然后創(chuàng)建對象即可水醋。那么我們需要某一個對象的時候,去哪里找到對應(yīng)的BeanDefination呢灼捂?這種通過Bean定義的id找到對象的BeanDefination的對應(yīng)關(guān)系或者說映射關(guān)系又是如何保存的呢离例?這就引出了BeanDefinationRegistry了。

Spring通過BeanDefinationReader將配置元信息加載到內(nèi)存生成相應(yīng)的BeanDefination之后悉稠,就將其注冊到BeanDefinationRegistry中宫蛆,BeanDefinationRegistry就是一個存放BeanDefination的大籃子,它也是一種鍵值對的形式,通過特定的Bean定義的id耀盗,映射到相應(yīng)的BeanDefination想虎。

2.1.5 BeanFactoryPostProcessor

BeanFactoryPostProcessor是容器啟動階段Spring提供的一個擴(kuò)展點,主要負(fù)責(zé)對注冊到BeanDefinationRegistry中的一個個的BeanDefination進(jìn)行一定程度上的修改與替換叛拷。例如我們的配置元信息中有些可能會修改的配置信息散落到各處舌厨,不夠靈活,修改相應(yīng)配置的時候比較麻煩忿薇,這時我們可以使用占位符的方式來配置裙椭。例如配置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>

BeanFactoryPostProcessor就會對注冊到BeanDefinationRegistry中的BeanDefination做最后的修改,替換$占位符為配置文件中的真實的數(shù)據(jù)署浩。至此揉燃,整個容器啟動階段就算完成了,容器的啟動階段的最終產(chǎn)物就是注冊到BeanDefinationRegistry中的一個個BeanDefination了筋栋,這就是Spring為Bean實例化所做的預(yù)熱的工作炊汤。

2.2 Bean實例化階段

需要指出,容器啟動階段與Bean實例化階段存在多少時間差弊攘,Spring把這個決定權(quán)交給了我們程序員抢腐。如果我們選擇懶加載的方式,那么直到我們伸手向Spring要依賴對象實例之前襟交,其都是以BeanDefinationRegistry中的一個個的BeanDefination的形式存在迈倍,也就是Spring只有在我們需要依賴對象的時候才開啟相應(yīng)對象的實例化階段。而如果我們不是選擇懶加載的方式婿着,容器啟動階段完成之后授瘦,將立即啟動Bean實例化階段,通過隱式的調(diào)用所有依賴對象的getBean方法來實例化所有配置的Bean并保存起來竟宋。

2.2.1 對象創(chuàng)建策略

到了這個時候提完,Spring就開始真刀真槍的干了,對象的創(chuàng)建采用了策略模式丘侠,借助我們前面BeanDefinationRegistry中的BeanDefination徒欣,我們可以使用反射的方式創(chuàng)建對象,也可以使用CGlib字節(jié)碼生成創(chuàng)建對象蜗字。同時我們可以靈活的配置來告訴Spring采用什么樣的策略創(chuàng)建指定的依賴對象打肝。Spring中Bean的創(chuàng)建是策略設(shè)計模式的經(jīng)典應(yīng)用。這個時候挪捕,內(nèi)存中應(yīng)該已經(jīng)有一個我們想要的具體的依賴對象的實例了粗梭,但是故事的發(fā)展還沒有我們想象中的那么簡單。

2.2.2 BeanWrapper對象的外衣

Spring中的Bean并不是以一個個的本來模樣存在的级零,由于Spring IOC容器中要管理多種類型的對象断医,因此為了統(tǒng)一對不同類型對象的訪問,Spring給所有創(chuàng)建的Bean實例穿上了一層外套,這個外套就是BeanWrapper鉴嗤。BeanWrapper實際上是對反射相關(guān)API的簡單封裝斩启,使得上層使用反射完成相關(guān)的業(yè)務(wù)邏輯大大的簡化,我們要獲取某個對象的屬性醉锅,調(diào)用某個對象的方法兔簇,現(xiàn)在不需要在寫繁雜的反射API了以及處理一堆麻煩的異常,直接通過BeanWrapper就可以完成相關(guān)操作硬耍,簡直不要太爽了垄琐。

2.2.3 設(shè)置對象屬性

上一步包裹在BeanWrapper中的對象還是一個少不經(jīng)事的孩子,需要為其設(shè)置屬性以及依賴對象默垄。

  • 對于基本類型的屬性此虑,如果配置元信息中有配置,那么將直接使用配置元信息中的設(shè)置值賦值即可口锭,即使基本類型的屬性沒有設(shè)置值,那么得益于JVM對象實例化過程介杆,屬性依然可以被賦予默認(rèn)的初始化零值鹃操。

  • 對于引用類型的屬性,Spring會將所有已經(jīng)創(chuàng)建好的對象放入一個Map結(jié)構(gòu)中春哨,此時Spring會檢查所依賴的對象是否已經(jīng)被納入容器的管理范圍之內(nèi)荆隘,也就是Map中是否已經(jīng)有對應(yīng)對象的實例了。如果有赴背,那么直接注入椰拒,如果沒有,那么Spring會暫時放下該對象的實例化過程凰荚,轉(zhuǎn)而先去實例化依賴對象燃观,再回過頭來完成該對象的實例化過程。

這里有一個Spring中的經(jīng)典問題便瑟,那就是Spring是如何解決循環(huán)依賴的缆毁?這里簡單提一下,Spring是通過三級緩存解決循環(huán)依賴到涂,并且只能解決Setter注入的循環(huán)依賴脊框。

2.2.4 檢查Aware相關(guān)接口

我們知道,我們?nèi)绻胍蕾嘢pring中的相關(guān)對象践啄,使用Spring的相關(guān)API浇雹,那么可以實現(xiàn)相應(yīng)的Aware接口,Spring IOC容器就會為我們自動注入相關(guān)依賴對象實例屿讽。Spring IOC容器大體可以分為兩種昭灵,BeanFactory提供IOC思想所設(shè)想所有的功能,同時也融入AOP等相關(guān)功能模塊,可以說BeanFactory是Spring提供的一個基本的IOC容器虎锚。ApplicationContext構(gòu)建于BeanFactory之上硫痰,同時提供了諸如容器內(nèi)的時間發(fā)布、統(tǒng)一的資源加載策略窜护、國際化的支持等功能效斑,是Spring提供的更為高級的IOC容器。

講了這么多柱徙,其實就是想表達(dá)對于BeanFactory來說缓屠,這一步的實現(xiàn)是先檢查相關(guān)的Aware接口,然后去Spring的對象池(也就是容器护侮,也就是那個Map結(jié)構(gòu))中去查找相關(guān)的實例(例如對于ApplicationContextAware接口敌完,就去找ApplicationContext實例),也就是說我們必須要在配置文件中或者使用注解的方式羊初,將相關(guān)實例注冊容器中滨溉,BeanFactory才可以為我們自動注入。

而對于ApplicationContext长赞,由于其本身繼承了一系列的相關(guān)接口晦攒,所以當(dāng)檢測到Aware相關(guān)接口,需要相關(guān)依賴對象的時候得哆,ApplicationContext完全可以將自身注入到其中脯颜,ApplicationContext實現(xiàn)這一步是通過下面要講到的BeanPostProcessor。

例如ApplicationContext繼承自ResourceLoader和MessageSource贩据,那么當(dāng)我們實現(xiàn)ResourceLoaderAware和MessageSourceAware相關(guān)接口時栋操,就將其自身注入到業(yè)務(wù)對象中即可。

2.2.5 BeanPostProcessor前置處理

剛才那個是什么Processor饱亮?相信剛看這兩個東西的人肯定有點暈乎了矾芙,我當(dāng)初也是,不過其實也好區(qū)分近尚,只要記住BeanFactoryPostProcessor存在于容器啟動階段而BeanPostProcessor存在于對象實例化階段蠕啄,BeanFactoryPostProcessor關(guān)注對象被創(chuàng)建之前那些配置的修修改改,縫縫補(bǔ)補(bǔ)戈锻,而BeanPostProcessor階段關(guān)注對象已經(jīng)被創(chuàng)建之后的功能增強(qiáng)歼跟,替換等操作,這樣就很容易區(qū)分了格遭。

BeanPostProcessor與BeanFactoryPostProcessor都是Spring在Bean生產(chǎn)過程中強(qiáng)有力的擴(kuò)展點哈街。如果你還對它感到很陌生,那么你肯定知道Spring中著名的AOP(面向切面編程)拒迅,其實就是依賴BeanPostProcessor對Bean對象功能增強(qiáng)的骚秦。

BeanPostProcessor前置處理就是在要生產(chǎn)的Bean實例放到容器之前她倘,允許我們程序員對Bean實例進(jìn)行一定程度的修改,替換等操作作箍。

前面講到的ApplicationContext對于Aware接口的檢查與自動注入就是通過BeanPostProcessor實現(xiàn)的硬梁,在這一步Spring將檢查Bean中是否實現(xiàn)了相關(guān)的Aware接口,如果是的話胞得,那么就將其自身注入Bean中即可荧止。Spring中AOP就是在這一步實現(xiàn)的偷梁換柱,產(chǎn)生對于原生對象的代理對象阶剑,然后將對源對象上的方法調(diào)用跃巡,轉(zhuǎn)而使用代理對象的相同方法調(diào)用實現(xiàn)的。

2.2.6 自定義初始化邏輯

在所有的準(zhǔn)備工作完成之后牧愁,如果我們的Bean還有一定的初始化邏輯素邪,那么Spring將允許我們通過兩種方式配置我們的初始化邏輯:(1)InitializingBean (2)配置init-method參數(shù),一般通過配置init-method方法比較靈活猪半。

2.2.7 BeanPostProcess后置處理

與前置處理類似兔朦,這里是在Bean自定義邏輯也執(zhí)行完成之后,Spring又留給我們的最后一個擴(kuò)展點磨确。我們可以在這里在做一些我們想要的擴(kuò)展烘绽。

2.2.8 自定義銷毀邏輯

這一步對應(yīng)自定義初始化邏輯,同樣有兩種方式:(1)實現(xiàn)DisposableBean接口 (2)配置destory-method參數(shù)俐填。這里一個比較典型的應(yīng)用就是配置dataSource的時候destory-method為數(shù)據(jù)庫連接的close()方法。

2.2.9 調(diào)用回調(diào)銷毀接口

Spring的Bean在為我們服務(wù)完之后翔忽,馬上就要消亡了(通常是在容器關(guān)閉的時候)英融,別忘了我們的自定義銷毀邏輯,這時候Spring將以回調(diào)的方式調(diào)用我們自定義的銷毀邏輯歇式,然后Bean就這樣走完了光榮的一生驶悟。我們再通過一張圖來一起看一看Bean實例化階段的執(zhí)行順序是如何的?

2

需要指出材失,容器啟動階段與Bean實例化階段之間的橋梁就是我們可以選擇自定義配置的延遲加載策略痕鳍,如果我們配置了Bean的延遲加載策略,那么只有我們在真實的使用依賴對象的時候龙巨,Spring才會開始Bean的實例化階段笼呆。而如果我們沒有開啟Bean的延遲加載,那么在容器啟動階段之后旨别,就會緊接著進(jìn)入Bean實例化階段诗赌,通過隱式的調(diào)用getBean方法,來實例化相關(guān)Bean秸弛。

轉(zhuǎn)載自:面試官必問:說說 Spring Bean 的實例化過程铭若?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洪碳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叼屠,更是在濱河造成了極大的恐慌瞳腌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镜雨,死亡現(xiàn)場離奇詭異嫂侍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)冷离,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門吵冒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人西剥,你說我怎么就攤上這事痹栖。” “怎么了瞭空?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵揪阿,是天一觀的道長。 經(jīng)常有香客問我咆畏,道長南捂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任旧找,我火速辦了婚禮溺健,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钮蛛。我一直安慰自己鞭缭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布魏颓。 她就那樣靜靜地躺著岭辣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪甸饱。 梳的紋絲不亂的頭發(fā)上沦童,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音叹话,去河邊找鬼偷遗。 笑死,一個胖子當(dāng)著我的面吹牛渣刷,可吹牛的內(nèi)容都是我干的鹦肿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼辅柴,長吁一口氣:“原來是場噩夢啊……” “哼箩溃!你這毒婦竟也來了瞭吃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤涣旨,失蹤者是張志新(化名)和其女友劉穎歪架,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霹陡,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡和蚪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烹棉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片攒霹。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浆洗,靈堂內(nèi)的尸體忽然破棺而出催束,到底是詐尸還是另有隱情,我是刑警寧澤伏社,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布抠刺,位于F島的核電站,受9級特大地震影響摘昌,放射性物質(zhì)發(fā)生泄漏速妖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一聪黎、第九天 我趴在偏房一處隱蔽的房頂上張望罕容。 院中可真熱鬧,春花似錦稿饰、人聲如沸杀赢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滤淳,卻和暖如春梧喷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脖咐。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工铺敌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屁擅。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓偿凭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親派歌。 傳聞我的和親對象是個殘疾皇子弯囊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容