Spring解決循環(huán)依賴(上)

本篇先嘗試自己實現(xiàn)一個解決循環(huán)依賴的方案,下篇分析Spring是如何解決的城舞。

1. 什么是循環(huán)依賴钧唐?

所謂的循環(huán)依賴是指忙灼,A 依賴 B,B 又依賴 A钝侠,它們之間形成了循環(huán)依賴该园。或者是 A 依賴 B帅韧,B 依賴 C里初,C 又依 賴 A。它們之間的依賴關(guān)系如下:


Spring循環(huán)依賴

2. Spring解決了什么忽舟?

首先要搞清楚双妨,Spring提供的幾種注入方式。

2.1 構(gòu)造器注入

顧名思義叮阅,構(gòu)造方法注入刁品,就是被注入對象可以通過在其構(gòu)造方法中聲明依賴對象的參數(shù)列表,讓外部(通常是IoC容器)知道它需要哪些依賴對象帘饶。

2.2 屬性注入

對于JavaBean對象來說哑诊,通常會通過setXXX()和getXXX()方法來訪問對應(yīng)屬性群扶。這些setXXX()方法統(tǒng)稱為setter方法及刻,getXXX()當然就稱為getter方法。通過setter方法竞阐,可以更改相應(yīng)的對象屬性缴饭,通過getter方法,可以獲得相應(yīng)屬性的狀態(tài)骆莹。所以颗搂,當前對象只要為其依賴對象所對應(yīng)的屬性添加setter方法,就可以通過setter方法將相應(yīng)的依賴對象設(shè)置到被注入對象中幕垦。

2.3 接口注入(平時用不到丢氢,可以不考慮)

相對于前兩種注入方式來說傅联,接口注入沒有那么簡單明了。被注入對象如果想要IoC Service
Provider為其注入依賴對象疚察,就必須實現(xiàn)某個接口蒸走。這個接口提供一個方法,用來為其注入依賴對象貌嫡。
IoC Service Provider最終通過這些接口來了解應(yīng)該為被注入對象注入什么依賴對象比驻。

2.4 Spring只解決了單例下屬性注入的循環(huán)依賴

直接上代碼,先看構(gòu)造器注入的岛抄。

package com.spring.beans;

import org.springframework.stereotype.Component;

@Component
public class ClassA {

    private ClassB classB;

    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}

package com.spring.beans;

import org.springframework.stereotype.Component;

@Component
public class ClassB {

    private ClassA classA;

    public ClassB(ClassA classA) {
        this.classA = classA;
    }
}

測試類

package com.spring.beans;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.spring.beans");

    }

}

最后報錯了别惦,可見Spring不能解決構(gòu)造器注入的循環(huán)依賴問題,記住這個報錯信息夫椭,以后出現(xiàn)該報錯都是循環(huán)依賴的問題掸掸。

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'classA': Requested bean is currently in creation: Is there an unresolvable circular reference?

再來看屬性注入的。

package com.spring.beans;

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

@Component
public class ClassA {

    @Autowired
    private ClassB classB;

}
package com.spring.beans;

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

@Component
public class ClassB {

    @Autowired
    private ClassA classA;

}
package com.spring.beans;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Demo {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.spring.beans");
    }

}

運行成功益楼。

3. 自己實現(xiàn)一個

3.1 第一版

3.1.1 解決思路

先上流程圖猾漫。


第一版

3.1.2 具體代碼

package com.spring.beans;

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

@Component
public class ClassA {

    @Autowired
    private ClassB classB;

    public ClassA(){
        System.out.println("ClassA實例化");
    }

    public ClassA(ClassB classB) {
        this.classB = classB;
    }

    public void setClassB(ClassB classB) {
        this.classB = classB;
    }

    public ClassB getClassB() {
        return classB;
    }

    public void sayHi(){
        System.out.println("我是classA");
    }
}
package com.spring.beans;

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

@Component
public class ClassB {

    @Autowired
    private ClassA classA;

    public ClassB(){
        System.out.println("ClassB實例化");
    }

    public ClassB(ClassA classA) {
        this.classA = classA;
    }

    public ClassA getClassA() {
        return classA;
    }

    public void sayHi(){
        System.out.println(classA);
    }
}
package com.spring.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.reflect.Field;
import java.util.Map;

public class Demo {

    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentReferenceHashMap<>(10);

