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

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

本次部落格的目標

1. 手寫spring迴圈依賴的整個過程

2. spring怎麼解決迴圈依賴

3. 為什麼要二級快取和三級快取

4. spring有沒有解決建構函式的迴圈依賴

5. spring有沒有解決多例下的迴圈依賴.


一.  什麼是迴圈依賴?

如下圖所示: 

 

 A類依賴了B類, 同時B類有依賴了A類. 這就是迴圈依賴, 形成了一個閉環

 

 

如上圖: A依賴了B, B同時依賴了A和C , C依賴了A. 這也是迴圈依賴. , 形成了一個閉環

 

那麼, 如果出現迴圈依賴, spring是如何解決迴圈依賴問題的呢?

二. 模擬迴圈依賴

2.1 復現迴圈依賴

我們定義三個類:

1. 新增類InstanceA

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class InstanceA {
    @Autowired
    private InstanceB instanceB;

    public InstanceA() {
        System.out.println("呼叫 instanceA的建構函式");
    }

    public InstanceA(InstanceB instanceB) {
        this.instanceB = instanceB;
    }

    public void say(){
        System.out.println( "I am A");
    }


    public InstanceB getInstanceB() {
        return instanceB;
    }

    public void setInstanceB(InstanceB instanceB) {
        this.instanceB = instanceB;
    }

}

 

這是InstanceA, 裡面引用了InstanceB.

 

2. 新增類instanceB

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope("prototype")
public class InstanceB {

    @Autowired
    private InstanceA instanceA;
    public InstanceB() {
        System.out.println("呼叫 instanceB的建構函式");
    }

    public InstanceA getInstanceA() {
        return instanceA;
    }

    public void setInstanceA(InstanceA instanceA) {
        this.instanceA = instanceA;
    }



}

 

這是InstanceB, 在裡面有引用了InstanceA

3:模擬spring是如何建立Bean的

這個在前面已經說過了, 首先會載入配置類的後置處理器, 將其解析後放入到beanDefinitionMap中. 然後載入配置類, 也將其解析後放入beanDefinitionMap中. 最後解析配置類. 我們這裡直接簡化掉前兩步, 將兩個類放入beanDefinitionMap中. 主要模擬第三步解析配置類. 在解析的過程中, 獲取bean的時候會出現迴圈依賴的問題迴圈依賴.

第一步: 將兩個類放入到beanDefinitionMap中

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

/*
* * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的 * * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中. * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中. */ public static void loadBeanDefinitions(){ RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class); RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class); beanDefinitionMap.put("instanceA", aBeanDefinition); beanDefinitionMap.put("instanceB", bBeanDefinition); } public static void main(String[] args) throws Exception { // 第一步: 掃描配置類, 讀取bean定義 loadBeanDefinitions(); ...... }

 

上面的程式碼結構很簡單, 再看一下注釋應該就能明白了. 這裡就是模擬spring將配置類解析放入到beanDefinitionMap的過程. 

 

第二步: 迴圈建立bean

首先,我們已經知道, 建立bean一共有三個步驟: 例項化, 屬性賦值, 初始化. 

 

 

 

而在屬性賦值的時候, 會判斷是否引用了其他的Bean, 如果引用了, 那麼需要構建此Bean. 下面來看一下程式碼

/**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {/**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

       /**
         *  第二步: 屬性賦值
         *  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.這個可以設定也可以不設定. 我們這裡就不設定了
         */
        return instanceBean;
    }

 

我們看到如上程式碼. 

第一步: 例項化: 使用反射的方式, 根據beanName查詢構建一個例項bean. 

第二步: 屬性賦值: 判斷屬性中是否有@Autowired屬性, 如果有這個屬性, 那麼需要構建bean. 我們發現在為InstanceA賦值的時候, 裡面引用了InstanceB, 所以去建立InstanceB, 而建立InstanceB的時候, 發現裡面又有InstanceA, 於是又去建立A. 然後以此類推,繼續判斷. 就形成了死迴圈. 無法走出這個環. 這就是迴圈依賴

