首先看一下下面的Spring配置文件java
<!-- beanA依賴於beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
<property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依賴於beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
<property name="beanA" ref="beanA"/>
</bean>
複製代碼
當IOC容器讀取上面的配置時,就會先對beanA進行加載;在對beanA進行屬性填充時,會發現beanA依賴於beanB,而後就會對beanB進行加載;當對beanB進行屬性填充時,又會發現beanB依賴於beanA,因而就加載beanA... 能夠想到,若是Spring的容器對於這種循環依賴問題不做出響應的處理,那麼就會無限執行上面的過程。最終的結果就可能形成OOM從而致使程序崩潰 spring
咱們知道在Spring中,注入bean的方式有【構造器注入】和【setter注入】兩種方式。但在咱們使用Spring管理bean時,可能會遇到一種特殊的狀況,那麼就是上面所說的循環依賴問題 咱們再看一下Spring建立bean的過程緩存
若是閱讀過IOC相關的源碼就會知道,建立bean的過程大致能夠分爲初始化bean
,對bean的屬性進行填充
,對bean進行初始化
三個步驟app
init-method
方法,因此能夠將一些初始化的行爲寫到這個方法中而後就來分析一下兩種注入方式ui
在普通的java程序中,若是已經new出了一個對象,咱們就知道這個對象已是可用的了,不論它的屬性是否完整。 但在Spring中,建立出來的bean必需要完成三個步驟才能被認爲是可用的,纔會將這個「完整」的bean放入到IOC容器中。 由於構造器注入是在實例化對象時反射調用構造器去注入參數,因此既然beanA、beanB的都拿不到完整的依賴,就會進行無限的循環調用,從而沒法解決【循環依賴問題】。解決辦法就只有是修改依賴關係了this
再看一下setter注入方式 setter注入方式就是new出一個對象後,調用該對象的set方法對屬性進行賦值。此時對象已經被new出來了,只不過是不完整而已。 若是出現了循環依賴的問題,這就要比構造器注入的方式好的多 因此Spring對於循環依賴問題的解決就是針對於setter方法的spa
接下來就開始分析Spring是如何解決循環依賴問題的3d
首先咱們要知道,Spring對於循環依賴的問題是採用【緩存】的方式解決的 看一下Spring源碼中的DefaultSingletonBeanRegistry類(注:SingletonBeanRegistry接口提供了關於訪問單例bean的功能,DefaultSingletonBeanRegistry就是該接口的默認實現)代理
/** Cache of singleton objects: bean name to bean instance. */
// 用於存儲完整的bean,接下來稱之爲【一級緩存】
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
// 用於存儲不完整的bean,即只是new出來,並無屬性值的bean,接下來稱之爲【二級緩存】
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
//用於存儲bean工廠對象,接下來稱之爲【三級緩存】
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
複製代碼
由於循環依賴都是產生在獲取bean時,因此咱們直接從AbstractBeanFactory的getBean()方法開始code
Object sharedInstance = getSingleton(beanName);
上面並無涉及到循環依賴和二級、三級緩存的問題,由於對於循環依賴的處理,都表如今代碼中的細節之處
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
// 從緩存中獲取單例bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { //若是獲取到單例bean,則走下面代碼
//......
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else {//若是沒有獲取到單例bean,則走下面代碼
//......
// 若是是單例的Bean,請下面的代碼
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
// 建立單例Bean的主要方法,返回的bean是完整的
return createBean(beanName, mbd, args);
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//......
}
return (T) bean;
}
}
複製代碼
上面的代碼中,sharedInstance是經過getSingleton()方法得到的,實際上getSingleton(beanName)方法沒什麼邏輯,內部調用了getSingleton(beanName, boolean)這個方法,因此接下來就進入到這個方法中
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中獲取單例對象
Object singletonObject = this.singletonObjects.get(beanName);
// isSingletonCurrentlyInCreation : 判斷當前單例bean是否正在建立中,也就是沒有初始化完成。好比beanA的構造器依賴了beanB對象因此得先去建立B對象,或者在A的populateBean過程當中依賴了B對象,得先去建立B對象,這時的beanA就是處於建立中的狀態
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從二級緩存中獲取單例bean
singletonObject = this.earlySingletonObjects.get(beanName);
// allowEarlyReference :是否容許從singletonFactories中經過getObject拿到對象
if (singletonObject == null && allowEarlyReference) {
// 從三級緩存中獲取單例bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 經過單例工廠獲取單例bean
singletonObject = singletonFactory.getObject();
// 從三級緩存移動到了二級緩存,並移除singletonFactory
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
複製代碼
從上面的代碼中能夠總結出如下幾點:
singletonFactory.getObject()
)。經過ObjectFactory獲取到的對象,是進行代理後的對象(假設有AOP)。將從【三級緩存】中獲取到的對象放到【二級緩存】中,同時刪除此beanName對應的【三級緩存數據】若是getSingleton()方法獲取到了bean,即sharedInstance不爲null,則對其進行處理而後返回 若是sharedInstance爲null,就要走else中的代碼了 首先判斷一下是否爲單例,(mbd是經過讀取配置文件中bean標籤生成的bean的定義信息,具體得到的方法這裏不詳細說了)。由於多例的bean是不須要放入到IOC容器中的,因此這裏只處理單例bean 若是爲單例,則調用getSingleton(String beanName, ObjectFactory<?> singletonFactory)
方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// ......
// 建立 bean 實例
singletonObject = singletonFactory.getObject();
newSingleton = true;
if (newSingleton) {
// 添加新建立的bean添加到【一級緩存】中,並刪除其餘緩存中對應的bean
addSingleton(beanName, singletonObject);
}
// ......
// 返回 singletonObject
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 將新建立的bean添加到【一級緩存】中
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
// 從其餘緩存中移除相關的bean
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
複製代碼
上面的代碼主要包含了兩個功能
能夠看到bean實例是由singletonFactory.getObject()
拿到的,也就是經過doGetBean()
方法中判斷是否單例後的匿名內部類獲取到的,從而知道獲取到的bean是由createBean()方法建立的
creatBean()方法調用了doCreatBean()方法,因此實際的建立邏輯就再doCreatBean()中
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// 默認調用無參構造實例化Bean
// 構造方法的依賴注入,就是發生在這一步
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 實例化後的Bean對象,這裏獲取到的是一個原始對象,即沒有進行屬性填充的對象
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
//......
// 解決循環依賴的關鍵步驟
// earlySingletonExposure:是否」提早暴露「原始對象的引用
// 由於不論這個bean是否完整,他先後的引用都是同樣的,因此提早暴露的引用到後來也指向完整的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 若是須要提早暴露單例bean,則將該bean工廠放入【三級緩存】中
if (earlySingletonExposure) {
// 將剛建立的bean工廠放入三級緩存中singleFactories(key是beanName,value是FactoryBean)
// 一樣也會移除【二級緩存】中對應的bean,即使沒有
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//填充屬性(依賴注入)
populateBean(beanName, mbd, instanceWrapper);
//調用初始化方法,完成bean的初始化操做
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//......
return exposedObject;
}
複製代碼
ok,看到這裏,整個在有循環依賴問題下建立、獲取bean的流程就結束了 舉個例子,從頭串一下流程。假設beanA->beanB, beanB->beanA,即A、B相互依賴
整個過程大概就是這樣了,因爲spring的源碼比較多,就只挑選了重點部分進行註釋 其實主要思想就是利用二級、三級緩存對未初始化完成的bean進行提早的引用暴露,也就是將其設置爲可引用的,這樣當依賴於他的bean在進行屬性填充時就能夠直接拿到引用,解決了死循環的問題
還有幾個比較重要的點,在這裏指出位置,能夠根據這些去查找看
》》》》》》》》》》》》》》》》》》》》》
有些東西不知道怎麼轉述成語言表達出來,還有若是有很差的或者說錯的地方但願看過的大佬能幫忙指正,謝謝~~