    private static Map<String, Object> singletonObjects = new ConcurrentReferenceHashMap<>(10);

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

        loadBeanDefinition();

        for(String beanName : beanDefinitionMap.keySet()){
            getBean(beanName);
        }

        ClassA classA = (ClassA) getBean("classA");

        classA.sayHi();
    }

    private static void loadBeanDefinition() {
        //生成Bean定義
        RootBeanDefinition rootBeanDefinitionClassA = new RootBeanDefinition(ClassA.class);
        RootBeanDefinition rootBeanDefinitionClassB = new RootBeanDefinition(ClassB.class);

        //將類注冊到Bean定義的Map里
        beanDefinitionMap.put("classA", rootBeanDefinitionClassA);
        beanDefinitionMap.put("classB", rootBeanDefinitionClassB);
    }

    @SuppressWarnings("")
    private static Object getBean(String beanName) throws Exception {

        //如果緩存里邊有,直接返回
        if (singletonObjects.containsKey(beanName)){
            return singletonObjects.get(beanName);
        }

        //實例化
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);

        Class<?> beanClass = beanDefinition.getBeanClass();
        
        //緩存里沒有的話感凤,通過無參構(gòu)造函數(shù)悯周,將對象本身實例化
        Object beanInstance = beanClass.newInstance();

        //放入緩存
        singletonObjects.put(beanName,beanInstance);

        //屬性賦值,解析Autowired
        //拿到所有的屬性名
        Field[] declaredFields = beanClass.getDeclaredFields();

        //循環(huán)所有屬性
        for (Field declaredField : declaredFields){
            declaredField.setAccessible(true);

            //從屬性上拿到@Autowired
            Autowired annotation = declaredField.getAnnotation(Autowired.class);

            if (annotation != null){
                String name = declaredField.getName();
                Object fieldObject = getBean(name);
                declaredField.set(beanInstance,fieldObject);
            }
        }

        return  beanInstance;

    }

}

3.2 第二版

第一版實現(xiàn)完了陪竿,雖然在單線程下沒有循環(huán)依賴的問題了禽翼,但是緩存只存入了還沒有賦值的實例,即這個實例是不完整的族跛,所以還需要用另一個緩存來存儲不完整的實例闰挡,用以區(qū)分完整的和不完整的實例。

3.2.1 流程圖

第二版

3.2.2 具體代碼

在原有代碼基礎(chǔ)上礁哄,做了小的改動长酗,并封裝了一些方法。

package com.spring.beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.util.ConcurrentReferenceHashMap;

import java.lang.reflect.Field;
import java.util.Map;

public class Demo {

    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentReferenceHashMap<>(10);

    private static Map<String, Object> singletonObjects = new ConcurrentReferenceHashMap<>(10);

    private static Map<String, Object> earlySingletonObjects = new ConcurrentReferenceHashMap<>(10);

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

        loadBeanDefinition();

        for (String beanName : beanDefinitionMap.keySet()) {
            getBean(beanName);
        }

        ClassA classA = (ClassA) getBean("classA");

        classA.sayHi();
    }

    private static void loadBeanDefinition() {
        //生成Bean定義
        RootBeanDefinition rootBeanDefinitionClassA = new RootBeanDefinition(ClassA.class);
        RootBeanDefinition rootBeanDefinitionClassB = new RootBeanDefinition(ClassB.class);

        //將類注冊到Bean定義的Map里
        beanDefinitionMap.put("classA", rootBeanDefinitionClassA);
        beanDefinitionMap.put("classB", rootBeanDefinitionClassB);
    }

    @SuppressWarnings("")
    private static Object getBean(String beanName) throws Exception {

        Object object = getSingletonObject(beanName);

        if (object != null) {
            return object;
        }

        Object beanInstance = createBeanInstance(beanName);

        //放入一級緩存
        singletonObjects.put(beanName, beanInstance);

        return beanInstance;

    }

    /**
     * 負責Bean的實例化
     * @param beanName 對象的名字
     * @return 對象的實例
     * @throws Exception 異常
     */
    private static Object createBeanInstance(String beanName) throws Exception {
        //實例化
        RootBeanDefinition beanDefinition = (RootBeanDefinition) beanDefinitionMap.get(beanName);

        Class<?> beanClass = beanDefinition.getBeanClass();

        //緩存里沒有的話桐绒,通過無參構(gòu)造函數(shù)夺脾,將對象本身實例化
        Object beanInstance = beanClass.newInstance();

        //放入二級緩存
        earlySingletonObjects.put(beanName, beanInstance);

        //屬性賦值,解析Autowired
        //拿到所有的屬性名
        Field[] declaredFields = beanClass.getDeclaredFields();

        //循環(huán)所有屬性
        for (Field declaredField : declaredFields) {
            declaredField.setAccessible(true);

            //從屬性上拿到@Autowired
            Autowired annotation = declaredField.getAnnotation(Autowired.class);

            if (annotation != null) {
                String name = declaredField.getName();
                Object fieldObject = getBean(name);
                declaredField.set(beanInstance, fieldObject);
            }
        }

        return beanInstance;
    }

    /**
     * 獲取單例對象
     * @param beanName 對象名字
     * @return 對象實例
     */
    private static Object getSingletonObject(String beanName) {
        //先從一級緩存中拿
        Object bean = singletonObjects.get(beanName);

        //一級緩存沒有茉继,從二級緩存拿
        if (bean == null){
            bean = earlySingletonObjects.get(beanName);
            if (bean ==null){
                return null;
            }
        }

        return bean;
    }

}

