3.3 Spring5原始碼---迴圈依賴過程中spring讀取不完整bean的最終解決方案

盛開的太陽發表於2020-11-14

根據之前解析的迴圈依賴的原始碼, 分析了一級快取,二級快取,三級快取的作用以及如何解決迴圈依賴的. 然而在多執行緒的情況下, Spring在建立bean的過程中, 可能會讀取到不完整的bean. 下面, 我們就來研究兩點:

1. 為什麼會讀取到不完整的bean.

2. 如何解決讀取到不完整bean的問題.

 

和本文相關的spring迴圈依賴的前兩篇博文如下: 

3.1 spring5原始碼系列--迴圈依賴 之 手寫程式碼模擬spring迴圈依賴

3.2spring原始碼系列----迴圈依賴原始碼分析


一. 為什麼會讀取到不完整的bean.

我們知道, 如果spring容器已經載入完了, 那麼肯定所有bean都是完整的了, 但如果, spring沒有載入完, 在載入的過程中, 構建bean就有可能出現不完整bean的情況

如下所示: 

3.3 Spring5原始碼---迴圈依賴過程中spring讀取不完整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;
    }

加了兩把鎖.

 

這樣, 在分析一下

3.3 Spring5原始碼---迴圈依賴過程中spring讀取不完整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;
    }

 

相關文章