Spring bean到底是如何建立的?(上)

三友的java日記發表於2022-06-04

前言

眾所周知,spring對於java程式設計師來說是一個及其重要的後端框架,幾乎所有的公司都會使用的框架,而且深受廣大面試官的青睞。所以本文就以常見的一個面試題"spring bean的生命週期"為切入點,從原始碼的角度帶領大家來看一看 spring bean到底是如何建立的 。spring bean的生命週期非常重要 ,因為幾乎所有的跟spring整合的框架,比如說mybatis 、dubbo 等框架基本上都是通過bean的生命週期來實現跟spring的整合。

本文是基於spring原始碼的5.1版本

在討論spring建立bean的原始碼之前,我先大概介紹一下spring的ioc和aop的概念。

ioc(Inversion of Control,縮寫為IoC)就是控制翻轉的意思,簡單來說就是你按照spring提供的配置bean的方式將bean的建立流程交給spring來完成,比如以xml的方式宣告bean,以@Bean的註解宣告bean,以@Componet註解方式宣告bean,當你用這些方式來宣告bean的時候,spring在啟動的時候就知道要為這個類建立一個物件,接下來spring會按照自己的流程來一步一步完成bean的生成過程,也就是本文的主題,spring bean的建立流程。

aop(Aspect Oriented Programming)英文意思就是面向切面程式設計,說白了其實就是一個動態代理的過程,只不過spring將生成動態代理的過程給封裝到框架的內部,開發者其實只需要宣告一下對哪個物件的哪個方法進行代理(pointcut)和在被代理的方法該執行什麼樣的程式碼(advice),這樣就實現了aop。

有時大家可能會很好奇怎麼實現動態代理的過程,怎麼我配置了一下切面,就給我代理了呢?我給大家簡單解釋一下,aop的實現離不開ioc,當在spring bean建立的過程,在某個環節(後面會說到)spring框架會去判斷,你有沒有配置過給建立的物件進行代理,怎麼判斷很簡單,就是根據你配置的切點表示式判斷一下,如果有就給你建立一個代理物件返回給你,這樣你拿到的就是代理物件,接下來你對這個物件方法呼叫就會走你寫的那個advice所對應的程式碼,如果判斷沒有,就會返回給你原來的物件,就這麼簡單。

大家不妨去了解一下靜態代理,這會有助於大家瞭解動態代理,動態代理其實跟靜態代理差不多,只不過靜態代理需要你手動寫物件的代理,屬於硬編碼的方式,有多少個類就得寫多少個類的代理類,很麻煩,而動態代理是動態生成代理類,但是本質都是加一些特殊的功能,這裡就不再過多贅述了。

好了說完了spring ioc和aop的基本概念之後,接下來就來進入spring ioc中的bean的生命週期原始碼分析。

一、Spring Bean 元資訊(BeanDefinition)讀取階段

spring容器剛啟動的時候,spring會按照你的宣告bean的方式(以xml的方式宣告bean,以@Bean的註解宣告bean,以@Componet註解方式宣告bean等(其實也可以通過Properties資源配置))去讀取你宣告的資訊,然後封裝在一個叫BeanDefinition物件裡面,BeanDefinition可以看成你配置資訊的一個封裝物件,就跟你平時new的User是一個概念,後面在建立物件的時候,spring拿出來BeanDefinition,基於你配置的資訊來建立物件。

二、 Spring Bean 註冊階段

Spring Bean 元資訊讀取階段結束後會為每一個bean生成一個BeanDefinition,你配置了那麼多的bean就有那麼多BeanDefinition吧,怎麼都得有一個地方存吧,可以很好想到的,java中資料結構還是很多的,比如說list,map等,所以spring選擇通過map來存,只不過spring考慮的比較完善,封裝了一個類,叫BeanDefinitionRegistry,直接實現就是DefaultListableBeanFactory這個類(這個類是spring ioc核心類,是BeanFactory基本上算是唯一的實現,非常非常的重要),通過registerBeanDefinition方法進行註冊

