天天看點

Spring 能解決所有循環依賴嗎?

作者:江南一點雨

以下内容基于 Spring6.0.4。

看了上篇文章的小夥伴,對于 Spring 解決循環依賴的思路應該有一個大緻了解了,今天我們再來看一看,按照上篇文章介紹的思路,有哪些循環依賴 Spring 處理不了。

嚴格來說,其實也不是解決不了,所有問題都有辦法解決,隻是還需要額外配置,這個不是本文的主題,松哥後面再整文章和小夥伴們細聊。

1. 基于構造器注入

如果依賴的對象是基于構造器注入的,那麼執行的時候就會報錯,代碼如下:

@Service
public class AService {
    BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}
@Service
public class BService {
    AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}
           

運作時報錯如下:

Spring 能解決所有循環依賴嗎?

原因分析:

上篇文章我們說解決循環依賴的思路是加入緩存,如下圖:

Spring 能解決所有循環依賴嗎?

我們說先把 AService 原始對象建立出來,存入到緩存池中,然後再處理 AService 中需要注入的外部 Bean 等等,但是,如果 AService 依賴的 BService 是通過構造器注入的,那就會導緻在建立 AService 原始對象的時候就需要用到 BService,去建立 BService 時候又需要 AService,這樣就陷入到死循環了,對于這樣的循環依賴執行時候就會出錯。

更進一步,如果我們在 AService 中是通過 @Autowired 來注入 BService 的,那麼應該是可以運作的,代碼如下:

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}
           

上面這段代碼,AService 的原始對象就可以順利建立出來放到緩存池中,BService 建立所需的 AService 也就能從緩存中擷取到,是以就可以執行了。

2. prototype 對象

循環依賴雙方 scope 都是 prototype 的話,也會循環依賴失敗,代碼如下:

@Service
@Scope("prototype")
public class AService {
    @Autowired
    BService bService;
}
@Service
@Scope("prototype")
public class BService {
    @Autowired
    AService aService;
}
           

這種循環依賴運作時也會報錯,報錯資訊如下(跟前面報錯資訊一樣):

Spring 能解決所有循環依賴嗎?

原因分析:

scope 為 prototype 意思就是說這個 Bean 每次需要的時候都現場建立,不用緩存裡的。那麼 AService 需要 BService,是以就去現場建立 BService,結果 BService 又需要 AService,繼續現場建立,AService 又需要 BService...,是以最終就陷入到死循環了。

3. @Async

帶有 @Async 注解的 Bean 産生循環依賴,代碼如下:

@Service
public class AService {
    @Autowired
    BService bService;

    @Async
    public void hello() {

    }
}
@Service
public class BService {
    @Autowired
    AService aService;

}
           

報錯資訊如下:

Spring 能解決所有循環依賴嗎?

其實大家從這段報錯資訊中也能看出來個七七八八:在 BService 中注入了 AService 的原始對象,但是 AService 在後續的處理流程中被 AOP 代理了,産生了新的對象,導緻 BService 中的 AService 并不是最終的 AService,是以就出錯了!

那有小夥伴要問了,上篇文章我們不是說了三級緩存就是為了解決 AOP 問題嗎,為什麼這裡發生了 AOP 卻無法解決?

如下兩個前置知識大家先了解一下:

第一:

其實大部分的 AOP 循環依賴是沒有問題的,這個 @Async 隻是一個特例,特别在哪裡呢?一般的 AOP 都是由 AbstractAutoProxyCreator 這個後置處理器來處理的,通過這個後置處理器生成代理對象,AbstractAutoProxyCreator 後置處理器是 SmartInstantiationAwareBeanPostProcessor 接口的子類,并且 AbstractAutoProxyCreator 後置處理器重寫了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法;而 @Async 是由 AsyncAnnotationBeanPostProcessor 來生成代理對象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子類,但是卻沒有重寫 getEarlyBeanReference 方法,預設情況下,getEarlyBeanReference 方法就是将傳進來的 Bean 原封不動的傳回去。

第二:

在 Bean 初始化的時候,Bean 建立完成後,後面會執行兩個方法:

  • populateBean:這個方法是用來做屬性填充的。
  • initializeBean:這個方法是用來初始化 Bean 的執行個體,執行工廠回調、init 方法以及各種 BeanPostProcessor。

大家先把這兩點搞清楚,然後我來跟大家說上面代碼的執行流程。

  1. 首先 AService 初始化,初始化完成之後,存入到三級緩存中。
  2. 執行 populateBean 方法進行 AService 的屬性填充,填充時發現需要用到 BService,于是就去初始化 BService。
  3. 初始化 BService 發現需要用到 AService,于是就去緩存池中找,找到之後拿來用,但是!!!這裡找到的 AService 不是代理對象,而是原始對象。因為在三級緩存中儲存的 AService 的那個 ObjectFactory 工廠,在對 AService 進行提前 AOP 的時候,執行的是 SmartInstantiationAwareBeanPostProcessor 類型的後置處理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,調用 getEarlyBeanReference 方法最終會觸發提前 AOP,但是,這裡執行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,該方法隻是傳回了原始的 Bean,并未做任何額外處理。
  4. 當 BService 建立完成後,AService 繼續初始化,繼續執行 initializeBean 方法。
  5. 在 initializeBean 方法中,執行其他的各種後置處理器,包括 AsyncAnnotationBeanPostProcessor,此時調用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在該方法中為 AService 生成了代理對象。
  6. 在 initializeBean 方法執行完成之後,AService 會繼續去檢查最終的 Bean 是不是還是一開始的 Bean,如果不是,就去檢查目前 Bean 有沒有被其他 Bean 引用過,如果被引用過,就會抛出來異常,也就是上圖大家看到的異常資訊。

好啦,這就是松哥和大家分享的三種 Spring 預設無法解決的循環依賴,其實也不是無法解決,需要一些額外配置也能解決,當然,這些額外配置并非本文重點,松哥後面再來和大家介紹~

另外最近兩篇關于循環依賴的文章都還沒有涉及到源碼分析,大家先把思路整清楚,後面松哥再出源碼分析的文章~