根據之前解析的循環依賴的源碼, 分析了一級緩存,二級緩存,三級緩存的作用以及如何解決循環依賴的. 然而在多線程的情況下, Spring在建立bean的過程中, 可能會讀取到不完整的bean. 下面, 我們就來研究兩點:
1. 為什麼會讀取到不完整的bean.
2. 如何解決讀取到不完整bean的問題.
和本文相關的spring循環依賴的前兩篇博文如下:
3.1 spring5源碼系列--循環依賴 之 手寫代碼模拟spring循環依賴
3.2spring源碼系列----循環依賴源碼分析
一. 為什麼會讀取到不完整的bean.
我們知道, 如果spring容器已經加載完了, 那麼肯定所有bean都是完整的了, 但如果, spring沒有加載完, 在加載的過程中, 建構bean就有可能出現不完整bean的情況
如下所示:
首先, 有一個線程要去建立A類, 調用getBean(A),他會怎麼做呢?
第一步: 調用getSingleton()方法, 去緩存中取資料, 我們發現緩存中啥都沒有, 肯定傳回null.
第二步: 将其放入到正在建立集合中,标記目前bean A正在建立
第三步: 執行個體化bean
第四步: 将bean放到三級緩存中. 定義一個函數接口, 友善後面調用
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
第四步: 屬性指派. 在屬性指派的時候, 返現要加載類B,就在這個時候, 另一個線程也進來了, 要建立Bean A.
第五步: 線程2 建立bean ,也是先去調用getSinglton()從緩存中取, 一二級換粗中都沒有,但是三級緩存中卻是有的. 于是就調用動态代理, 去建立bean, 很顯然這時候建立的bean是不完整的. 然後将其放入到二級緩存中, 二級緩存裡的bean也是不完整的. 這就導緻了後面是用的bean可能都是不完整的. 詳細的分析上圖
二. 如何解決讀取到不完整bean的問題.
其實, 之是以出現這樣的問題, 原因就在于, 第一個bean還沒有被建立完, 第二個bean就開始了. 這是典型的并發問題.
針對這個問題, 其實,我們加鎖就可以了.
用自己手寫的代碼為例
第一: 将整個建立過程加一把鎖
/**
* 擷取bean, 根據beanName擷取
*/
public static Object getBean(String beanName) throws Exception {
// 增加一個出口. 判斷實體類是否已經被加載過了
Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
Object instanceBean;
synchronized (singletonObjects) {
// 标記bean正在建立
if (!singletonsCurrectlyInCreation.contains(beanName)) {
singletonsCurrectlyInCreation.add(beanName);
}
/**
* 第一步: 執行個體化
* 我們這裡是模拟, 采用反射的方式進行執行個體化. 調用的也是最簡單的無參構造函數
*/
RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
// 調用無參的構造函數進行執行個體化
instanceBean = beanClass.newInstance();
/**
* 第二步: 放入到三級緩存
* 每一次createBean都會将其放入到三級緩存中. getObject是一個鈎子方法. 在這裡不會被調用.
* 什麼時候被調用呢?
* 在getSingleton()從三級緩存中取資料, 調用建立動态代理的時候
*/
singletonFactories.put(beanName, new ObjectFactory() {
@Override
public Object getObject() throws BeansException {
return new JdkProxyBeanPostProcessor().getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);
}
});
//earlySingletonObjects.put(beanName, instanceBean);
/**
* 第三步: 屬性指派
* instanceA這類類裡面有一個屬性, InstanceB. 是以, 先拿到 instanceB, 然後在判斷屬性頭上有沒有Autowired注解.
* 注意: 這裡我們隻是判斷有沒有Autowired注解. spring中還會判斷有沒有@Resource注解. @Resource注解還有兩種方式, 一種是name, 一種是type
*/
Field[] declaredFields = beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 判斷每一個屬性是否有@Autowired注解
Autowired annotation = declaredField.getAnnotation(Autowired.class);
if (annotation != null) {
// 設定這個屬性是可通路的
declaredField.setAccessible(true);
// 那麼這個時候還要建構這個屬性的bean.
/*
* 擷取屬性的名字
* 真實情況, spring這裡會判斷, 是根據名字, 還是類型, 還是構造函數來擷取類.
* 我們這裡模拟, 是以簡單一些, 直接根據名字擷取.
*/
String name = declaredField.getName();
/**
* 這樣, 在這裡我們就拿到了 instanceB 的 bean
*/
Object fileObject = getBean(name);
// 為屬性設定類型
declaredField.set(instanceBean, fileObject);
}
}
/**
* 第四步: 初始化
* 初始化就是設定類的init-method.這個可以設定也可以不設定. 我們這裡就不設定了
*/
/**
* 第五步: 放入到一級緩存
*
* 在這裡二級緩存存的是動态代理, 那麼一級緩存肯定也要存動态代理的執行個體.
* 從二級緩存中取出執行個體, 放入到一級緩存中
*/
if (earlySingletonObjects.containsKey(beanName)) {
instanceBean = earlySingletonObjects.get(beanName);
}
singletonObjects.put(beanName, instanceBean);
// 删除二級緩存
// 删除三級緩存
}
return instanceBean;
}
然後在從緩存取資料的getSingleton()上也加一把鎖
private static Object getSingleton(String beanName) {
//先去一級緩存裡拿,
Object bean = singletonObjects.get(beanName);
// 一級緩存中沒有, 但是正在建立的bean辨別中有, 說明是循環依賴
if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
synchronized (singletonObjects) {
bean = earlySingletonObjects.get(beanName);
// 如果二級緩存中沒有, 就從三級緩存中拿
if (bean == null) {
// 從三級緩存中取
ObjectFactory objectFactory = singletonFactories.get(beanName);
if (objectFactory != null) {
// 這裡是真正建立動态代理的地方.
bean = objectFactory.getObject();
// 然後将其放入到二級緩存中. 因為如果有多次依賴, 就去二級緩存中判斷. 已經有了就不在再次建立了
earlySingletonObjects.put(beanName, bean);
}
}
}
}
return bean;
}
加了兩把鎖.
這樣, 在分析一下
如上圖,線程B執行到getSingleton()的時候, 從一級緩存中取沒有, 到二級緩存的時候就加鎖了,他要等待直到線程A完成執行完才能進入. 這樣就避免出現不完整bean的情況.
三. 源碼解決
在建立執行個體bean的時候, 加了一把鎖, 鎖是一級緩存.
1 public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
2 Assert.notNull(beanName, "Bean name must not be null");
3 synchronized (this.singletonObjects) {
4 // 第一步: 從一級緩存中擷取單例對象
5 Object singletonObject = this.singletonObjects.get(beanName);
6 if (singletonObject == null) {
7 if (this.singletonsCurrentlyInDestruction) {
8 throw new BeanCreationNotAllowedException(beanName,
9 "Singleton bean creation not allowed while singletons of this factory are in destruction " +
10 "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
11 }
12 if (logger.isDebugEnabled()) {
13 logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
14 }
15 // 第二步: 将bean添加到singletonsCurrentlyInCreation中, 表示bean正在建立
16 beforeSingletonCreation(beanName);
17 boolean newSingleton = false;
18 boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
19 if (recordSuppressedExceptions) {
20 this.suppressedExceptions = new LinkedHashSet<>();
21 }
22 try {
23 // 第三步: 這裡調用getObject()鈎子方法, 就會回調匿名函數, 調用singletonFactory的createBean()
24 singletonObject = singletonFactory.getObject();
25 newSingleton = true;
26 }
27 catch (IllegalStateException ex) {
28 // Has the singleton object implicitly appeared in the meantime ->
29 // if yes, proceed with it since the exception indicates that state.
30 singletonObject = this.singletonObjects.get(beanName);
31 if (singletonObject == null) {
32 throw ex;
33 }
34 }
35 catch (BeanCreationException ex) {
36 if (recordSuppressedExceptions) {
37 for (Exception suppressedException : this.suppressedExceptions) {
38 ex.addRelatedCause(suppressedException);
39 }
40 }
41 throw ex;
42 }
43 finally {
44 if (recordSuppressedExceptions) {
45 this.suppressedExceptions = null;
46 }
47 afterSingletonCreation(beanName);
48 }
49 if (newSingleton) {
50 addSingleton(beanName, singletonObject);
51 }
52 }
53 return singletonObject;
54 }
55 }
再從緩存中取資料的時候, 也加了一把鎖, 和我們的demo邏輯是一樣的. 鎖也是一級緩存.
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從一級緩存中擷取bean執行個體對象
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 如果在第一級的緩存中沒有擷取到對象, 并且singletonsCurrentlyIncreation為true,也就是這個類正在建立.
* 标明目前是一個循環依賴.
*
* 這裡有處理循環依賴的問題.-- 我們使用三級緩存解決循環依賴
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
/**
* 從二級緩存中拿bean, 二級緩存中的對象是一個早期對象
* 什麼是早期對象?就是bean剛剛調用了構造方法, 還沒有給bean的屬性進行指派, 和初始化, 這就是早期對象
*/
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
/**
* 從三級緩存拿bean, singletonFactories是用來解決循環依賴的關鍵所在.
* 在ios後期的過程中, 當bean調用了構造方法的時候, 把早期對象包裝成一個ObjectFactory對象,暴露在三級緩存中
*/
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 在這裡通過暴露的ObjectFactory包裝對象. 通過調用他的getObject()方法來擷取對象
* 在這個環節中會調用getEarlyBeanReference()來進行後置處理
*/
singletonObject = singletonFactory.getObject();
// 把早期對象放置在二級緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 删除三級緩存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}