如何通俗易懂地解釋循環依賴?(理論+源碼)

  • 2020 年 2 月 19 日
  • 筆記

說起Spring,通常面試官都會問循環依賴怎麼解決?

如果你沒看過Spring IoC的相關源碼,也不必驚慌,聽我娓娓道來!

其實,解決循環依賴並沒有想像得那麼困難。

Spring IoC是幹什麼事的,你肯定知道,無非就是創建Bean放到IoC容器中,至於這個容器是什麼,你也不必太Care。

既然,要解決循環依賴,那肯定存在著依賴,我們假設有兩個類:

A和B,A->B,B->A,且二者是通過@Autowired相互注入的。

既然,我沒看過IoC的源碼,那我就從我的角度來分析一個創建Bean的過程。(一般你說沒看過,面試官都會說,如果你是Spring的作者,你會怎麼處理?)

我們假設從A開始創建,那就是先創建A對象,然後創建B對象,再通過反射把B對象set到A對象的屬性上去(fieldB.set(a, b)),在創建B的時候發現它又依賴於A,這時候同樣地,我要尋找一個A對象set到B的屬性上去,但是我們的系統中只能存在一個A對象(單例),我該怎麼辦呢?

很簡單,前面創建A對象的時候就把它保存起來不就行了么?嗯想想,是不是這個道理?我們假設保存在快取中,後面B對象要使用的時候先去快取中查找一下不就OK了嘛?!

所以,解決循環依賴的方法就是保存所有創建的對象,後面創建對象的時候有依賴的情況先去快取中找一下,找到了直接set到那個正在創建的對象的屬性上,沒找到就創建一個新的對象給那個正在創建的對象,並保存到快取中。

實際上,Spring中也是這麼乾的,只不過它的快取不只一個,而是有四個,讓我們來看一看下面的方法。

首先,先從singletonObjects快取中尋找存不存在,如果不存在,再看看是不是符合正在創建的過程中(isSingletonCurrentlyInCreation()方法中使用的是singletonsCurrentlyInCreation快取),如果是正在創建中,再看看earlySingletonObjects快取中是不是存在,如果還不存在且允許早期引用,就從singletonFactories快取中尋找有沒有創建這個Bean的工廠,如果找到了,就調用這個工廠的getObject()方法,並把返回值加入到earlySingletonObjects中且把這個工廠移除,從而防止重複調用這個工廠,保證單例。

整個過程一共用到了四個Map:

  • singletonObjects
  • singletonsCurrentlyInCreation
  • singletonFactories
  • earlySingletonObjects

你可能會問,為什麼要搞這麼複雜?

那是因為Spring本身就很複雜,它返回的對象可並不是簡簡單單的對象,很大可能是代理對象,比如,你用到了事務,它會通過AOP代理給你返回一個包含事務的代理對象(通過XxxAutoProxyCreator這種BeanPostProcessor處理),這種對象在早期是不存在的,而我們程式碼中用到的也應該是這個代理對象,那麼,Spring又是怎麼實現的呢?

答案就在singletonFactories這個快取中。

在一個Bean實例化之後,還未執行屬性注入等其它BeanPostProcessor之前,會先往singletonFactories中添加一個對象:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

這裡就加入了一個叫做早期引用相關的東西:

  protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {      Object exposedObject = bean;      if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {        for (BeanPostProcessor bp : getBeanPostProcessors()) {          if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {            SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;            exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);          }        }      }      return exposedObject;    }

上面取出來的singletonFactory執行getObject()的時候就會調用到上面這個方法,然後就會執行這裡的BeanPostProcessor,生成一個代理對象返回去,同樣地,這個代理對象也會快取起來,全局只有這麼一份,等到後面再需要這個類的代理對象的時候直接從快取裡面拿就可以了。

整個過程大概就是這樣,光看上面的描述可能有點糊塗,還是要打斷點自己慢慢調試才能理解比較深刻。