一張圖徹底搞懂Spring循環依賴


1 什么是循環依賴?

如下圖所示:

file

BeanA類依賴了BeanB類,同時BeanB類又依賴了BeanA類。這種依賴關系形成了一個閉環,我們把這種依賴關系就稱之為循環依賴。同理,再如下圖的情況:

file

上圖中,BeanA類依賴了BeanB類,BeanB類依賴了BeanC類,BeanC類依賴了BeanA類,如此,也形成了一個依賴閉環。再比如:

file

上圖中,自己引用了自己,自己和自己形成了依賴關系。同樣也是一個依賴閉環。那么,如果出現此類循環依賴的情況,會出現什么問題呢?

2 循環依賴問題復現

2.1 定義依賴關系

我們繼續擴展前面的內容,給ModifyService增加一個屬性,代碼如下:


@GPService
public class ModifyService implements IModifyService {

	@GPAutowired private QueryService queryService;
    
    ...

}

給QueryService增加一個屬性,代碼如下:


@GPService
@Slf4j
public class QueryService implements IQueryService {

	@GPAutowired private ModifyService modifyService;

    ...
    
}

如此,ModifyService依賴了QueryService,同時QueryService也依賴了ModifyService,形成了依賴閉環。那么這種情況下會出現什么問題呢?

2.2 問題復現

我們來運行調試一下之前的代碼,在GPApplicationContext初始化后打上斷點,我們來跟蹤一下IoC容器里面的情況,如下圖:

file

啟動項目,我們發現只要是有循環依賴關系的屬性並沒有自動賦值,而沒有循環依賴關系的屬性均有自動賦值,如下圖所示:

file

這種情況是怎么造成的呢?我們分析原因之后發現,因為,IoC容器對Bean的初始化是根據BeanDefinition循環迭代,有一定的順序。這樣,在執行依賴注入時,需要自動賦值的屬性對應的對象有可能還沒初始化,沒有初始化也就沒有對應的實例可以注入。於是,就出現我們看到的情況。

3 使用緩存解決循環依賴問題

file

3.1 定義緩存

具體代碼如下:


// 循環依賴的標識---當前正在創建的實例bean
    private Set<String> singletonsCurrectlyInCreation = new HashSet<String>();

    //一級緩存
    private Map<String, Object> singletonObjects = new HashMap<String, Object>();

    // 二級緩存: 為了將成熟的bean和純凈的bean分離. 避免讀取到不完整的bean.
private Map<String, Object> earlySingletonObjects = new HashMap<String, Object>();

3.2 判斷循環依賴

增加getSingleton()方法:


/**
     * 判斷是否是循環引用的出口.
     * @param beanName
     * @return
     */
    private Object getSingleton(String beanName,GPBeanDefinition beanDefinition) {

        //先去一級緩存里拿,
        Object bean = singletonObjects.get(beanName);
        // 一級緩存中沒有, 但是正在創建的bean標識中有, 說明是循環依賴
        if (bean == null && singletonsCurrentlyInCreation.contains(beanName)) {

            bean = earlySingletonObjects.get(beanName);
            // 如果二級緩存中沒有, 就從三級緩存中拿
            if (bean == null) {
                // 從三級緩存中取
                Object object = instantiateBean(beanName,beanDefinition);

                // 然后將其放入到二級緩存中. 因為如果有多次依賴, 就去二級緩存中判斷. 已經有了就不在再次創建了
                earlySingletonObjects.put(beanName, object);


            }
        }
        return bean;
    }
		

3.3 添加緩存

修改getBean()方法,在getBean()方法中添加如下代碼:


		 //Bean的實例化,DI是從而這個方法開始的
    public Object getBean(String beanName){

        //1、先拿到BeanDefinition配置信息
        GPBeanDefinition beanDefinition = regitry.beanDefinitionMap.get(beanName);

        // 增加一個出口. 判斷實體類是否已經被加載過了
        Object singleton = getSingleton(beanName,beanDefinition);
        if (singleton != null) { return singleton; }

        // 標記bean正在創建
        if (!singletonsCurrentlyInCreation.contains(beanName)) {
            singletonsCurrentlyInCreation.add(beanName);
        }

        //2、反射實例化newInstance();
        Object instance = instantiateBean(beanName,beanDefinition);

        //放入一級緩存
        this.singletonObjects.put(beanName, instance);

        //3、封裝成一個叫做BeanWrapper
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        //4、執行依賴注入
        populateBean(beanName,beanDefinition,beanWrapper);
        //5、保存到IoC容器
        factoryBeanInstanceCache.put(beanName,beanWrapper);

        return beanWrapper.getWrapperInstance();
    
		}