第三步: 初始化: 呼叫init-method, 這個方法不是必須有, 所以,我們這裡不模擬了

看看如下圖所示

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

 

 紅色部分就形成了迴圈依賴.

4: 增加一級快取, 解決迴圈依賴的問題. 

我們知道上面進行了迴圈依賴了. 其實, 我們的目標很簡單, 如果一個類建立過了, 那麼就請不要在建立了. 

所以, 我們增加一級快取

  // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

   /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

        /**
         * 第二步: 放入到一級快取
         */
        singletonObjects.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.這個可以設定也可以不設定. 我們這裡就不設定了
         */
        return instanceBean;
    }

 

還是上面的獲取bean的流程, 不一樣的是, 這裡增加了以及快取. 當我們獲取到bean例項以後, 將其放入到快取中. 下次再需要建立之前, 先去快取裡判斷,是否已經有了, 如果沒有, 那麼再建立. 

這樣就給建立bean增加了一個出口. 不會迴圈建立了.

 

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

如上圖所示, 在@Autowired的時候, 增加了一個出口. 判斷即將要建立的類是否已經存在, 如果存在了, 那麼就直接返回, 不在建立

雖然使用了一級快取解決了迴圈依賴的問題, 但要是在多執行緒下, 這個依賴可能就會出現問題.

比如: 有兩個執行緒, 同時建立instanceA 和instanceB, instanceA和instanceB都引用了instanceC. 他們同步進行, 都去建立instanceC. 首先A去建立, A在例項化instanceC以後就將其放入到一級快取了, 這時候, B去一級快取裡拿. 此時拿到的instanceC是不完整的. 後面的屬性賦值, 初始化都還沒有執行呢. 所以, 我們增加耳機快取來解決這個問題. 

 

