3.1 spring5源碼系列--循環依賴 之 手寫代碼模擬spring循環依賴

本次博客的目標

1. 手寫spring循環依賴的整個過程java

2. spring怎麼解決循環依賴面試

3. 爲何要二級緩存和三級緩存spring

4. spring有沒有解決構造函數的循環依賴緩存

5. spring有沒有解決多例下的循環依賴.多線程


一.  什麼是循環依賴?

以下圖所示: ide

 

 A類依賴了B類, 同時B類有依賴了A類. 這就是循環依賴, 造成了一個閉環函數

 

 

如上圖: A依賴了B, B同時依賴了A和C , C依賴了A. 這也是循環依賴. , 造成了一個閉環測試

 

那麼, 若是出現循環依賴, spring是如何解決循環依賴問題的呢?this

二. 模擬循環依賴

2.1 復現循環依賴

咱們定義三個類:spa

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, 這個方法不是必須有, 因此,咱們這裏不模擬了

看看以下圖所示

 

 紅色部分就造成了循環依賴.

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增長了一個出口. 不會循環建立了.

 

 

如上圖所示, 在@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的動態代理

 

 如上圖. 咱們在建立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; }
}

 

 下面就咱們的代碼分析一下:

第一種狀況: 沒有循環依賴

第二種狀況: 有循環依賴

第三種狀況: 有屢次循環依賴

咱們模擬一個循環依賴的場景, 覆蓋這三種狀況. 

 

 

 用代碼表示

類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. 

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

相關文章
相關標籤/搜索