Spring原始碼分析之迴圈依賴及解決方案
阿新 • • 發佈:2020-11-16
# Spring原始碼分析之迴圈依賴及解決方案
往期文章:
1. [Spring原始碼分析之預啟動流程](https://mp.weixin.qq.com/s/bfbPJOlYo2Vz2UTSMWRGkw)
2. [Spring原始碼分析之BeanFactory體系結構](https://mp.weixin.qq.com/s/FDx0hmCp7dEfw5wzhS3fNA)
3. [Spring原始碼分析之BeanFactoryPostProcessor呼叫過程詳解](https://mp.weixin.qq.com/s/gHL6Q0A0xwxSCZ0_hKJhEQ)
4. [Spring原始碼分析之Bean的建立過程詳解](https://mp.weixin.qq.com/s/MLOJzJRFNrEjTqAlr3YCSw)
正文:
首先,我們需要明白什麼是迴圈依賴?簡單來說就是A物件建立過程中需要依賴B物件,而B物件建立過程中同樣也需要A物件,所以A建立時需要先去把B創建出來,但B建立時又要先把A創建出來...死迴圈有木有...
![迴圈依賴](https://img2020.cnblogs.com/other/1187061/202011/1187061-20201116112933811-1778032139.png)
那麼在Spring中,有多少種迴圈依賴的情況呢?大部分人只知道兩個普通的Bean之間的迴圈依賴,而Spring中其實存在三種物件(普通Bean,工廠Bean,代理物件),他們之間都會存在迴圈依賴,這裡我給列舉出來,大致分別以下幾種:
- 普通Bean與普通Bean之間
- 普通Bean與代理物件之間
- 代理物件與代理物件之間
- 普通Bean與工廠Bean之間
- 工廠Bean與工廠Bean之間
- 工廠Bean與代理物件之間
那麼,在Spring中是如何解決這個問題的呢?
## 1. 普通Bean與普通Bean
首先,我們先設想一下,如果讓我們自己來編碼,我們會如何解決這個問題?
### 栗子
現在我們有兩個互相依賴的物件A和B
```java
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {
this.normalBeanB = normalBeanB;
}
}
```
```java
public class NormalBeanB {
private NormalBeanA normalBeanA;
public void setNormalBeanA(NormalBeanA normalBeanA) {
this.normalBeanA = normalBeanA;
}
}
```
然後我們想要讓他們彼此都含有物件
```java
public class Main {
public static void main(String[] args) {
// 先建立A物件
NormalBeanA normalBeanA = new NormalBeanA();
// 建立B物件
NormalBeanB normalBeanB = new NormalBeanB();
// 將A物件的引用賦給B
normalBeanB.setNormalBeanA(normalBeanA);
// 再將B賦給A
normalBeanA.setNormalBeanB(normalBeanB);
}
}
```
發現了嗎?我們並沒有先建立一個完整的A物件,而是先建立了一個空殼物件(Spring中稱為早期物件),將這個早期物件A先賦給了B,使得得到了一個完整的B物件,再將這個完整的B物件賦給A,從而解決了這個迴圈依賴問題,so easy!
那麼Spring中是不是也這樣做的呢?我們就來看看吧~
### Spring中的解決方案
> 由於上一篇已經分析過Bean的建立過程了,其中的某些部分就不再細講了
#### 先來到建立Bean的方法
> AbstractAutowireCapableBeanFactory#doCreateBean
假設此時在建立A
```java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
// beanName -> A
// 例項化A
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 是否允許暴露早期物件
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 將獲取早期物件的回撥方法放到三級快取中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
}
```
addSingletonFactory
```java
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
synchronized (this.singletonObjects) {
// 單例快取池中沒有該Bean
if (!this.singletonObjects.containsKey(beanName)) {
// 將回調函式放入三級快取
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
```
> ObjectFactory是一個函式式介面
在這裡,我們發現在建立Bean時,Spring不管三七二十一,直接將一個獲取早期物件的回撥方法放進了一個三級快取中,我們再來看一下回調方法的邏輯
getEarlyBeanReference
```java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 呼叫BeanPostProcessor對早期物件進行處理,在Spring的內建處理器中,並無相關的處理邏輯
// 如果開啟了AOP,將引入一個AnnotationAwareAspectJAutoProxyCreator,此時將可能對Bean進行動態代理
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
```
> 在這裡,如果沒有開啟AOP,或者該物件不需要動態代理,會直接返回原物件
此時,已經將A的早期物件快取起來了,接下來在填充屬性時會發生什麼呢?
相信大家也應該想到了,A物件填充屬性時必然發現依賴了B物件,此時就將轉頭建立B,在建立B時同樣會經歷以上步驟,此時就該B物件填充屬性了,這時,又將要轉頭建立A,那麼,現在會有什麼不一樣的地方呢?我們看看getBean的邏輯吧
doGetBean
```java
pr