5. 增加二級快取, 區分完整的bean和純淨的bean.

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    /**
     * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的
     *
     * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中.
     * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中.
     */
    public static void loadBeanDefinitions(){
        RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA", aBeanDefinition);
        beanDefinitionMap.put("instanceB", bBeanDefinition);
    }

    public static void main(String[] args) throws Exception {

        // 第一步: 掃描配置類, 讀取bean定義
        loadBeanDefinitions();

        // 第二步: 迴圈建立bean
        for (String key: beanDefinitionMap.keySet()) {
            // 第一次: key是instanceA, 所以先建立A類
            getBean(key);

        }

        // 測試: 看是否能執行成功
        InstanceA instanceA = (InstanceA) getBean("instanceA");
        instanceA.say();

    }

    /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();
        /**
         * 第二步: 放入到二級快取
         */
        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.這個可以設定也可以不設定. 我們這裡就不設定了
         */

        /**
         * 第二步: 放入到一級快取
         */
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

    /**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        // 先去一級快取裡拿,如果一級快取沒有拿到,去二級快取裡拿
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else if (earlySingletonObjects.containsKey(beanName)){
            return earlySingletonObjects.get(beanName);
        } else {
            return null;
        }
    }
}

 

如上圖所示,增加了一個二級快取. 首先, 構建出instanceBean以後, 直接將其放入到二級快取中. 這時只是一個純淨的bean, 裡面還沒有給屬性賦值, 初始化. 在給屬性賦值完成, 初始化完成以後, 在將其放入到一級快取中. 

我們判斷快取中是否有某個例項bean的時候, 先去一級快取中判斷是否有完整的bean, 如果沒有, 就去二級快取中判斷有沒有例項化過這個bean. 

 總結: 一級快取和二級快取的作用

 

一級快取: 解決迴圈依賴的問題

二級快取: 在建立例項bean和放入到一級快取之間還有一段間隙. 如果在這之間從一級快取拿例項, 肯定是返回null的. 為了避免這個問題, 增加了二級快取.

 

我們都知道spring中有一級快取, 二級快取, 三級快取. 一級快取和二級快取的作用我們知道了, 那麼三級快取有什麼用呢?

 6. 增加三級快取

 三級快取有什麼作用呢? 這個問題眾說紛紜, 有說代理, 有說AOP. 其實AOP的問題可以用二級快取來解決. 下面就來看看AOP如何用二級快取解決.

建立AOP動態代理 (不是耦合的, 採用解耦的, 通過BeanPostProcessor bean的後置處理器來建立). 之前講過, 如下圖

在初始化之後, 呼叫Bean的後置處理器去建立的AOP的動態代理

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

 

 如上圖. 我們在建立bean 的時候, 會有很多Bean的後置處理器BeanPostProcessor. 如果有AOP, 會在什麼時候建立呢? 在初始化以後, 呼叫BeanPostProcessor建立動態代理. 

結合上面的程式碼, 我們想一想, 其實在初始化以後建立動態代理就晚了. 為什麼呢? 因為, 如果有迴圈依賴, 在初始化之後才呼叫, 那就不是動態代理. 其實我們這時候應該在例項化之後, 放入到二級快取之前呼叫

面試題: 在建立bean的時候, 在哪裡建立的動態代理, 這個應該怎麼回答呢?
很多人會說在初始化之後, 或者在例項化之後.
其實更嚴謹的說, 有兩種情況: 第一種是在初始化之後呼叫 . 第二種是出現了迴圈依賴, 會在例項化之後呼叫

 我們上面說的就是第二種情況. 也就是說,正常情況下是在初始化之後呼叫的, 但是如果有迴圈依賴, 就要在例項化之後呼叫了.

 

下面來看看如何在二級快取加動態代理. 

首先, 我們這裡有迴圈依賴, 所以將動態代理放在例項化之後, 

  /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object instanceBean = beanClass.newInstance();

        /**
         * 建立AOP動態代理 (不是耦合的, 採用解耦的, 通過BeanPostProcessor bean的後置處理器得來的.  之前講過,
         * 在初始化之後, 呼叫Bean的後置處理器去建立的AOP的動態代理 )
         */
        instanceBean = new JdkProxyBeanPostProcessor().getEarlyBeanReference(instanceBean, "instanceA");

        /**
         * 第二步: 放入到二級快取
         */
        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.這個可以設定也可以不設定. 我們這裡就不設定了
         */
      //   正常動態代理建立的時機
        
        /**
         * 第五步: 放入到一級快取
         */
        singletonObjects.put(beanName, instanceBean);
        return instanceBean;
    }

 

這裡只是簡單模擬了動態代理.

我們知道動態代理有兩個地方. 如果是普通類動態代理在初始化之後執行, 如果是迴圈依賴, 那麼動態代理是在例項化之後. 

 

上面在例項化之後建立proxy的程式碼不完整, 為什麼不完整呢, 因為沒有判斷是否是迴圈依賴. 

 

我們簡單模擬一個動態代理的實現.

public class JdkProxyBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {
    /**
     * 假設A被切點命中 需要建立代理  @PointCut("execution(* *..InstanceA.*(..))")
     * @param bean the raw bean instance
     * @param beanName the name of the bean
     * @return
     * @throws BeansException
     */
    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {

        // 假設A被切點命中 需要建立代理  @PointCut("execution(* *..InstanceA.*(..))")

        /**
         * 這裡, 我們簡單直接判斷bean是不是InstanceA例項, 如果是, 就建立動態代理.
         * 這裡沒有去解析切點, 解析切點是AspectJ做的事.
         */
        if (bean instanceof InstanceA) {
            JdkDynimcProxy jdkDynimcProxy = new JdkDynimcProxy(bean);
            return jdkDynimcProxy.getProxy();
        }
        return bean;
    }
}

 

這裡直接判斷, 如果bean是InstanceA的例項, 那麼就呼叫bean的動態代理.  動態代理的簡單邏輯就是: 解析切面, 然後建立類, 如果類不存在就新增, 如果存在則不在建立, 直接取出來返回.

 