3.4 添加依賴注入

修改populateBean()方法,代碼如下:


    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {

        ...

            try {

                //ioc.get(beanName) 相當於通過接口的全名拿到接口的實現的實例
                field.set(instance,getBean(autowiredBeanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        ...

    }

4 循環依賴對AOP創建代理對象的影響

4.1 循環依賴下的代理對象創建過程

我們都知道Spring AOP、事務等都是通過代理對象來實現的,而事務的代理對象是由自動代理創建器來自動完成的。也就是說Spring最終給我們放進容器里面的是一個代理對象,而非原始對象。

這里我們結合循環依賴,再分析一下AOP代理對象的創建過程和最終放進容器內的動作,看如下代碼:


@Service
public class MyServiceImpl implements MyService {
    @Autowired
    private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

此Service類使用到了事務,所以最終會生成一個JDK動態代理對象Proxy。剛好它又存在自己引用自己的循環依賴的情況。跟進到Spring創建Bean的源碼部分,來看doCreateBean()方法:


protected Object doCreateBean( ... ){
	
		...

		// 如果允許循環依賴,此處會添加一個ObjectFactory到三級緩存里面,以備創建對象並且提前暴露引用
		// 此處Tips:getEarlyBeanReference是后置處理器SmartInstantiationAwareBeanPostProcessor的一個方法,
		// 主要是保證自己被循環依賴的時候,即使被別的Bean @Autowire進去的也是代理對象
		// AOP自動代理創建器此方法里會創建的代理對象

		// 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) { // 需要提前暴露(支持循環依賴),注冊一個ObjectFactory到三級緩存
				addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// 如果發現自己被循環依賴,會執行上面的getEarlyBeanReference()方法,從而創建一個代理對象從三級緩存轉移到二級緩存里
		// 注意此時候對象還在二級緩存里,並沒有在一級緩存。並且此時可以知道exposedObject仍舊是原始對象	populateBean(beanName, mbd, instanceWrapper);
		exposedObject = initializeBean(beanName, exposedObject, mbd);
	
		// 經過這兩大步后,exposedObject還是原始對象
		// 注意:此處是以事務的AOP為例
		// 因為事務的AOP自動代理創建器在getEarlyBeanReference()創建代理后,
	 // initializeBean() 就不會再重復創建了,二選一,下面會有詳細描述)
	
		...
	
		// 循環依賴校驗(非常重要)
		if (earlySingletonExposure) {
				// 前面講到因為自己被循環依賴了,所以此時候代理對象還存放在二級緩存中
				// 因此,此處getSingleton(),就會把代理對象拿出來
				// 然后賦值給exposedObject對象並返回,最終被addSingleton()添加進一級緩存中
				// 這樣就保證了我們容器里緩存的對象實際上是代理對象,而非原始對象

				Object earlySingletonReference = getSingleton(beanName, false);
				if (earlySingletonReference != null) {
	
						// 這個判斷不可少(因為initializeBean()方法中給exposedObject對象重新賦過值,否則就是是兩個不同的對象實例)
						if (exposedObject == bean) { 				
								exposedObject = earlySingletonReference;
						}
				}
				...
		}
	
}

以上代碼分析的是代理對象有自己存在循環依賴的情況,Spring用三級緩存很巧妙的進行解決了這個問題。

4.2 非循環依賴下的代理對象創建過程

如果自己並不存在循環依賴的情況,Spring的處理過程就稍微不同,繼續跟進源碼:


protected Object doCreateBean( ... ) {
		...

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

		...
	
		// 此處注意,因為沒有循環引用,所以上面getEarlyBeanReference()方法不會執行
		// 也就是說此時二級緩存里並不會存在
		populateBean(beanName, mbd, instanceWrapper);

		// 重點在此
		//AnnotationAwareAspectJAutoProxyCreator自動代理創建器此處的postProcessAfterInitialization()方法里,會給創建一個代理對象返回
		// 所以此部分執行完成后,exposedObject() 容器中緩存的已經是代理對象,不再是原始對象
	 // 此時二級緩存里依舊無它,更別提一級緩存了
	 exposedObject = initializeBean(beanName, exposedObject, mbd);

		...
	
		// 循環依賴校驗
		if (earlySingletonExposure) {
				// 前面講到一級、二級緩存里都沒有緩存,然后這里傳參數是false,表示不從三級緩存中取值
				// 因此,此時earlySingletonReference = null ,並直接返回

				// 然后執行addSingleton()方法,由此可知,容器里最終存在的也還是代理對象

				Object earlySingletonReference = getSingleton(beanName, false);
				if (earlySingletonReference != null) {
						if (exposedObject == bean) { 
								exposedObject = earlySingletonReference;
						}
				}
			 ...
}

根據以上代碼分析可知,只要用到代理,沒有被循環引用的,最終存在Spring容器里緩存的仍舊是代理對象。如果我們關閉Spring容器的循環依賴,也就是把allowCircularReferences設值為false,那么會不會出現問題呢?先關閉循環依賴開關。


// 它用於關閉循環引用(關閉后只要有循環引用現象將報錯)
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(false);

    }
}

關閉循環依賴后,上面代碼中存在A、B循環依賴的情況,運行程序會出現如下異常:


Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
	

此處異常類型也是BeanCurrentlyInCreationException異常,但報錯位置在DefaultSingletonBeanRegistry.beforeSingletonCreation

我們來分析一下,在實例化A后給其屬性賦值時,Spring會去實例化B。B實例化完成后會繼續給B屬性賦值,由於我們關閉了循環依賴,所以不存在提前暴露引用。因此B無法直接拿到A的引用地址,只能又去創建A的實例。而此時我們知道A其實已經正在創建中了,不能再創建了。所有就出現了異常。對照演示代碼,來分析一下程序運行過程:


@Service
public class MyServiceImpl implements MyService {

	// 因為關閉了循環依賴,所以此處不能再依賴自己
	// 但是MyService需要創建AOP代理對象
    //@Autowired
    //private MyService myService;
    
    @Transactional
    @Override
    public Object hello(Integer id) {
        return "service hello";
    }
}

其大致運行步驟如下:


protected Object doCreateBean( ... ) {

		// earlySingletonExposure = false  也就是Bean都不會提前暴露引用,因此不能被循環依賴

		boolean earlySingletonExposure = (mbd.isSingleton() && 
													this.allowCircularReferences && 
													isSingletonCurrentlyInCreation(beanName));
		...

		populateBean(beanName, mbd, instanceWrapper);

		// 若是開啟事務,此處會為原生Bean創建代理對象
		exposedObject = initializeBean(beanName, exposedObject, mbd);

		if (earlySingletonExposure) {
				... 

				// 因為上面沒有提前暴露代理對象,所以上面的代理對象exposedObject直接返回。

		}
}

由上面代碼可知,即使關閉循環依賴開關,最終緩存到容器中的對象仍舊是代理對象,顯然@Autowired給屬性賦值的也一定是代理對象。

最后,以AbstractAutoProxyCreator為例看看自動代理創建器實現循環依賴代理對象的細節。

AbstractAutoProxyCreator是抽象類,它的三大實現子類InfrastructureAdvisorAutoProxyCreator、AspectJAwareAdvisorAutoProxyCreator、AnnotationAwareAspectJAutoProxyCreator小伙伴們應該比較熟悉,該抽象類實現了創建代理的動作:


// 該類實現了SmartInstantiationAwareBeanPostProcessor接口 ,通過getEarlyBeanReference()方法解決循環引用問題

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

	...

	// 下面兩個方法是自動代理創建器創建代理對象的唯二的兩個節點:

	// 提前暴露代理對象的引用,在postProcessAfterInitialization之前執行
	// 創建好后放進緩存earlyProxyReferences中,注意此處value是原始Bean

	@Override
	public Object getEarlyBeanReference(Object bean, String beanName) {

			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			this.earlyProxyReferences.put(cacheKey, bean);
			return wrapIfNecessary(bean, beanName, cacheKey);

	}

	// 因為它會在getEarlyBeanReference之后執行,這個方法最重要的是下面的邏輯判斷
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {

		if (bean != null) {

			Object cacheKey = getCacheKey(bean.getClass(), beanName);

			// 下面的remove()方法返回被移除的value,也就是原始Bean
			// 判斷如果存在循環引用,也就是執行了上面的getEarlyBeanReference()方法,
		    // 此時remove() 返回值肯定是原始對象
			
		    // 若沒有被循環引用,getEarlyBeanReference()不執行
			// 所以remove() 方法返回null,此時進入if執行邏輯,調用創建代理對象方法
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
					return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}

		return bean;

	}
	...
}

根據以上分析可得知,自動代理創建器它保證了代理對象只會被創建一次,而且支持循環依賴的自動注入的依舊是代理對象。由上面分析得出結論,在Spring容器中,不論是否存在循環依賴的情況,甚至關閉Spring容器的循環依賴功能,它對Spring AOP代理的創建流程有影響,但對結果是無影響的。也就是說Spring很好地屏蔽了容器中對象的創建細節,讓使用者完全無感知。

【推薦】Tom彈架構:收藏本文,相當於收藏一本“設計模式”的書

本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM