<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    面試官必問:說說 Spring Bean 的實(shí)例化過程?

    共 6673字,需瀏覽 14分鐘

     ·

    2022-03-03 00:16

    點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)??

    來源:juejin.cn/post/6929672218322731022

    • 不貼代碼,Spring的Bean實(shí)例化過程應(yīng)該是怎樣的?
    • 兩個(gè)階段
      • 容器啟動(dòng)階段
      • Bean實(shí)例化階段
    圖片

    不貼代碼,Spring的Bean實(shí)例化過程應(yīng)該是怎樣的?

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

    這里我們并不會(huì)詳細(xì)的分析源代碼,只是給出Spring在完成哪些工作的時(shí)候使用到了什么類,這些類具體的職責(zé)都是什么,如果我們要弄清楚Spring Bean實(shí)例化的內(nèi)幕與詳細(xì)信息,那么可以看哪些源代碼? 至于具體的詳細(xì)的代碼信息,大家可以查看Spring相關(guān)類的代碼。

    兩個(gè)階段

    這里首先聲明一下,Spring將管理的一個(gè)個(gè)的依賴對(duì)象稱之為Bean,這從xml配置文件中也可以看出。

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

    1. 容器啟動(dòng)階段
    2. Bean實(shí)例化階段

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

    容器啟動(dòng)階段

    1、配置元信息

    我們說Spring IOC容器將對(duì)象實(shí)例的創(chuàng)建與對(duì)象實(shí)例的使用分離,我們的業(yè)務(wù)中需要依賴哪個(gè)對(duì)象不再依靠我們自己手動(dòng)創(chuàng)建,只要向Spring要,Spring就會(huì)以注入的方式交給我們需要的依賴對(duì)象。但是,你不干,我不干,總要有人干,既然我們將對(duì)象創(chuàng)建的任務(wù)交給了Spring,那么Spring就需要知道創(chuàng)建一個(gè)對(duì)象所需要的一些必要的信息。而這些必要的信息可以是Spring過去支持最完善的xml配置文件,或者是其他形式的例如properties的磁盤文件,也可以是現(xiàn)在主流的注解,甚至是直接的代碼硬編碼??傊?,這些創(chuàng)建對(duì)象所需要的必要信息稱為配置元信息。

    "role"?class="com.wbg.springxmlbean.entity.Role">
    ????
    ????"id"?value="1"/>
    ????"roleName"?value="高級(jí)工程師"/>
    ????"note"?value="重要人員"/>

    2、BeanDefination

    我們大家都知道,在Java世界中,萬物皆對(duì)象,散落于程序代碼各處的注解以及保存在磁盤上的xml或者其他文件等等配置元信息,在內(nèi)存中總要以一種對(duì)象的形式表示,就好比我們活生生的人對(duì)應(yīng)到Java世界中就是一個(gè)Person類,而Spring選擇在內(nèi)存中表示這些配置元信息的方式就是BeanDefination,這里我們不會(huì)去分析BeanDefination的代碼,感興趣的可以去看相關(guān)源碼,*這里我們只是需要知道配置元信息被加載到內(nèi)存之后是以BeanDefination的形存在的即可。 *

    3、BeanDefinationReader

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

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

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

    4、BeanDefinationRegistry

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

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

    5、BeanFactoryPostProcessor

    BeanFactoryPostProcessor是容器啟動(dòng)階段Spring提供的一個(gè)擴(kuò)展點(diǎn),主要負(fù)責(zé)對(duì)注冊(cè)到BeanDefinationRegistry中的一個(gè)個(gè)的BeanDefination進(jìn)行一定程度上的修改與替換。例如我們的配置元信息中有些可能會(huì)修改的配置信息散落到各處,不夠靈活,修改相應(yīng)配置的時(shí)候比較麻煩,這時(shí)我們可以使用占位符的方式來配置。例如配置Jdbc的DataSource連接的時(shí)候可以這樣配置:

    "dataSource"
    ????class="org.apache.commons.dbcp.BasicDataSource"
    ????destroy-method="close">
    ????"maxIdle"?value="${jdbc.maxIdle}">
    ????"maxActive"?value="${jdbc.maxActive}">
    ????"maxWait"?value="${jdbc.maxWait}">
    ????"minIdle"?value="${jdbc.minIdle}">

    ????"driverClassName"
    ????????value="${jdbc.driverClassName}">
    ????
    ????"url"?value="${jdbc.url}">

    ????"username"?value="${jdbc.username}">
    ????"password"?value="${jdbc.password}">

    BeanFactoryPostProcessor就會(huì)對(duì)注冊(cè)到BeanDefinationRegistry中的BeanDefination做最后的修改,替換$占位符為配置文件中的真實(shí)的數(shù)據(jù)。

    至此,整個(gè)容器啟動(dòng)階段就算完成了,容器的啟動(dòng)階段的最終產(chǎn)物就是注冊(cè)到BeanDefinationRegistry中的一個(gè)個(gè)BeanDefination了,這就是Spring為Bean實(shí)例化所做的預(yù)熱的工作。讓我們?cè)偻ㄟ^一張圖的形式回顧一下容器啟動(dòng)階段都是搞了什么事吧。

    圖片

    Bean實(shí)例化階段

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

    接下來我們就聊一聊Bean實(shí)例化過程的那些事兒~

    1、對(duì)象創(chuàng)建策略

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

    關(guān)于策略模式有不了解的可以查閱相關(guān)書籍,或者網(wǎng)上相關(guān)資料,這是設(shè)計(jì)模式相關(guān)的內(nèi)容,本文主要關(guān)注Bean實(shí)例化的整體流程,設(shè)計(jì)模式相關(guān)知識(shí)不在討論。

    2、BeanWrapper——對(duì)象的外衣

    Spring中的Bean并不是以一個(gè)個(gè)的本來模樣存在的,由于Spring IOC容器中要管理多種類型的對(duì)象,因此為了統(tǒng)一對(duì)不同類型對(duì)象的訪問,*Spring給所有創(chuàng)建的Bean實(shí)例穿上了一層外套 *,這個(gè)外套就是BeanWrapper(關(guān)于BeanWrapper的具體內(nèi)容感興趣的請(qǐng)查閱相關(guān)源碼)。BeanWrapper實(shí)際上是對(duì)反射相關(guān)API的簡(jiǎn)單封裝,使得上層使用反射完成相關(guān)的業(yè)務(wù)邏輯大大的簡(jiǎn)化,我們要獲取某個(gè)對(duì)象的屬性,調(diào)用某個(gè)對(duì)象的方法,現(xiàn)在不需要在寫繁雜的反射API了以及處理一堆麻煩的異常,直接通過BeanWrapper就可以完成相關(guān)操作,簡(jiǎn)直不要太爽了。

    3、設(shè)置對(duì)象屬性

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

    • 對(duì)于基本類型的屬性,如果配置元信息中有配置,那么將直接使用配置元信息中的設(shè)置值賦值即可,即使基本類型的屬性沒有設(shè)置值,那么得益于JVM對(duì)象實(shí)例化過程,屬性依然可以被賦予默認(rèn)的初始化零值。
    • 對(duì)于引用類型的屬性,Spring會(huì)將所有已經(jīng)創(chuàng)建好的對(duì)象放入一個(gè)Map結(jié)構(gòu)中,此時(shí)Spring會(huì)檢查所依賴的對(duì)象是否已經(jīng)被納入容器的管理范圍之內(nèi),也就是Map中是否已經(jīng)有對(duì)應(yīng)對(duì)象的實(shí)例了。如果有,那么直接注入,如果沒有,那么Spring會(huì)暫時(shí)放下該對(duì)象的實(shí)例化過程,轉(zhuǎn)而先去實(shí)例化依賴對(duì)象,再回過頭來完成該對(duì)象的實(shí)例化過程。

    這里有一個(gè)Spring中的經(jīng)典問題,那就是Spring是如何解決循環(huán)依賴的?

    這里簡(jiǎn)單提一下,Spring是通過三級(jí)緩存解決循環(huán)依賴,并且只能解決Setter注入的循環(huán)依賴,請(qǐng)大家思考一下如何解決?為何只能是Setter注入?詳細(xì)內(nèi)容可以查閱相關(guān)博客,文檔,書籍。

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

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

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

    而對(duì)于ApplicationContext,由于其本身繼承了一系列的相關(guān)接口,所以當(dāng)檢測(cè)到Aware相關(guān)接口,需要相關(guān)依賴對(duì)象的時(shí)候,ApplicationContext完全可以將自身注入到其中,ApplicationContext實(shí)現(xiàn)這一步是通過下面要講到的東東——BeanPostProcessor。

    圖片

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

    5、BeanPostProcessor前置處理

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

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

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

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

    6、自定義初始化邏輯

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

    一般通過配置init-method方法比較靈活。

    7、BeanPostProcess后置處理

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

    8、自定義銷毀邏輯

    這一步對(duì)應(yīng)自定義初始化邏輯,同樣有兩種方式:(1)實(shí)現(xiàn)DisposableBean接口 (2)配置destory-method參數(shù)。

    這里一個(gè)比較典型的應(yīng)用就是配置dataSource的時(shí)候destory-method為數(shù)據(jù)庫連接的close()方法。

    9、使用

    經(jīng)過了以上道道工序,我們終于可以享受Spring為我們帶來的便捷了,這個(gè)時(shí)候我們像對(duì)待平常的對(duì)象一樣對(duì)待Spring為我們產(chǎn)生的Bean實(shí)例,如果你覺得還不錯(cuò)的話,動(dòng)手試一下吧!

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

    Spring的Bean在為我們服務(wù)完之后,馬上就要消亡了(通常是在容器關(guān)閉的時(shí)候),別忘了我們的自定義銷毀邏輯,這時(shí)候Spring將以回調(diào)的方式調(diào)用我們自定義的銷毀邏輯,然后Bean就這樣走完了光榮的一生!

    我們?cè)偻ㄟ^一張圖來一起看一看Bean實(shí)例化階段的執(zhí)行順序是如何的?

    圖片

    需要指出,容器啟動(dòng)階段與Bean實(shí)例化階段之間的橋梁就是我們可以選擇自定義配置的延遲加載策略,如果我們配置了Bean的延遲加載策略,那么只有我們?cè)谡鎸?shí)的使用依賴對(duì)象的時(shí)候,Spring才會(huì)開始Bean的實(shí)例化階段。而如果我們沒有開啟Bean的延遲加載,那么在容器啟動(dòng)階段之后,就會(huì)緊接著進(jìn)入Bean實(shí)例化階段,通過隱式的調(diào)用getBean方法,來實(shí)例化相關(guān)Bean。

    1.?一口氣說出 Redis 16 個(gè)常見使用場(chǎng)景 !

    2.?無需 XML Mapper,超級(jí) Mybatis 代碼即是 SQL 操作!真香?

    3.?聊聊 InnoDB 數(shù)據(jù)頁變成索引這件事

    4.?同事說,我寫Java代碼像寫詩

    最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

    獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

    PS:因公眾號(hào)平臺(tái)更改了推送規(guī)則,如果不想錯(cuò)過內(nèi)容,記得讀完點(diǎn)一下在看,加個(gè)星標(biāo),這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。

    點(diǎn)“在看”支持小哈呀,謝謝啦??

    瀏覽 34
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)
    評(píng)論
    圖片
    表情
    推薦
    點(diǎn)贊
    評(píng)論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    天天操B| 中国第一美女毛片 | 亚洲涩情91日韩一区二区 | 亚洲综合色色 | 可以免费看黄片的网站 |