在來看看動態代理,放在例項化之後. 建立AOP, 但是, 在這裡建立AOP動態代理的條件是迴圈依賴.

問題1: 那麼如何判斷是迴圈依賴呢?

二級快取中bean不是null. 

如果一個類在建立的過程中, 會放入到二級快取, 如果完全建立完了, 會放入到一級快取, 然後刪除二級快取. 所以, 如果二級快取中的bean只要存在, 就說明這個類是建立中, 出現了迴圈依賴.

問題2: 什麼時候判斷呢?

應該在getSingleton()判斷是否是迴圈依賴的時候判斷. 因為這時候我們剛好判斷了二級快取中bean是否為空.

/**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        // 先去一級快取裡拿,如果一級快取沒有拿到,去二級快取裡拿
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        } else if (earlySingletonObjects.containsKey(beanName)){

            /**
             * 第一次建立bean是正常的instanceBean. 他並不是迴圈依賴. 第二次進來判斷, 這個bean已經存在了, 就說明是迴圈依賴了
             * 這時候通過動態代理建立bean. 然後將這個bean在放入到二級快取中覆蓋原來的instanceBean.
             */
            Object obj = new JdkProxyBeanPostProcessor()
                    .getEarlyBeanReference(earlySingletonObjects.get(beanName), beanName);

            earlySingletonObjects.put(beanName, obj);

            return earlySingletonObjects.get(beanName);
        } else {
            return null;
        }
    }

 

這樣我們在迴圈依賴的時候就完成了AOP的建立. 這是在二級快取裡建立的AOP,

問題3: 那這是不是說就不需要三級快取了呢?

那麼,來找問題.  這裡有兩個問題:

問題1: 我們發現在建立動態代理的時候, 我們使用的bean的後置處理器JdkProxyBeanPostProcessor.這有點不太符合規則,
      因為, spring在getBean()的時候並沒有使用Bean的後置處理器, 而是在createBean()的時候才去使用的bean的後置處理器.
問題2: 如果A是AOP, 他一直都是, 最開始建立的時候也應該是. 使用這種方法, 結果是第一次建立出來的bean不是AOP動態代理.

 

對於第一個問題: 我們希望在例項化的時候建立AOP, 但是具體判斷是在getSingleton()方法裡判斷. 這裡通過三級快取來實現. 三級快取裡面放的是一個介面定義的鉤子方法. 方法的執行在後面呼叫的時候執行. 

 

對於第二個問題: 我們的二級快取就不能直接儲存instanceBean例項了, 增加一個引數, 用來標記當前這個類是一個正在建立中的類. 這樣來判斷迴圈依賴.

 

下面先來看看建立的三個快取和一個標識

  // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取: 為了將成熟的bean和純淨的bean分離. 避免讀取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三級快取:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 迴圈依賴的標識---當前正在建立的例項bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();

 

然後在來看看迴圈依賴的出口

