天天看點

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

文章目錄

  • 什麼是循環依賴
  • 如何解決
    • 官方的幾點建議
    • 屬性注入如何解決循環依賴
    • 關鍵詞介紹
  • 為什麼要用三級緩存
    • 推斷
    • 分析
    • 代碼舉證
    • 原因分析
    • 結論

什麼是循環依賴

循環依賴主要是 A服務依賴B服務,B服務又依賴A服務,兩者之間的調用關系形成了一個環,也就是環形調用,這種一般是由于不規範的的編碼行為導緻。

如何解決

在Spring 中,依賴注入有兩種方式,以xml配置為例,

  1. 屬性注入
  2. 構造器注入

官方的幾點建議

關于循環依賴,spring官方給了幾種建議:

  1. 對于屬性注入,spring在bean執行個體化的時候使用了三級緩存的方案來解決循環依賴,僅針對單例模式,也就是bean的作用域是配置single的,才能解決,對于原型模式等,會直接報錯。
  2. 構造器注入:
    1. 使用 @LazyInit 注解,延遲其中一個bean的執行個體化時間。
    2. 修改編碼方式,沒有循環依賴的關系存在。

屬性注入如何解決循環依賴

我們這裡主要是來分析第一種屬性注入方式,spring是如何通過代碼來解決循環依賴的問題。

為了突出重點,以下代碼塊,我們隻截出了和循環依賴相關的代碼。

public Object getBean(String name) throws BeansException {
    
      return doGetBean(name, null, null, false);
    }
    
    
    protected <T> T doGetBean(
          final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
          throws BeansException {
       //提取對應的beanName
       final String beanName = transformedBeanName(name);
       Object bean;
    
       //檢查緩存或執行個體工廠中是否有對應的執行個體
       Object sharedInstance = getSingleton(beanName);
       if (sharedInstance != null && args == null) {
          if (logger.isDebugEnabled()) {
             if (isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                      "' that is not fully initialized yet - a consequence of a circular reference");
             }
             else {
                logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
             }
          }
           //傳回對應的執行個體,有時候存在諸如BeanFactory的情況并不是直接傳回執行個體本身而是傳回指定方法傳回的執行個體
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
       }
    
       else {
     	 //隻有在單例情況才會嘗試解決循環依賴
          if (isPrototypeCurrentlyInCreation(beanName)) {
             throw new BeanCurrentlyInCreationException(beanName);
          }
    
          BeanFactory parentBeanFactory = getParentBeanFactory();
            //如果 beanDefinitionMap 中也就是在所有已經加載的類中不包括 beanName 則嘗試從parentBeanFactory中檢測
          if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
       		//遞歸到BeanFactory中尋找
             String nameToLookup = originalBeanName(name);
             if (args != null) {
                // Delegation to parent with explicit args.
                return (T) parentBeanFactory.getBean(nameToLookup, args);
             }
             else {
                 
                return parentBeanFactory.getBean(nameToLookup, requiredType);
             }
          }
    }
        
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    	Object singletonObject =this.singletonObjects.get(beanName);
    	if (singletonObject == null) {
    		synchronized (this.singletonObjects) {
    			singletonObject =this.earlySingletonObjects.get(beanName);
    			if (singletonObject == null &&allowEarlyReference) {
    				ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
    				if (singletonFactory != null) {
    					singletonObject =singletonFactory.getObject();
    		this.earlySingletonObjects.put(beanName,singletonObject);
    				this.singletonFactories.remove(beanName);
    					}
    				}
    			}
    		}
    		return (singletonObject != NULL_OBJECT ? singletonObject : null);
    	}
           

