根據之前解析的循環依賴的源碼, 分析了一級緩存,二級緩存,三級緩存的作用以及如何解決循環依賴的. 然而在多線程的情況下, Spring在創建bean的過程中, 可能會讀取到不完整的bean. 下面, 我們就來研究兩點:
1. 為什么會讀取到不完整的bean.
2. 如何解決讀取到不完整bean的問題.
和本文相關的spring循環依賴的前兩篇博文如下:
3.1 spring5源碼系列--循環依賴 之 手寫代碼模擬spring循環依賴
一. 為什么會讀取到不完整的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; }