/**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一級快取裡拿
        Object bean = singletonObjects.get(beanName);
        // 一級快取中沒有, 但是正在建立的bean標識中有, 說明是迴圈依賴
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二級快取中沒有, 就從三級快取中拿
            if (bean == null) {
                // 從三級快取中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 這裡是真正建立動態代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然後將其放入到二級快取中. 因為如果有多次依賴, 就去二級快取中判斷. 已經有了就不在再次建立了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }

 

這裡的邏輯是, 先去一級快取中拿, 一級快取放的是成熟的bean, 也就是他已經完成了屬性賦值和初始化. 如果一級快取沒有, 而正在建立中的類標識是true, 就說明這個類正在建立中, 這是一個迴圈依賴. 這個時候就去二級快取中取資料, 二級快取中的資料是何時放進去的呢, 是後面從三級快取中建立動態代理後放進去的. 如果二級快取為空, 說明沒有建立過動態代理, 這時候在去三級快取中拿, 然後建立動態代理. 建立完以後放入二級快取中, 後面就不用再建立. 

 

完成的程式碼如下:

package com.lxl.www.circulardependencies;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class MainStart {
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    // 一級快取
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 二級快取: 為了將成熟的bean和純淨的bean分離. 避免讀取到不完整的bean.
    private static Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

    // 三級快取:
    private static Map<String, ObjectFactory> singletonFactories = new ConcurrentHashMap<>();

    // 迴圈依賴的標識---當前正在建立的例項bean
    private static Set<String> singletonsCurrectlyInCreation = new HashSet<>();

    /**
     * 讀取bean定義, 當然在spring中肯定是根據配置 動態掃描註冊的
     *
     * InstanceA和InstanceB都有註解@Component, 所以, 在spring掃描讀取配置類的時候, 會把他們兩個掃描到BeanDefinitionMap中.
     * 這裡, 我們省略這一步, 直接將instanceA和instanceB放到BeanDefinitionMap中.
     */
    public static void loadBeanDefinitions(){
        RootBeanDefinition aBeanDefinition = new RootBeanDefinition(InstanceA.class);
        RootBeanDefinition bBeanDefinition = new RootBeanDefinition(InstanceB.class);
        beanDefinitionMap.put("instanceA", aBeanDefinition);
        beanDefinitionMap.put("instanceB", bBeanDefinition);
    }

    public static void main(String[] args) throws Exception {

        // 第一步: 掃描配置類, 讀取bean定義
        loadBeanDefinitions();

        // 第二步: 迴圈建立bean
        for (String key: beanDefinitionMap.keySet()) {
            // 第一次: key是instanceA, 所以先建立A類
            getBean(key);

        }

        // 測試: 看是否能執行成功
        InstanceA instanceA = (InstanceA) getBean("instanceA");
        instanceA.say();

    }

    /**
     * 獲取bean, 根據beanName獲取
     */
    public static Object getBean(String beanName) throws Exception {

        // 增加一個出口. 判斷實體類是否已經被載入過了
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }

        // 標記bean正在建立
        if (!singletonsCurrectlyInCreation.contains(beanName)) {
            singletonsCurrectlyInCreation.add(beanName);
        }

        /**
         * 第一步: 例項化
         * 我們這裡是模擬, 採用反射的方式進行例項化. 呼叫的也是最簡單的無參建構函式
         */
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        // 呼叫無參的建構函式進行例項化
        Object 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;
    }

    /**
     * 判斷是否是迴圈引用的出口.
     * @param beanName
     * @return
     */
    private static Object getSingleton(String beanName) {
        //先去一級快取裡拿,
        Object bean = singletonObjects.get(beanName);
        // 一級快取中沒有, 但是正在建立的bean標識中有, 說明是迴圈依賴
        if (bean == null && singletonsCurrectlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            // 如果二級快取中沒有, 就從三級快取中拿
            if (bean == null) {
                // 從三級快取中取
                ObjectFactory objectFactory = singletonFactories.get(beanName);
                if (objectFactory != null) {
                    // 這裡是真正建立動態代理的地方.
                    Object obj = objectFactory.getObject();
                    // 然後將其放入到二級快取中. 因為如果有多次依賴, 就去二級快取中判斷. 已經有了就不在再次建立了
                    earlySingletonObjects.put(beanName, obj);
                }
            }
        }
        return bean;
    }
}

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

 

 下面就我們的程式碼分析一下:

第一種情況: 沒有迴圈依賴

第二種情況: 有迴圈依賴

第三種情況: 有多次迴圈依賴

我們模擬一個迴圈依賴的場景, 覆蓋這三種情況. 

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

 

 用程式碼表示

類A

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;

public class A {
    @Autowired
    private B b;

    @Autowired
    private C c;
}

類B

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;

public class B {
    @Autowired
    private A a;

    @Autowired
    private B b;
}

類C

package com.lxl.www.circulardependencies;

import org.springframework.beans.factory.annotation.Autowired;

public class C {
    @Autowired
    private A a;
}

其中類A剛好匹配AOP的切面@PointCut("execution(* *..A.*(..))")

 

下面分析他們的迴圈依賴關係. 

此時beanDefinitionMap中有三個bean定義. 分別是A, B, C

1. 先解析類A, 根據上面的流程.

  1) 首先呼叫getSingleton, 此時一級快取, 二級快取都沒有, 正在建立標誌也是null. 所以, 返回的是null

  2) 標記當前類正在建立中

  3) 例項化

  4) 將A放入到三級快取, 並定義動態代理的鉤子方法

  5) 屬性賦值. A有兩個屬性, 分別是B和C. 都帶有@Autowired註解, 先解析B.

  6) A暫停, 解析B

2. 解析A類的屬性類B

  1) 首先呼叫getSingleton, 此時一級快取, 二級快取都沒有, 正在建立標誌也是null. 所以, 返回的是null  

  2) 標記當前類正在建立中

  3) 例項化

  4) 將B放入到三級快取, 並定義動態代理的鉤子方法

  5) 屬性賦值. B有兩個屬性, 分別是A和C. 都帶有@Autowired註解, 先解析A. 在解析C

  6) B暫停, 解析A

3. 解析B類的屬性A 

  1) 首先呼叫getSingleton, 此時一級快取中這個屬性為null, 正在建立中標誌位true, 二級快取為空, 從三級快取中建立動態代理, 然後判斷是否符合動態代理切面要求, A符合. 所以通過動態代理建立A的代理bean放入到二級快取. 返回例項bean.

  2) A此時已經存在了, 所以, 直接返回

4. 解析B類的屬性C

  1) 首先呼叫getSingleton, 此時一級快取, 二級快取都沒有, 正在建立標誌也是null. 所以, 返回的是null  

  2) 標記當前類C正在建立中

  3) 例項化

  4) 將C放入到三級快取, 並定義動態代理的鉤子方法

  5) 屬性賦值. C有一個屬性, 是A. 帶有@Autowired註解, 先解析A

  6) C暫停, 解析A

5. 解析C中的屬性A

  1) 首先呼叫getSingleton()方法, 此時一級快取中沒有, 標誌位為true, 二級快取中已經有A的動態代理例項了, 所以,直接返回.

  2) A此時已經在存在, 直接返回

6. 繼續解析B類的屬性C

  1) 接著第4步往下走

  2) 初始化類C

  3) 將類C放入到一級快取中. 放之前去二級快取中取, 二級快取中沒有. 所以, 這裡存的是C通過反射構建的instanceBean

7. 繼續解析A類的屬性類B

  1) 接著第2步往下走

  2) 初始化類B

  3) 將類B放入到一級快取中. 放之前去二級快取中取.二級快取中沒有, 所以, 這裡存的是B通過反射構建的instanceBean

  4) 構建結束,返回

8. 解析A類的屬性類C

  1) 首先呼叫getSingleton()方法, 此時一級快取中已經有了類C, 所以直接返回

9. 繼續解析A類

  1) 接著第1步往下走

  2) 初始化類A

  3) 將A放入到一級快取中. 放之前判斷二級快取中有沒有例項bean, 我們發現有, 所以, 取出來放入到A的一級快取中.

  4) 構建bean結束, 返回

10. 接下來構建beanDefinitionMap中的類B

  1) 首先呼叫getSingleton()方法, 此時一級快取中已經有了類B, 所以直接返回

11. 接下來構建beanDefinitionMap中的類C

  1) 首先呼叫getSingleton()方法, 此時一級快取中已經有了類C, 所以直接返回

 至此整個構建過程結束. 

 

總結: 

再來感受一下三級快取的作用:

一級快取: 用來存放成熟的bean. 這個bean如果是切入點, 則是一個動態代理的bean,如果不是切入點, 則是一個普通的類

二級快取: 用來存放迴圈依賴過程中建立的動態代理bean. 

三級快取: 用來存放動態代理的鉤子方法. 用來在需要構建動態代理類的時候使用.

相關文章