1 什麼是循環依賴?
循環依賴就是指循環引用,是兩個或多個Bean互相之間的持有對方的引用。
如下圖所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yYlRDMjRDZiFjMmJWN5ITO1ITZ0YDN1YTYhVWYzMGN18CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
BeanA 類依賴了Bean B類,同時Bean B類又依賴了Bean A類。這種依賴關系形成了一個閉環,我們把這種依賴關系就稱之為循環依賴。同理,再如下圖的情況:
上圖中,Bean A類依賴了Bean B類,Bean B類依賴了Bean C類,Bean C類依賴了Bean A類,如此,也形成了一個依賴閉環。再比如:
上圖中,自己引用了自己,自己和自己形成了依賴關系。同樣也是一個依賴閉環。那麼,如果出現此類循環依賴的情況,會出現什麼問題呢?
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容器裡面的情況,如下圖:
啟動項目,我們發現隻要是有循環依賴關系的屬性并沒有自動指派,而沒有循環依賴關系的屬性均有自動指派,如下圖所示:
這種情況是怎麼造成的呢?我們分析原因之後發現,因為,IoC容器對Bean的初始化是根據BeanDefinition循環疊代,有一定的順序。這樣,在執行依賴注入時,需要自動指派的屬性對應的對象有可能還沒初始化,沒有初始化也就沒有對應的執行個體可以注入。于是,就出現我們看到的情況。
3 使用緩存解決循環依賴問題
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彈架構 』回複“Spring”可擷取完整源碼。
本文為“Tom彈架構”原創,轉載請注明出處。技術在于分享,我分享我快樂!如果您有任何建議也可留言評論或私信,您的支援是我堅持創作的動力。關注微信公衆号『 Tom彈架構 』可擷取更多技術幹貨!
原創不易,堅持很酷,都看到這裡了,小夥伴記得點贊、收藏、在看,一鍵三連加關注!如果你覺得内容太幹,可以分享轉發給朋友滋潤滋潤!