圖片

 在此為止,你通過xml或者各種方式宣告的bean已經註冊到ioc容器中了,所謂的註冊,就是表明,這些物件需要spring來幫你建立,雖然你只配置了一下,但是spring得去做很多的事來讀取配置。所以接下來就是通過spring 來獲取到你註冊bean,就會進入spring bean的建立階段

三、Bean的獲取階段

為什麼先講獲取。因為spring原始碼中是先從ioc容器中獲取物件,獲取不到再建立的。所以這裡先從DefaultListableBeanFactory的doGetBean方法入手,它會委派給它的父類來處理

圖片

 先根據getSingleton方法去獲取物件 ,這裡就牽扯出三級快取解決迴圈依賴的問題.。

spring是如何解決迴圈依賴的?

我之前寫過一篇關於三級快取是如何解決迴圈依賴的,文章通過畫圖的方式,一步一步來講解三級快取在Bean建立過程中是如何發揮作用的,以及三級快取不能解決迴圈依賴的場景。如果有需要的話可以關注微信公眾號 三友的java日記 ,回覆 迴圈依賴 即可獲取文章連結。

圖片

 

 四、Bean的例項化階段

從這個階段開始,bean就會一步一步被建立出來父容器也沒有,就要自己去建立這個物件,在建立之前合併BeanDefinition 和 註冊依賴的bean,@DependsOn註解就是在這個階段發揮作用的

 

圖片

 接下來就是對bean的作用返回進行判斷,從這裡可以看出,其實spring對於bean的作用範圍中的單例和多例其實是採用硬編碼的方式來進行完成的,其餘的bean的作用範圍,比如在web環境中的bean作用域session、springcloud環境中的@RefreshScope註解等都是通過擴充套件org.springframework.beans.factory.config.Scope的實現來完成的。大家有興趣可以看看SessionScope和RefreshScope這兩個實現類。

 

圖片

補充一點,肯定會有人好奇,我的程式碼明明沒有動過,我的Controller一直在那,怎麼做到的一個session一個Controller,其實原理很簡單,就是你看見的Controller其實是個代理物件,每次呼叫的時候都會根據session的不同去重新建立一個新的真正的Controller物件去呼叫,這裡涉及到spring aop的知識,有機會我們再講。不過從這裡可以看出,spring 的 ioc和aop是spring的核心功能,spring實現的其他機制,很多都是通過這兩個特性展開的。

接著我們順著建立單例bean繼續往下看,把建立單例bean的重要的每個環節都看一遍,從這我們就開始深入bean的生命週期原始碼階段。從createBean方法開始

1)bean class 的載入階段

圖片

 因為可能是通過xml檔案來宣告bean的,所以要把bean class載入一下

2)bean例項化之前階段

這個階段主要是回撥所有的InstantiationAwareBeanPostProcessor物件的postProcessBeforeInstantiation方法,這個階段如果有返回物件,直接不走下面的生命週期了(因為返回值不為null,直接return了),所以一般沒有人這麼玩。

BeanPostProcessor元件體系

InstantiationAwareBeanPostProcessor,這個介面是BeanPostProcessor的子類,BeanPostProcessor介面及其衍生的介面(接下來我稱為BeanPostProcessor元件)是bean生命週期中一個非常核心的類體系,因為spring bean在建立過程中不同的階段都會回撥BeanPostProcessor元件的方法,這樣就可以達到擴充套件的目的。因為只要你自己實現了BeanPostProcessor元件,就可以在生命週期的不同階段可以對你的bean進行不同的操作,達到自己的目的。比如說阿里開源的dubbo中@DubboReference註解(2.7.7版本推出的註解,取代@Reference註解,功能沒有什麼變化)在整合spring的過程中主要是通過ReferenceAnnotationBeanPostProcessor來實現的,這個介面就是BeanPostProcessor的實現。說實話,bean的生命週期一大部分都是通過BeanPostProcessor元件來完成擴充套件的。

我們繼續往下看

圖片

進入resolveBeforeInstantiation方法

圖片

進入applyBeanPostProcessorsBeforeInstantiation方法

3)bean的例項化階段

這個階段是根據你的class物件,來建立一個例項物件出來。

進入doCreateBean方法

圖片

進入createBeanInstance方法,物件就在這個方法建立的

圖片

@Bean的構建方式、構造器注入建立物件的方式,這兩個建立的細節就不研究了

圖片

通過帶構造引數的例項化構造方法來例項化我們就不看了。那麼就進入instantiateBean方法

圖片

從這裡可以看出,不論怎麼走,都是通過getInstantiationStrategy方法獲取例項化物件的策略然後呼叫instantiate來例項化物件。點進getInstantiationStrategy方法會發現其實是獲取的CglibSubclassingInstantiationStrategy,那麼我們就進入instantiate方法

圖片

這裡我們可以看出,其實是獲得了class的預設構造器,然後呼叫BeanUtils.instantiateClass(constructorToUse)來例項化物件,這是這內部就是簡單的反射呼叫構造器建立物件。就不點進去了。

其實從這裡我們可以看出,其實spring在建立物件例項的時候,最簡單的方式其實就是通過反射直接通過呼叫的構造方法進行例項化。其實spring物件的例項化還有其他的方式,比如我上面圖片標註的@Bean的構建方式、構造器注入建立物件的方式都不是走這。

在後面就是對建立建立出來的物件包裝成BeanWrapper物件,直接返回。至此,bean的物件就被例項化出來了。

4) bean 的例項化之後階段

接著往下看。

圖片

 這是一個很重要的一步,主要是為了解決迴圈依賴的,跟文章最前面說的解決迴圈依賴是能夠相呼應上的。

接下來看populateBean方法 

圖片

 

圖片

 看看,這裡就是繼續回撥BeanPostProcessor元件體系的方法,所以回撥完就表明spring bean的建立階段完成。至於這個階段為什麼叫spring的bean的例項化之後階段,你可以看看回撥的方法的名字,翻譯過來就是後置處理在bean例項化之後,所以叫這個階段。

這個方法回撥完之後下面程式碼就是bean生命週期中又一個核心的階段,那就是屬性賦值階段,什麼@Autowired依賴注入之類的其實就是在下面程式碼給你完成的。但是你有沒有發現,postProcessAfterInstantiation如果這個方法返回false,下面的程式碼就不會執行了,所以一般擴充套件也沒有返回false的,沒人這麼玩。其實你可以發現,spring在bean的建立過程中提供了非常多的可擴充套件點,你可以在每個階段改變bean的建立行為,雖然可能沒有人去這麼做,但是spring依然提供了這些點。其實這也是讀原始碼的有趣的地方,你可以看見各種擴充套件點,自己就可以去使用擴充套件點,進行各種騷操作。

總結

我們把這篇文章總結一下,最開始根據配置bean的方式封裝到BeanDefinition中註冊到BeanDefinitionRegistry中,然後說講了bean的獲取,自己的容器獲取不到就會從父容器獲取,如果都沒獲取到就會自己建立。說建立之前,簡單的說明了spring是如何通過三級快取解決迴圈依賴的問題。建立的時候會根據bean的作用域不同,進行了不同的建立。接下來我們選擇了深入單例bean的建立原始碼,進入了bean建立的生命週期建立階段,bean class 的載入,bean的例項化階段,詳細分為例項化之前階段、例項化階段、例項化之後階段,順便插入了對BeanPostProcessor元件體系的講解。至於spring bean的生命週期的其它階段,比如屬性賦值階段,可以看一下這篇文章 Spring Bean到底是如何建立的(下)

如果覺得這篇文章對你有所幫助,還請幫忙點贊、在看、轉發給更多的人,碼字不易,非常感謝!

往期熱門文章推薦

掃碼或者搜尋關注公眾號 三友的java日記 ,及時乾貨不錯過,公眾號致力於通過畫圖加上通俗易懂的語言講解技術,讓技術更加容易學習。 

相關文章