Spring 迴圈依賴及解決方式
迴圈依賴
Spring 有一個經典的問題,就是如何解決迴圈依賴,話不多說,直接開始,
@Component
public Class A {
@Autowired private B b;
}
@Component
public Class B {
@Autowired private A b;
}
spring bean 的生命週期
獲取一個 Bean 的操作從 getBean(String name) 開始主要步驟為
1、getBean(String name)
2、例項化物件 A a = new A(); 此時執行構造方法的依賴注入
3、設定物件屬性 populateBean(beanName, mbd, instanceWrapper); 此時執行屬性的依賴注入
4、執行初始化方法 initializeBean(beanName, exposedObject, mbd); 此時執行 bean 的 initialize 方法
5、將生成好的 bean 物件新增到 單例池(一個 hashMap,保證單例 bean 在 context 僅僅存在一個物件)
6、結束
虛擬碼如下:
public Object getBean(String name) {
//省略根據name獲取A的過程
A a = new A();
a.initialze();
singletonObjects.put(name, a);
return a;
}
A 依賴 B 的情況下的載入流程
虛擬碼如下:
public Object getBean(String name) {
//省略根據name獲取A的過程
A a = new A(); //例項化A
a.setB(getBean("B")); //設定屬性,發現a依賴於b,所以先載入b,載入B完成以後再繼續載入a
a.initialze(); //執行初始化方法
singletonObjects.put(name, a); //將a放入單例池中
return a;
}
A、B 互相依賴的載入流程
以上就會出現一個問題,由於 a、b 都是單例 Bean,載入 b 的時候,到了上圖中標紅的階段後,b 依賴注入的 a 的引用應該是通過 getBean(A) 得到的引入,如果還是以上的邏輯,又再一次走入了 A 的建立邏輯,此時就是發生了迴圈依賴。下面我們就開始介紹 Spring 是如何解決迴圈依賴的。
一級快取:單例池 singletonObjects
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
我們都知道如果是單例的 Bean,每次 getBean(beanName)返回同一個 bean,也就是在整個 ApplicationContext 裡面,僅有一個單例 Bean,單例 Bean 建立完成後就放在 singletonObjects 這個 Map 裡面,這就是一級快取。此時說的“建立完成”指的是圖一的第 6 步驟,圖三中 getBean("B") 的過程中,a 是沒有加入到一級快取中,所以在 getBean("B") 的流程中,b 依賴了 a,此時 b 是找不到 a 物件的。依然會無法解決迴圈引用的問題。
二級快取:earlySingletonObjects
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
這個時候我們考慮再引入一個 Map 存放引用,earlySingletonObjects 這個 map 我們打算存放提前暴露 bean 的引用,例項化以後,我們就把物件放入到 earlySingletonObjects 這個 map 中,這樣在 載入 b 的過程中,b.setA(getBean("a")),我們就可以在 earlySingletonObjects 拿到 a 的引用,此時 a 僅僅經過了例項化,並沒有設定屬性。流程如下:
1、getBean(A)
2、A a = new A();
3、earlySingletonObjects.put("a", a); 將 A 放入二級快取
3、設定 A 的屬性
4、getBean(B)
5、設定 B 的屬性,發現 B 依賴 A,從二級快取中獲取 A
6、載入 B 成功
7、將 B 放入一級快取
8、繼續載入 A
9、載入 A 完成,將 A 放入單例池
到目前為止,發現使用二級快取似乎就能解決我們的問題。看起來很美好,這是 Spring IOC 的特性,Spring 的另一大特性是 AOP 面向切面程式設計,動態增強物件,不管使用 JDK 的動態代理和 Cglib 動態代理,都會生成一個全新的物件。下圖中我標出了 AOP 動態增強的位置。
此時就會出現一個問題,因為經過 AOP 以後,生成的是增強後的 bean 物件,也就是一個全新的物件,java培訓我們可以看到經過圖中的流程後,單例池中會存在兩個 bean:增強後的 a、b 物件,此時 a 物件中依賴的 b 為增強後的,而 b 物件依賴的 a 是為原始物件,未增強的。所以使用二級快取解決不了迴圈依賴中發生過 aop 的引用問題。
三級快取:singletonFactories
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
為了解決二級快取中 AOP 生成新物件的問題,Spring 中的解決方案是:提前 AOP,如果我們能夠提前 AOP 就能解決上面的問題了,提前 AOP 指的就是,在 載入 B 的流程中,如果發生了迴圈依賴,就是說 b 又依賴了 a,我們就要對 a 執行 aop,提前獲取增強以後的 a 物件,這樣 b 物件依賴的 a 物件就是增強以後的 a 了。三級快取的 key 是 beanName,value 是一個 lambda 表示式,這個 lambda 表示式的作用就是進行提前 AOP。
下面是加入了三級快取和 AOP 的流程圖,PS:可能會有點亂。。。。。。
上面就是三級快取的作用,其中有個三級快取到二級快取的升級過程,這個非常重重要,這個主要是防止重複 aop。好的,寫到這裡,我們對 Spring 如何使用三級快取解決迴圈依賴的流程已經大概清楚了,下面分析一下原始碼。
原始碼解析:
1、 doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
Object bean;
//首先先嚐試獲取bean,如果載入過就不會在重複載入了
Object sharedInstance = getSingleton(beanName);
//省略細節
if(sharedInstance != null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
//根據beanName獲取 beanDefinition 物件
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
if (mbd.isSingleton()) {
//單例bean的載入邏輯
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
else if (mbd.isPrototype()) {
//原型域bean的載入邏輯
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
return (T)bean;
}
2、 第 1 步中 getSingleton(beanName)
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先去一級快取中獲取如果獲取的到說明bean已經存在,直接返回
Object singletonObject = this.singletonObjects.get(beanName);
//如果一級快取中不存在,則去判斷該bean是否在建立中,如果該bean正在建立中,就說明了,這個時候發生了迴圈依賴
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//如果發生迴圈依賴,首先去二級快取中獲取,如果獲取到則返回,這個地方就是獲取aop增強以後的bean
singletonObject = this.earlySingletonObjects.get(beanName);
//如果二級快取中不存在,且允許提前訪問三級引用
if (singletonObject == null && allowEarlyReference) {
//去三級快取中獲取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//如果三級快取中的lambda表示式存在,執行aop,獲取增強以後的物件,為了防止重複aop,將三級快取刪除,升級到二級快取中
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
3、 第 1 步中 單例 bean 的載入邏輯
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
//獲取bean
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//將當前bean加入到 singletonsCurrentlyInCreation 這個map中,這個map裡面是正在建立中的bean,用於判斷迴圈依賴
beforeSingletonCreation(beanName);
//執行上面方法的lambda表示式,建立bean
singletonObject = singletonFactory.getObject();
//將 singletonsCurrentlyInCreation 裡面的這個bean刪除
afterSingletonCreation(beanName);
//bean建立完成,將bean加入到單例池中
addSingleton(beanName, singletonObject);
}
return singletonObject;
}
}
4、核心方法,載入 bean
//createBean(beanName, mbd, args); 方法 建立bean的核心邏輯
// 最終呼叫的是 AbstractAutowiredCapableBeanFactory.createBean 這個方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//例項化,操作等同於 new 一個bean物件
final Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
//是否允許提前暴露物件,如果當前bean為單例,且允許迴圈引用,與當前bean正在建立中,則允許提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
//將當前bean放入三級快取中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//開始設定屬性,當前bean依賴於其它bean,則需要 doGetBean 建立以來的bean,如果依賴的bean不存在,則首先建立依賴的bean,迴圈依賴發生的位置
populateBean(beanName, mbd, instanceWrapper);
//執行初始化方法和aop增強,此時如果有aop,exposedObject就是增強以後的物件了,但是有一點需要注意,如果提前執行了aop,則exposedObject不會再次執行aop了
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
throw ex;
}
if (earlySingletonExposure) {
//這個我們可以看上面getSingleton方法,該方法的引數為false,就說明只允許去一級快取和二級快取獲取,此時bean在建立中,一級快取一定沒有,就看二級快取能不能獲取到了
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//如果二級快取獲取到了,則說明提前執行了aop
if (exposedObject == bean) {
//exposedObject就是要放入單例池中的物件,如果提前執行了aop,則將exposedObject物件替換為aop以後的物件
//這個地方可能有一些疑問,exposedObject是原始物件執行過 依賴注入,而earlySingletonReference是提前執行aop的物件,沒有執行過依賴注入,是不是有什麼問題呢?
//答案是不會,因為earlySingletonReference作為exposedObject的增強物件,內部是持有原物件的引用的
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
//下面我們看一下 上面方法中的 將bean放入三級快取提前暴露的方法
//addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
通過對上面原始碼的解析,看到了 bean 載入的整個生命週期,和三級快取的作用。
比如:
1、如何判斷是否存在迴圈依賴:使用 isSingletonCurrentlyInCreation(beanName) 這個方法
2、三級快取如何升級到二級快取的:參考第二步
3、提前執行 AOP:這個東西是在 獲取三級快取的時候執行的,裡面有一個 getEarlyBeanReference(beanName, mbd, bean) 這個 lambda 表示式,這個方法就是提前執行 aop,具體可以參考 AbstractAutoProxyCreator
4、如果提前執行 AOP,則需要替換原物件
文字總結 A、B 迴圈依賴
1、getBean(A) 先去單例池獲取,單例池不存在,二級快取獲取,二級快取不存在且允許提前訪問,三級快取中取,此時返回為空,開始載入 A
2、singletonsCurrentlyInCreation(A) 將 A 放入正在建立的 Map 中
3、new A(); 例項化 A
4、提前暴露 A,將 A 放入三級快取,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
5、設定屬性 populateBean(beanName, mbd, instanceWrapper);
6、發現 A 依賴 B,需要先建立 B
7、getBean(B)
8、先去單例池獲取 B,單例池不存在,二級快取獲取,二級快取不存在且允許提前訪問,三級快取中取,此時返回為空,開始載入 B
9、將 B 放入 singletonsCurrentlyInCreation() 的 Map 中
10、new B() 例項化 B
11、將 B 放入三級快取 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
12、設定屬性 populateBean(beanName, mbd, instanceWrapper);
13、發現 B 依賴 A
14、getBean(A)
15、發現三級快取中存在 A,getEarlyBeanReference(A, mbd, bean) 獲取 A,同時把 A 放入二級快取,刪除三級快取
16、執行 B 的 initializeBean 方法,執行 aop,獲取增強以後的引用
17、B 建立完了,將 B 放入單例池衝
18、繼續執行第 7 步,返回的 getBean(B)就是建立好的 B
19、接下來 A 初始化
20、因為 A 的三級快取中的 getEarlyBeanReference(beanName, mbd, bean) 被 B 已經執行過了
21、A 就能從二級快取中獲取自己的引用
22、如果發現引用變了,此時 A 就指向二級快取中的引用
23、將 A 放出單例池中
24、刪除二級快取和三級快取