getSingleton(String beanName, boolean allowEarlyReference),在擷取bean的開始首先使用的就是這段方法,為什麼會首先使用這段代碼呢,這段代碼主要就是為了避免循環依賴的問題産生的,在建立單例bean的時候會存在依賴注入的情況,spring建立bean的原則是不等bean建立完成就會将bean的ObjectFactory提前曝光,也就是将ObjectFactory放入到緩存中,如果下個bean建立的時候需要依賴這個bean,則直接使用上。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //檢查緩存是否存在執行個體過,因為singleton模式其實就是複用已建立的bean
   Object singletonObject = this.singletonObjects.get(beanName);
    
    //如果不存在,則鎖定singletonObjects全局變量進行擷取
   if (singletonObject == null) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {

               singletonObject = singletonFactory.getObject();
               //記錄在緩存中,earlySingletonObjects和singletonFactories互斥
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
           

關鍵詞介紹

  1. singletonObjects:用于儲存BeanName和建立bean執行個體之間的關系,俗稱的一級緩存。
  2. earlySingletonObjects:儲存 BeanName 和建立 bean 執行個體之間的關系,與singletonObjects的不同之處在于,當一個單例bean被放到這裡面後,那麼當bean還在建立過程中,就可以通過getBean方法擷取到了,其目的是用來檢測循環引用,二級緩存。
  3. singletonFactories:用于儲存BeanName和建立 bean的工廠之間的關系,三級緩存。
  4. registeredSingletons:用來儲存目前所有已注冊的bean。

singletonFactories作為最終的緩存,是在什麼時候有資料的呢,在doCreateBean中可以看到有存放的動作,我們跟着代碼往下走:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    		
    		// 判斷是否需要提前暴露bean:1.是否單例,2.是否允許循環依賴,3. 目前bean是否正在建立中
    		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");
			}
            //重點:上面的條件滿足後,為避免後期bean循環依賴,在bean初始化完成之前将建立ObjectFactory加入工廠
			addSingletonFactory(beanName, new ObjectFactory() {
				public Object getObject() throws BeansException {
					return getEarlyBeanReference(beanName, mbd, bean);
				}
			});
		}
}

protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if 
                (!this.singletonObjects.containsKey(beanName)) {
                //singletonFactories和earlySingletonObjects互斥
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}
           

通過源碼的分析,在spring中出現循環依賴的的情況的時候,比如A類包含有屬性B,B類又包含屬性A,在A和B都是單例的情況下,在建立 B的時候,并不是再去直接建立 A,而是通過放入緩存中地方ObjectFactory來建立執行個體,也就是singletonFactories,這樣就解決了循環依賴的問題。

我們來模拟一個A和B互相包含的建立過程:

  1. 首先,建立A的執行個體, A在經曆了一系列檢查之後,調用addSingletonFactory()方法,這時singletonFactories就有了包含A的ObjectFactory。
  2. 然後,A開始填充屬性的時候,發現B,這時B還沒有建立,然後開始了B的建立過程。
  3. 這時候B在重複了1的條件之後,在填充屬性的時候發現A,注意這時候,singletonFactories已經有了包含A的ObjectFactory,是以在getSingleton()的時候能夠擷取到A的,這時B就已經完成了初始化。
  4. 在B建立完成之後,A中的屬性B也就能夠填充,A也完成了之後的初始化。

是以Spring解決循環依賴的思路的本質就是通過一個中間存儲,提前暴露對象,來解決該問題的,也就是twosum。那麼根據上面的代碼分析,隻需要二級緩存就可以了,為什麼需要三級緩存?

為什麼要用三級緩存

推斷

隻需要二級緩存就可以了,為什麼需要三級緩存?

因為有代理的存在,為了避免每次擷取都生成新的代理,是以采用了三級。

分析

這個得從addSingletonFactory(String beanName, ObjectFactory singletonFactory)這個方法說起,在上面的doCreateBean()源碼中,我們看到,調用addSingletonFactory()的時候,傳入了一個匿名函數, ObjectFactory的getObject()實際上調用的是getEarlyBeanReference()

addSingletonFactory(beanName, new ObjectFactory() {
	public Object getObject() throws BeansException {
        //匿名函數, getObject()實際上調用的是getEarlyBeanReference()
		return getEarlyBeanReference(beanName, mbd, bean);
	}
});
           

}

這時回到我們剛剛模拟建立過程的第三步:

