Spring IOC原始碼深度剖析:Spring IoC迴圈依賴問題

無敵天驕發表於2021-03-28

一、什麼是迴圈依賴

迴圈依賴其實就是迴圈引⽤,也就是兩個或者兩個以上的 Bean 互相持有對⽅,最終形成閉環。⽐如A依賴於B,B依賴於C,C⼜依賴於A。

Spring IOC原始碼深度剖析:Spring IoC迴圈依賴問題

注意,這⾥不是函式的迴圈調⽤,是物件的相互依賴關係。迴圈調⽤其實就是⼀個死迴圈,除⾮有終結條件。

Spring中迴圈依賴場景有:

  • 構造器的迴圈依賴(構造器注⼊)
  • Field 屬性的迴圈依賴(set注⼊)

其中,構造器的迴圈依賴問題⽆法解決,只能丟擲  BeanCurrentlyInCreationException 異常,在解決屬性迴圈依賴時,spring採⽤的是提前暴露物件的⽅法。

二、迴圈依賴處理機制


  • 單例 bean 構造器引數迴圈依賴(⽆法解決)

  • prototype 原型 bean迴圈依賴(⽆法解決)

對於原型bean的初始化過程中不論是透過構造器引數迴圈依賴還是透過 setXxx⽅法產⽣迴圈依賴,Spring都 會直接報錯處理。

AbstractBeanFactory.doGetBean()⽅法:

if (isPrototypeCurrentlyInCreation(beanName)) {
 throw new BeanCurrentlyInCreationException(beanName);
}


protected boolean isPrototypeCurrentlyInCreation(String beanName) {
 Object curVal = this.prototypesCurrentlyInCreation.get();
 return (curVal != null &&
 (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)curVal).contains(beanName))));
}

在獲取bean之前如果這個原型bean正在被建立則直接丟擲異常。原型bean在建立之前會進⾏標記這個 beanName正在被建立,等建立結束之後會刪除標記

try {
 //建立原型bean之前新增標記 beforePrototypeCreation(beanName);
 //建立原型bean prototypeInstance = createBean(beanName, mbd, args);}finally {
 //建立原型bean之後刪除標記 afterPrototypeCreation(beanName);
}

總結:Spring 不⽀持原型 bean 的迴圈依賴。

單例bean透過 setXxx或者 @Autowired進⾏迴圈依賴

Spring 的迴圈依賴的理論依據基於 Java 的引⽤傳遞,當獲得物件的引⽤時,物件的屬性是可以延後設定的,但是構造器必須是在獲取引⽤之前

Spring透過 setXxx或者 @Autowired⽅法解決迴圈依賴其實是透過提前暴露⼀個 ObjectFactory物件來完成的,簡單來說ClassA在調⽤構造器完成物件初始化之後,在調⽤ClassA的 setClassB⽅法之前就把ClassA例項化的物件透過 ObjectFactory提前暴露到Spring容器中。

  • Spring容器初始化ClassA透過構造器初始化物件後提前暴露到Spring容器。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");
    }
    //將初始化後的物件提前已ObjectFactory物件注⼊到容器中    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
 }
  • ClassA調⽤setClassB⽅法,Spring⾸先嚐試從容器中獲取ClassB,此時ClassB不存在Spring容器中。

  • Spring容器初始化ClassB,同時也會將ClassB提前暴露到Spring容器中

  • ClassB調⽤setClassA⽅法,Spring從容器中獲取ClassA ,因為第⼀步中已經提前暴露了ClassA,因此可以獲取到ClassA例項

  • ClassA透過spring容器獲取到ClassB,完成了物件初始化操作。

  • 這樣ClassA和ClassB都完成了物件初始化操作,解決了迴圈依賴問題。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69964492/viewspace-2765336/,如需轉載,請註明出處,否則將追究法律責任。

相關文章