問題:Spring中是如何初始化單例bean的?
我們都知道Spring解析xml文件描述成BeanDefinition,解析BeanDefinition最后創(chuàng)建Bean將Bean放入單例池中,那么Spring在創(chuàng)建Bean的這個過程都做了什么。
Spring核心方法refresh()中最最重要的一個方法 finishBeanFactoryInitialization() 方法,該方法負(fù)責(zé)初始化所有的單例bean。
finishBeanFactoryInitialization()方法位于refresh()中第11步。
走到這一步的時候,Spring容器中所有的BeanFactory都已經(jīng)實例化完成了,也就是實現(xiàn)BeanFactoryPostProcessor
接口的 Bean 都已經(jīng)初始化完成了。剩下的就是初始化singleton beans
,在我們的業(yè)務(wù)bean中大多數(shù)都是單例的,finishBeanFactoryInitialization
這一步就是去實例化單例的并且沒有設(shè)置懶加載的Bean。
Spring會在finishBeanFactoryInitialization
這個方法里面初始化所有的singleton bean
。
Ok,我們先來看一下finishBeanFactoryInitialization方法內(nèi)部的邏輯。這個方法的核心就在于完成BeanFactory的配置。該階段完成了上下文的實例化,包含所有單例Bean對象已經(jīng)實例化。
核心在于 preInstantiateSingletons() 方法,preInstantiateSingletons方法主要任務(wù)是進(jìn)行初始化,在初始化前同樣是一系列判斷,如,是否是懶加載的,是否是一個factorybean(一個特別的bean,負(fù)責(zé)工廠創(chuàng)建的bean),最后調(diào)用getBean()方法。
注釋中提到的SmartInitializingSingleton
接口,是讓bean初始化后做一些操作。
OK,那么主要的方法還是getBean()
方法,getBean()方法的作用就是加載、實例化Bean。方法內(nèi)部調(diào)用了doGetBean(),我們直接看**doGetBean()**方法內(nèi)部。
代碼很多,我們主要看createBean()方法。
我們繼續(xù)往doCreateBean 這個方法里面看。
方法很多,我們主需要關(guān)注三個方法即可。
- createBeanInstance:實例化,其實也就是調(diào)用對象的構(gòu)造方法實例化對象
- populateBean:填充屬性,這一步主要是多bean的依賴屬性進(jìn)行填充
- initializeBean:調(diào)用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環(huán)依賴主要發(fā)生在第一、第二步。也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴。
那么我們要解決循環(huán)引用也應(yīng)該從初始化過程著手,對于單例來說,在Spring容器整個生命周期內(nèi),有且只有一個對象,所以很容易想到這個對象應(yīng)該存在Cache中,Spring為了解決單例的循環(huán)依賴問題,使用了三級緩存。
什么是Spring的三級緩存?
Spring的IOC容器里面的三級緩存都是Map結(jié)構(gòu)。
- 一級緩存(成熟的bean)
-
singletonObjects
單例池 ,存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用
-
- 二級緩存
-
earlySingletonObjects
提前曝光的單例對象的cache,存放原始的 bean 對象(尚未填充屬性),用于解決循環(huán)依賴
-
- 三級緩存
-
singletonFactories
單例對象工廠的cache,存放 bean 工廠對象,用于解決循環(huán)依賴
-
OK,了解完三級緩存我們再來看下 getSingleton() 這個方法。這個方法主要是用于從單例池中獲取指定名稱的單例Bean實例,方法內(nèi)部實現(xiàn)了三級緩存查找機制,通過三級查找的機制來獲取指定名稱的單例Bean實例對象,同時該方法會使用同步代碼塊保證多線程環(huán)境下的線程安全性。
OK,那么到這里我們會有一個疑問,Spring為什么要用三級緩存來解決循環(huán)依賴的問題。
首先我們要明確一點,Spring可以解決setter的依賴注入,但是不能解決構(gòu)造器的依賴注入。
假如我們現(xiàn)在有個A對象和B對象,A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環(huán)依賴的情況。
A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴對象B,此時就嘗試去get(B),發(fā)現(xiàn)B還沒有被create,所以走create流程,B在初始化第一步的時候發(fā)現(xiàn)自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級緩存singletonObjects中。
此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,進(jìn)去了一級緩存singletonObjects中,而且更加幸運的是,由于B拿到了A的對象引用,所以B現(xiàn)在hold住的A對象完成了初始化。
知道了這個原理時候,肯定就知道為啥Spring不能解決"A的構(gòu)造方法中依賴了B的實例對象,同時B的構(gòu)造方法中依賴了A的實例對象"這類問題啦!因為加入singletonFactories三級緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒法解決。
下面一個案例帶大家體驗下構(gòu)造器注入和set注入的演示
編寫類A類B
public class A {
private B b;
public A() {}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public void method(){
System.out.println("A方法調(diào)用");
}
}
public class B {
private A a;
public B(){}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public void method(){
System.out.println("B方法調(diào)用");
}
}
編寫xml
<bean id="b" class="com.lixiang.demo.B">
<property name="a" ref="a"></property>
</bean>
<bean id="a" class="com.lixiang.demo.A">
<property name="b" ref="b"></property>
</bean>
測試
修改xml配置
<bean id="b" class="com.lixiang.demo.B">
<!--將a改成用構(gòu)造器注入-->
<constructor-arg name="a" ref="a"></constructor-arg>
</bean>
<bean id="a" class="com.lixiang.demo.A">
<property name="b" ref="b"></property>
</bean>
代碼調(diào)整
public class B {
private A a;
public B(A a){
this.a = a;
}
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
public void method(){
System.out.println("B方法調(diào)用");
}
}
測試
可以發(fā)現(xiàn)用構(gòu)造器注入是發(fā)生異常的。
Spring引入了“提前暴露Bean”的機制,在創(chuàng)建A對象時,會先創(chuàng)建一個A的空對象并將其添加到緩存池中。
即“提前暴露Bean”,然后繼續(xù)創(chuàng)建B對象,將其注入A對象中。在創(chuàng)建B對象時,由于A對象已經(jīng)在緩存池中,可以直接獲取到A對象,接著將B對象注入到A對象中,完成Bean的初始化。
好的,到現(xiàn)在整一個Bean的創(chuàng)建流程,就已經(jīng)完成啦。我們在看一下以下三個方法的具體實現(xiàn)。
首先第一個就是 createBeanInstance() 方法
然后是 populateBean() 方法
最后是 initializeBean() 方法
ok,到這里Spring的bean的創(chuàng)建過程就已經(jīng)梳理完成啦。
記得點個贊+關(guān)注哦!文章來源:http://www.zghlxwxcb.cn/news/detail-485577.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-485577.html
到了這里,關(guān)于【框架源碼】Spring源碼解析之Bean創(chuàng)建源碼流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!