執行個體B在建立屬性A的時候,**A已經提前暴露在singletonFactories裡了,**是以B是從singletonFactories裡擷取的A,那麼實際上singletonObject = singletonFactory.getObject()執行的就是getEarlyBeanReference()這個擷取方法( 匿名函數傳進去的).如下圖

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

在getEarlyBeanReference()中,當bean的後置處理器是SmartInstantiationAwareBeanPostProcessor類型的時候,還會在調用實作類的getEarlyBeanReference的方法。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
       Object exposedObject = bean;
       if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
          for (BeanPostProcessor bp : getBeanPostProcessors()) {
             
              if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                  //當bean的後置處理器是SmartInstantiationAwareBeanPostProcessor類型的時候,還會在調用實作類的getEarlyBeanReference的方法
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                   return exposedObject;
                }
             }
          }
       }
       return exposedObject;
    }
           

我們來看下SmartInstantiationAwareBeanPostProcessor有哪些實作類

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

其中的一個實作類AbstractAutoProxyCreator,很明顯,這是一個生成代理類的工具類,那麼這時候, 我們隻要驗證需要被代理的類在調用了這個方法的時候是不是每次都會生成新的代理對象就可以了。

//AbstractAutoProxyCreator實作類中getEarlyBeanReference方法
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.add(cacheKey);
   return wrapIfNecessary(bean, beanName, cacheKey);
}
           

代碼舉證

如下面代碼所示,我們建構了一個循環依賴的示例代碼

@Service
    public class TestA {
    
        @Autowired
        TestB testB;
    
    }
    
    @Service
    public class TestB {
    
        @Autowired
        TestA testA;
    
    }
           

現在啟動spring容器,觀察剛剛的模拟條件3,B擷取A屬性的時候,是否會生成新的對象。

這是 已經執行了singletonFactory.getObject()的時候擷取的A對象示例

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

多次執行,發現A對象的位址沒有變,是以剛剛的推論不成立。

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

問題出在哪裡呢?

是因為A沒有被代理,是以不會生成新的對象,我們下面增加代理類,重新驗證。

@Aspect
@Component
public class AopProxy {


    @Around("execution(public * com.test.impl.TestA.*(..))")
    public void test(){
        System.out.println("測試代理對象");
    }
}

@Service
public class TestA {

    @Autowired
    TestB testB;

    public void test(){
        System.out.println("我是被代理的");
    }
}

@Service
public class TestB {

    @Autowired
    TestA testA;

}
           

增加了AopProxy代理類來代理TestA,然後繼續試驗。

一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存
一文讀懂Spring循環依賴什麼是循環依賴如何解決為什麼要用三級緩存

上圖是第一次擷取和debug再次執行singletonFactory.getObject()的時候,很明顯對象位址不同,TestA類被代理後,每次執行singletonFactory.getObject()方法,都會生成一個新的代理對象。

原因分析

回到上文,如果使用二級緩存,每次調用都生成一個新的對象,會産生什麼後果?

從文章讀下來,我們知道隻有是單例模式并且是屬性注入的循環依賴,spring才能解決,如果隻使用二級緩存,注入會出現什麼問題呢?僞代碼如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
       Object singletonObject = this.singletonObjects.get(beanName);
       if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
          synchronized (this.singletonObjects) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
              
                if (singletonFactory != null) {
                    //原來第三級緩存變成二級緩存,這時如果beanName被代理,則每次get都會生成一個新的對象。
                   singletonObject = singletonFactory.getObject();
                   this.singletonFactories.remove(beanName);
                }
             }
          }
       }
       return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
           

結論

通過上面僞代碼分析,如果變成二級緩存,則該對象發生多個屬性循環依賴的時候(TestA 包含屬性B,C,B和C均依賴A),則每次都會生成一個新的代理對象,這樣就破壞了單例模式的語義,并且由于該bean提前暴露的是一個半成品,并沒有完全加載成功,那樣singletonObject會存在大量的半成品bean,破壞了bean的生命周期。