spring getBean源碼解析03

spring中bean的生命周期

Springbean的生命周期.png

spring bean的作用域

spring bean的作用域.png

spring 如何解決循環(huán)依賴

@Component
public class A {

    @Autowired
    private B b;

    public void aMethod(){
        b.bMethod();
    }
}

@Component
public class B {
    @Autowired
    private A a;

    public void bMethod(){
        System.out.println("bmethod");
    }
}

@SpringBootApplication
public class MyspringlearningApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
        A a = context.getBean(A.class);
        a.aMethod();

    }
}

上面的示例中孕惜,A依賴B政己,B又依賴了A.是典型的循環(huán)依賴的情況酌壕,但是運行的結果來看,程序能正常運行歇由,調用a.aMethod()能打印出"bmethod"字符串

那spring是怎么解決循環(huán)依賴的呢卵牍??沦泌?

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        if (logger.isDebugEnabled()) {
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
                        "' that is not fully initialized yet - a consequence of a circular reference");
            }
            else {
                logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }

    else {
        // Fail if we're already creating this bean instance:
        // We're assumably within a circular reference.
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        .......
        各種作用域的創(chuàng)建bean
}

獲取bean的時候

  1. 從spring維護的三個緩存中獲取bean
  2. 如果緩存中獲取失敗糊昙,并且bean處于創(chuàng)建中,拋出循環(huán)依賴異常
  3. 在對應的作用域中創(chuàng)建bean

spring為bean創(chuàng)建了三個級別的緩存谢谦,
這三級緩存分別是

級別 名稱 類型 內容
1 singletonObjects Map<String, Object> 正在完成加載的bean
2 earlySingletonObjects Map<String, Object> 完成了實例化释牺,提前曝光的Bean02
3 singletonFactories Map<String, ObjectFactory<?>> 提前曝光的bean01
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

getBean的第一步操作就是從三個級別緩存中獲取bean

  1. 如果singletonObjects中有萝衩,可以直接返回
  2. 如果earlySingletonObjects中有,直接返回
  3. 如果singletonFactories中有
    1. 調用ObjectFactory的getObject方法
    2. 將獲取到的bean放入二級緩存中
    3. 從三級緩存中刪除該beanName

在bean完成實例化之后,populateBean之前船侧,會將bean提前曝光欠气。加入到三級緩存中

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {

    .....
    createBeanInstance

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    ....
    populateBean()
    ....
}
循環(huán)依賴的解決.png

當對beanB執(zhí)行populateBean填充屬性的時候,因為A已經加入到三級緩存中了镜撩,所以在getBean的最開始就能從緩存中獲取到已經完成實例化的beanA,完成beanB的屬性填充预柒。

什么情況下的循環(huán)依賴問題可以被spring解決

首先,我們常用的依賴注入方式有如下幾種

  1. 構造函數注入
@Component
public class C {

    private D d;
    public C(@Autowired D d) {
    }

    public void cMethod(){
        d.dMethod();
    }
}

@Component
public class D {
    private C c;
    public D(@Autowired C c) {
        this.c = c;
    }

    public void dMethod(){
        System.out.println("dMethod");
    }
}
@SpringBootApplication
public class MyspringlearningApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
        C c = context.getBean(C.class);
        c.cMethod();

    }
}

運行結果:報循環(huán)依賴異常

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  c defined in file [/Users/lihongli/testWorkSpace/myspringlearning/target/classes/com/li/myspringlearning/C.class]
↑     ↓
|  d defined in file [/Users/lihongli/testWorkSpace/myspringlearning/target/classes/com/li/myspringlearning/D.class]
└─────┘

  1. 屬性注入
@Component
public class A {

    @Autowired
    private B b;

    public void aMethod(){
        b.bMethod();
    }
}

@Component
public class B {
    @Autowired
    private A a;

    public void bMethod(){
        System.out.println("bmethod");
    }
}

運行正常


按照前面的代碼分析袁梗,spring會在完成實例化createBeanInstance方法執(zhí)行完成后宜鸯,才會把bean提前曝光到緩存中,才能讓依賴方獲取到這個提前曝光的bean完成依賴方bean的加載遮怜。如果將依賴寫在構造函數中淋袖,會在實例化C的時候就去加載依賴的D,在D加載過程中也會在他的構造函數中去加載C的bean,C當前處于創(chuàng)建中锯梁,但是沒有進行提前曝光即碗,導致循環(huán)依賴異常

如果將構造器注入的示例改成如下:

@Component
public class C {
    @Autowired
    private D d;
//    public C(@Autowired D d) {
//    }

    public void cMethod(){
        d.dMethod();
    }
}

@Component
public class D {
    private C c;
    public D(@Autowired C c) {
        this.c = c;
    }

    public void dMethod(){
        System.out.println("dMethod");
    }
}
@SpringBootApplication
public class MyspringlearningApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyspringlearningApplication.class, args);
        C c = context.getBean(C.class);
        c.cMethod();

    }
}

讓C在獲取D的bean之前,能把自己提前曝光出去陌凳,就不過報循環(huán)依賴了剥懒。

earlySingletonObjects和singletonFactories

在doCreateBean方法中,完成bean的實例化后合敦,如果允許提前曝光初橘,會把剛剛完成實例化的bean暴露出去

此時加入到三級緩存緩存中。目的是為了在調用getSingleton從三級緩存升級到二級緩存中的時候能執(zhí)行一些拓展的操作

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末充岛,一起剝皮案震驚了整個濱河市保檐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崔梗,老刑警劉巖夜只,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒜魄,居然都是意外死亡盐肃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門权悟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砸王,“玉大人,你說我怎么就攤上這事峦阁∏澹” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵榔昔,是天一觀的道長驹闰。 經常有香客問我瘪菌,道長,這世上最難降的妖魔是什么嘹朗? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任师妙,我火速辦了婚禮,結果婚禮上屹培,老公的妹妹穿的比我還像新娘默穴。我一直安慰自己,他們只是感情好褪秀,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布蓄诽。 她就那樣靜靜地躺著,像睡著了一般媒吗。 火紅的嫁衣襯著肌膚如雪仑氛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天闸英,我揣著相機與錄音锯岖,去河邊找鬼。 笑死甫何,一個胖子當著我的面吹牛出吹,可吹牛的內容都是我干的。 我是一名探鬼主播沛豌,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼赃额!你這毒婦竟也來了加派?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤跳芳,失蹤者是張志新(化名)和其女友劉穎芍锦,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體飞盆,經...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡娄琉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了吓歇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孽水。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖城看,靈堂內的尸體忽然破棺而出女气,到底是詐尸還是另有隱情,我是刑警寧澤测柠,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布炼鞠,位于F島的核電站缘滥,受9級特大地震影響,放射性物質發(fā)生泄漏谒主。R本人自食惡果不足惜朝扼,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望霎肯。 院中可真熱鬧擎颖,春花似錦、人聲如沸姿现。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽备典。三九已至异旧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間提佣,已是汗流浹背吮蛹。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拌屏,地道東北人潮针。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像倚喂,于是被迫代替她去往敵國和親每篷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容