至此咧叭,也算解決了單線程下的循環(huán)依賴問題,但是Spring作為一個開源框架烁竭,會有很多的擴展點以及使用場景菲茬,比如AOP、多線程環(huán)境下的使用,那么考慮這兩點的話婉弹,上邊代碼就不足以解決這些問題了睬魂,敬請期待下篇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末镀赌,一起剝皮案震驚了整個濱河市汉买,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佩脊,老刑警劉巖蛙粘,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異威彰,居然都是意外死亡出牧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門歇盼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舔痕,“玉大人,你說我怎么就攤上這事豹缀〔矗” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵邢笙,是天一觀的道長啸如。 經(jīng)常有香客問我,道長氮惯,這世上最難降的妖魔是什么叮雳? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮妇汗,結(jié)果婚禮上帘不,老公的妹妹穿的比我還像新娘。我一直安慰自己杨箭,他們只是感情好寞焙,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著互婿,像睡著了一般捣郊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上擒悬,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天模她,我揣著相機與錄音稻艰,去河邊找鬼懂牧。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的僧凤。 我是一名探鬼主播畜侦,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼躯保!你這毒婦竟也來了旋膳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤途事,失蹤者是張志新(化名)和其女友劉穎验懊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尸变,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡义图,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了召烂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碱工。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奏夫,靈堂內(nèi)的尸體忽然破棺而出怕篷,到底是詐尸還是另有隱情,我是刑警寧澤酗昼,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布廊谓,位于F島的核電站,受9級特大地震影響麻削,放射性物質(zhì)發(fā)生泄漏蹂析。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一碟婆、第九天 我趴在偏房一處隱蔽的房頂上張望电抚。 院中可真熱鬧,春花似錦竖共、人聲如沸蝙叛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽借帘。三九已至,卻和暖如春淌铐,著一層夾襖步出監(jiān)牢的瞬間肺然,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工腿准, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留际起,地道東北人拾碌。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像街望,于是被迫代替她去往敵國和親校翔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • [toc] 循環(huán)依賴 循環(huán)依賴就是N個類中循環(huán)嵌套引用灾前,如果日常開發(fā)中我們用new對象的方式發(fā)生這種循環(huán)依賴的程序...
    星空怎樣閱讀 329評論 0 0
  • 1防症、什么是循環(huán)依賴 循環(huán)依賴就是循環(huán)引用,就是兩個或多個bean相互之間的持有對方哎甲,比如A引用B蔫敲,而B又引用A,則...
    JBryan閱讀 599評論 0 0
  • 什么是循環(huán)依賴炭玫?在創(chuàng)建A的時候發(fā)現(xiàn)A中的屬性需要B對象燕偶,那就先去創(chuàng)建B對象,又發(fā)現(xiàn)B中的屬性需要A對象础嫡,那又去創(chuàng)建...
    Y了個J閱讀 1,835評論 0 0
  • 夜鶯2517閱讀 127,712評論 1 9
  • 版本:ios 1.2.1 亮點: 1.app角標可以實時更新天氣溫度或選擇空氣質(zhì)量指么,建議處女座就不要選了,不然老想...
    我就是沉沉閱讀 6,878評論 1 6