一篇文章徹底搞定Spring循環依賴
lecture:波哥
一、什麼是循環依賴
看下圖:
上圖是循環依賴的三種情況,雖然方式有點不一樣,但是循環依賴的本質是一樣的,就你的完整建立要依賴與我,我的完整建立也依賴于你。互相依賴進而沒法完整建立造成失敗。
二、構造注入
1. 案例示範
構造注入就是依賴關系在構造方法中關聯。
@Component
@Scope(value = "prototype")
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
}
@Component
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA) {
this.beanA = beanA;
}
}
然後我們可以通過啟動容器來檢視,在Spring中構造注入的
循環依賴
在Spring中是沒有辦法解決的。隻是通過抛出異常來處理了。
@Configuration
@ComponentScan
public class Demo1Main {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(Demo1Main.class);
}
}
2.解決方案
那麼在Spring中是如何發現執行個體的Bean對象有
循環依賴
的問題,并抛出異常的呢?其實實作邏輯也比較簡單,如下圖:
實作的過程也很簡單,就是在建立對象之前判斷該對象在這個臨時容器中是否存在,如果不存在就開始建立,如果存在則說明産生了
循環依賴
。對象建立完成後就把該對象從容器中移除。對應的看看Spring的源碼中的處理。
3.單例分析
先來看單例的處理,進入到
getSingleton
方法中。我們需要關注的是兩個方法
beforeSingletonCreation(beanName)
和
afterSingletonCreation(beanName);
beforeSingletonCreation方法
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
上面的代碼很清楚的看到,如果已經存在就抛出循環依賴的錯誤。而且因為是單例,是以容器也沒有通過
ThreadLocal
來處理了。
對應的
afterSingletonCreation
方法就是移走了。
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
4.原型分析
原型Bean思路差不多,但是實作方式差別有點大,我們先來看看
beforePrototypeCreation(beanName)
和
afterPrototypeCreation(beanName);
這兩個方法。
// 資料存儲在了 ThreadLocal 中
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
可以看到這兩個方法都隻是在
ThreadLocal
中錯了BeanName的寫入和移除的操作。并沒有做相關的檢查,而這個檢查其實是在
這樣一來Spring中的構造注入的循環依賴針對單例或是原型的處理我們就清楚了!
三、依賴注入
1.案例示範
設值注入就是通過setter方法來完成屬性的指派。
@Component
public class UserD {
@Autowired
private UserC userC;
}
@Component
public class UserC {
@Autowired
private UserD userD;
}
寫主方法測試
@Configuration
@ComponentScan
public class Demo2Main {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(Demo2Main.class);
System.out.println(ac.getBean(UserC.class));
}
}
我們可以看到在單例的情況下,Spring解決了
循環依賴
的問題了。
但是在兩個對象都是原型的情況下就解決不了了。
@Component
@Scope(value = "prototype")
public class UserC {
@Autowired
private UserD userD;
}
@Component
@Scope(value = "prototype")
public class UserD {
@Autowired
private UserC userC;
}
啟動測試我們可以看到出現了循環依賴的錯誤提示了
當然,如果我們給其中的一個設定為單例,那麼循環依賴問題即可解決。原理,我們分析下就清楚了。
2.解決方案
針對依賴注入這種情況下産生的
循環依賴
問題,我們的解決方案是
提前暴露
。
但是在Spring中對于Bean執行個體因為AOP的存在,我們可能傳回的并不是原始的Bean對象,而是被AOP增強的代理對象,這時在Spring中需要使用到三級緩存來處理。我們具體來看看Spring中的處理方案。
3.原理分析
然後我們來具體看看在Spring中對于依賴注入的
循環依賴
的解決。進入到
AbstractAutowireCapableBeanFactory
的
doCreateBean
方法中。
上面看到了具體建立Bean對象的方法
createBeanInstance
方法中,然後在下面中比較重要的兩個的方法
那麼對于的
提前暴露
應該要在
populateBean
方法之前實作。往前面找看到了
earlySingletonExposure
變量,辨別的就是是否支援
提前暴露
。
// 判斷條件: 1.是否是單例Bean 2.是否支援循環依賴 3.是否是目前正在建立的Bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
如果上面的條件成立就會走下面的代碼
if (earlySingletonExposure) {
// 把 Lambda 表達式存儲在了三級緩存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 對應的 addSingletonFactory 方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 該單例對象還未建立過
if (!this.singletonObjects.containsKey(beanName)) {
// 把 上面的 Lambda 表達式 存儲在了 三級緩存中
this.singletonFactories.put(beanName, singletonFactory);
// 對應的二級緩存置空
this.earlySingletonObjects.remove(beanName);
// 記錄該單例對象名稱
this.registeredSingletons.add(beanName);
}
}
}
然後我們需要在AOP建立對應的代理增強對象之後再看是如果來暴露代理對象的。
也就是我們看的關鍵是
getSingleton
方法
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中擷取對象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 沒有擷取到建立好的Bean單例,同時目前正在建立Bean對象
// 從 二級緩存中擷取 半層品 對象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 二級緩存中沒有需要擷取的Bean 半成品對象
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 一級緩存中沒有
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 二級緩存 沒有 就取出之前存儲在三級緩存中的 Lambda 表達式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 根據Lambda表達式 擷取到Bean的 代理對象
singletonObject = singletonFactory.getObject();
// 把Bean的半成品對象存儲在二級緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 清除 一級緩存的 對象
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
// 傳回對應的半成品對象
return singletonObject;
}
然後對應的擷取代理對象的邏輯是在
singletonFactory.getObject()
中。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean; // 原始Bean
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 通過每個BeanPostProcessor 對Bean對象做後置處理
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 遞歸擷取對應的 代理對象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
// 傳回 對應的增強Bean
return exposedObject;
}
具體邏輯如下圖:
當依賴關系完成後。會進入到前面的
getSingleton
方法中。
然後還要做對應的二級緩存和一級緩存資料處理
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 建立好的單例Bean會存儲在 singletonObjects 這個Map 中,也就是一級緩存中
this.singletonObjects.put(beanName, singletonObject);
// 移除三級緩存的Lambda表達式
this.singletonFactories.remove(beanName);
// 移除二級緩存中的Bean的半成品對象,移除提前暴露的對象
this.earlySingletonObjects.remove(beanName);
// 注冊 單例
this.registeredSingletons.add(beanName);
}
}