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

前言

眾所周知,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日記 ,及時乾貨不錯過,公眾號致力於通過畫圖加上通俗易懂的語言講解技術,讓技術更加容易學習。