以下内容基于 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;
}
}
運作時報錯如下:
原因分析:
上篇文章我們說解決循環依賴的思路是加入緩存,如下圖:
我們說先把 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;
}
這種循環依賴運作時也會報錯,報錯資訊如下(跟前面報錯資訊一樣):
原因分析:
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;
}
報錯資訊如下:
其實大家從這段報錯資訊中也能看出來個七七八八:在 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。
大家先把這兩點搞清楚,然後我來跟大家說上面代碼的執行流程。
- 首先 AService 初始化,初始化完成之後,存入到三級緩存中。
- 執行 populateBean 方法進行 AService 的屬性填充,填充時發現需要用到 BService,于是就去初始化 BService。
- 初始化 BService 發現需要用到 AService,于是就去緩存池中找,找到之後拿來用,但是!!!這裡找到的 AService 不是代理對象,而是原始對象。因為在三級緩存中儲存的 AService 的那個 ObjectFactory 工廠,在對 AService 進行提前 AOP 的時候,執行的是 SmartInstantiationAwareBeanPostProcessor 類型的後置處理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,調用 getEarlyBeanReference 方法最終會觸發提前 AOP,但是,這裡執行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,該方法隻是傳回了原始的 Bean,并未做任何額外處理。
- 當 BService 建立完成後,AService 繼續初始化,繼續執行 initializeBean 方法。
- 在 initializeBean 方法中,執行其他的各種後置處理器,包括 AsyncAnnotationBeanPostProcessor,此時調用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在該方法中為 AService 生成了代理對象。
- 在 initializeBean 方法執行完成之後,AService 會繼續去檢查最終的 Bean 是不是還是一開始的 Bean,如果不是,就去檢查目前 Bean 有沒有被其他 Bean 引用過,如果被引用過,就會抛出來異常,也就是上圖大家看到的異常資訊。
好啦,這就是松哥和大家分享的三種 Spring 預設無法解決的循環依賴,其實也不是無法解決,需要一些額外配置也能解決,當然,這些額外配置并非本文重點,松哥後面再來和大家介紹~
另外最近兩篇關于循環依賴的文章都還沒有涉及到源碼分析,大家先把思路整清楚,後面松哥再出源碼分析的文章~