1. 程式人生 > 程式設計 >Spring為何要用三級快取來解決迴圈依賴問題

Spring為何要用三級快取來解決迴圈依賴問題

我們都知道Spring為了解決迴圈依賴使用了三級快取

Spring三級快取

一級快取singletonObjects

用於儲存BeanName和建立bean例項之間的關係,beanName -> bean instance

private final Map<String,Object> singletonObjects = new ConcurrentHashMap(256);

二級快取earlySingletonObjects

儲存提前曝光的單例bean物件

private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);

三級快取singletonFactories

儲存beanName和建立bean例項之間的關係,與singletonObjects不同的地方在於,當一個單例bean被放到這裡面後,bean在建立過程中,可以通過getBean方法獲取到,目的是用來檢測迴圈引用

private final Map<String,Object> singletonFactories = new HashMap(16);

在建立bean的時候,首先從快取中獲取單例的bean,這個快取就是singletonObjects,如果獲取不到且bean正在建立中,就再從earlySingletonObjects中獲取,如果還是獲取不到且允許從singletonFactories中通過getObject拿到物件,就從singletonFactories中獲取,如果獲取到了就存入earlySingletonObjects並從singletonFactories中移除。

為什麼要使用三級快取?

一級快取

首先我們看看一級快取行不行,如果只留第一級快取,那麼單例的Bean都存在singletonObjects 中,Spring迴圈依賴主要基於Java引用傳遞,當獲取到物件時,物件的field或者屬性可以延後設定,理論上可以,但是如果延後設定出了問題,就會導致完整的Bean和不完整的Bean都在一級快取中,這個引用時就有空指標的可能,所以一級快取不行,至少要有singletonObjects 和earlySingletonObjects 兩級。

兩級快取

那麼我們再看看兩級快取行不行

現在有A的field或者setter依賴B的例項物件,同時B的field或者setter依賴了A的例項,A首先開始建立,並將自己暴露到 earlySingletonObjects 中,開始填充屬性,此時發現自己依賴B的屬性,嘗試去get(B),發現B還沒有被建立,所以開始建立B,在進行屬性填充時初始化A,就從earlySingletonObjects 中獲取到了例項化但沒有任何屬性的A,B拿到A後完成了初始化階段,將自己放到singletonObjects中,此時返回A,A拿到B的物件繼續完成初始化,完成後將自己放到singletonObjects中,由A與B中所表示的A的屬性地址是一樣的,所以A的屬性填充完後,B也獲取了A的屬性,這樣就解決了迴圈的問題。

似乎完美解決,如果就這麼使用的話也沒什麼問題,但是再加上AOP情況就不同了,被AOP增強的Bean會在初始化後代理成為一個新的物件,也就是說:

如果有AOP,A依賴於B,B依賴於A,A例項化完成暴露出去,開始注入屬性,發現引用B,B開始例項化,使用A暴露的物件,初始化完成後封裝成代理物件,A再將代理後的B注入,再做代理,那麼代理A中的B就是代理後的B,但是代理後的B中的A是沒用代理的A。

顯然這是不對的,所以在Spring中存在第三級快取,在建立物件時判斷是否是單例,允許迴圈依賴,正在建立中,就將其從earlySingletonObjects中移除掉,並在singletonFactories放入新的物件,這樣後續再查詢beanName時會走到singletonFactory.getObject(),其中就會去呼叫各個beanPostProcessor的getEarlyBeanReference方法,返回的物件就是代理後的物件。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
  implements AutowireCapableBeanFactory { 
 
  // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  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");
   }
   addSingletonFactory(beanName,() -> getEarlyBeanReference(beanName,mbd,bean));
  }

  /**
  * Add the given singleton factory for building the specified singleton
  * if necessary.
  * <p>To be called for eager registration of singletons,e.g. to be able to
  * resolve circular references.
  * @param beanName the name of the bean
  * @param singletonFactory the factory for the singleton object
  */
 protected void addSingletonFactory(String beanName,ObjectFactory<?> singletonFactory) {
  Assert.notNull(singletonFactory,"Singleton factory must not be null");
  synchronized (this.singletonObjects) {
   if (!this.singletonObjects.containsKey(beanName)) {
    this.singletonFactories.put(beanName,singletonFactory);
    this.earlySingletonObjects.remove(beanName);
    this.registeredSingletons.add(beanName);
   }
  }
 }

總結

到此這篇關於Spring為何要用三級快取來解決迴圈依賴問題的文章就介紹到這了,更多相關Spring用三級快取解決迴圈依賴內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!