幾個(gè)直擊靈魂的Spring拷問(七)

今天這一篇主要想圍繞著Spring的循環(huán)依賴問題以及終極靈魂拷問如何手寫Spring的問題講講。

一刊愚、Spring循環(huán)依賴


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

Spring中的循環(huán)依賴一直是Spring中一個(gè)很重要的話題,一方面是因?yàn)樵创a中為了解決循環(huán)依賴做了很多處理踩验,另外一方面是因?yàn)槊嬖嚨臅r(shí)候鸥诽,如果問到Spring中比較高階的問題,那么循環(huán)依賴必定逃不掉箕憾。所以還是可以看一下這塊的源碼牡借,看看Spring是如何解決循環(huán)依賴的問題的。

Spring中之所以會(huì)出現(xiàn)循環(huán)依賴跟Bean的生命周期有關(guān)系袭异,在創(chuàng)建一個(gè)Bean的過程中如果依賴的另外一個(gè)Bean還沒有創(chuàng)建钠龙,就會(huì)需要去創(chuàng)建依賴的那個(gè)Bean,而如果兩個(gè)Bean相互依賴的話,就會(huì)出現(xiàn)循環(huán)依賴的問題碴里。體現(xiàn)到代碼層次就是像下面這個(gè)樣子的沈矿,比如兩個(gè)對(duì)象相互依賴:

@Component
public class A {
    // A中注入了B
    @Autowired
    private B b;
}

@Component
public class B {
    // B中也注入了A
    @Autowired
    private A a;
}

或者自己依賴自己

@Component
public class A {
    // A中注入了A
    @Autowired
    private A a;
}

2.三級(jí)緩存方案

假設(shè)按照上面代碼Class A 和 B,按照從A->B的順序來實(shí)例化咬腋,Spring創(chuàng)建bean的過程可以分為三個(gè)階段:
1羹膳、實(shí)例化,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # createBeanInstance方法
2根竿、屬性注入陵像,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # populateBean方法
3、初始化寇壳,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory # initializeBean

所以執(zhí)行順序是先在這個(gè)類中的 AbstractBeanFactory 按調(diào)用鏈執(zhí)行如下三個(gè)方法:

//AbstractBeanFactory 
1醒颖、getBean("a")
2、doGetBean("a")
3壳炎、getSingleton("a") 

在調(diào)用getSingleton(a)方法泞歉,這個(gè)方法又會(huì)調(diào)用getSingleton(beanName, true),所以才進(jìn)入到下面這個(gè)方法:

//DefaultSingletonBeanRegistry
public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}
getSingleton(beanName, true)

這是個(gè)重點(diǎn)方法冕广,該方法實(shí)際上就是到緩存中嘗試去獲取Bean疏日,整個(gè)緩存分為三級(jí)
singletonObjects,一級(jí)緩存撒汉,存儲(chǔ)的是所有創(chuàng)建好了的單例Bean
earlySingletonObjects沟优,完成實(shí)例化,但是還未進(jìn)行屬性注入及初始化的對(duì)象
singletonFactories睬辐,提前暴露的一個(gè)單例工廠挠阁,二級(jí)緩存中存儲(chǔ)的就是從這個(gè)工廠中獲取到的對(duì)象

//DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //一級(jí)緩存中獲取-->完整bean
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                //二級(jí)緩存中-->獲取未屬性注入的bean
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    //三級(jí)緩存中-->獲取工廠
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //三級(jí)緩存中的工廠getObject的對(duì)象-->放入二級(jí)緩存中
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

因?yàn)槭堑谝淮蝿?chuàng)建,因此上面的三級(jí)緩存都未命中溯饵,此時(shí)會(huì)進(jìn)入getSingleton的另外一個(gè)重載方法getSingleton(beanName, singletonFactory)侵俗。這里我們知道 singletonFactory 是需要等待createBean(beanName, mbd, args) 方法的返回,然后作為第二個(gè)輸入?yún)?shù)給到下面 getSingleton 方法丰刊。

createBean 方法的返回將作為 getSingleton 的輸入隘谣,然后會(huì)進(jìn)入到下面這段代碼中:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //省略...
                try {
                    //入?yún)⒌膌ambda會(huì)提供一個(gè)singletonFactory
                    //調(diào)用createBean方法創(chuàng)建一個(gè)Bean后返回
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                //省略...
                if (newSingleton) {
                    //添加到一級(jí)緩存singletonObjects中
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

上面的代碼主要實(shí)現(xiàn)了:將已經(jīng)完全創(chuàng)建好了的單例Bean放入一級(jí)緩存中。在前面一步 createBean()方法的創(chuàng)建實(shí)例過程中還有一個(gè) doCreateBean 方法啄巧,里面還有這樣一段代碼:

這個(gè)也就是在Bean實(shí)例化后寻歧,屬性注入之前Spring將Bean包裝成一個(gè)工廠添加進(jìn)了三級(jí)緩存中,addSingletonFactory 對(duì)應(yīng)源碼如下:

// 這里傳入的參數(shù)也是一個(gè)lambda表達(dá)式秩仆,() -> getEarlyBeanReference(beanName, mbd, bean)
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三級(jí)緩存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

那么getEarlyBeanReference方法又做了什么呢码泛?進(jìn)入源碼看下:

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;
}
非AOP的二級(jí)緩存

這個(gè)地方的BeanPostProcessor后置處理器,只在處理AOP的實(shí)例對(duì)象時(shí)才會(huì)發(fā)揮作用澄耍,如果不考慮AOP噪珊,代碼就是:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
}

可見晌缘,對(duì)于非Aop實(shí)例對(duì)象,這個(gè)工廠直接將實(shí)例化階段創(chuàng)建的對(duì)象返回了痢站!

現(xiàn)在整體來梳理一下磷箕,繼續(xù)走A對(duì)象創(chuàng)建的流程,通過this.singletonFactories.put(beanName, singletonFactory)這個(gè)方法只是添加了一個(gè)工廠瑟押,通過這個(gè)工廠(ObjectFactory)的getObject方法可以得到一個(gè)對(duì)象搀捷。當(dāng)A完成了實(shí)例化并添加進(jìn)了三級(jí)緩存后,就要開始為A進(jìn)行屬性注入了多望,在注入時(shí)發(fā)現(xiàn)A依賴了B嫩舟,那么這個(gè)時(shí)候Spring又會(huì)去getBean(b),然后反射調(diào)用setter方法完成屬性注入怀偷。因?yàn)锽需要注入A家厌,所以在創(chuàng)建B的時(shí)候,又會(huì)去調(diào)用getBean(a)椎工,這個(gè)時(shí)候就又回到之前的流程了饭于,但是不同的是,之前的getBean是為了創(chuàng)建Bean维蒙,而此時(shí)再調(diào)用getBean不是為了創(chuàng)建了掰吕,而是要從緩存中獲取,因?yàn)橹癆在實(shí)例化后已經(jīng)將其放入了三級(jí)緩存singletonFactories中颅痊,此時(shí)getBean(a)的二級(jí)緩存會(huì)通過調(diào)用三級(jí)緩存的facotry殖熟,通過工廠的getObject方法將對(duì)象放入到二級(jí)緩存中并返回,所以此時(shí)getBean(a)的流程就是這樣子了斑响,一個(gè)清晰的流程圖如下:

非AOP的普通循環(huán)依賴
結(jié)合了AOP的循環(huán)依賴

如果在開啟AOP的情況下菱属,那么就是調(diào)用 getEarlyBeanReference 方法對(duì)應(yīng)的源碼如下:

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果需要代理,返回一個(gè)代理對(duì)象舰罚,不需要代理纽门,直接返回當(dāng)前傳入的這個(gè)bean對(duì)象
    return wrapIfNecessary(bean, beanName, cacheKey);
}

對(duì)A進(jìn)行了AOP代理的話,那么此時(shí)getEarlyBeanReference將返回一個(gè)代理后的對(duì)象营罢,而不是實(shí)例化階段創(chuàng)建的對(duì)象赏陵,這樣就意味著B中注入的A將是一個(gè)代理對(duì)象而不是A的實(shí)例化階段創(chuàng)建后的對(duì)象。整個(gè)注入的流程圖就變成了如下:

AOP的循環(huán)依賴

圖片和思路整理自《面試必殺技饲漾,講一講Spring中的循環(huán)依賴》瘟滨,想要徹底搞懂可以刷這個(gè)文章理解。

3.循環(huán)依賴的總結(jié)

1能颁、Spring到底是如何解決循環(huán)依賴的呢,這里來一波文字的總結(jié):

Spring通過三級(jí)緩存解決了循環(huán)依賴倒淫,其中一級(jí)緩存為單例池(singletonObjects),二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects伙菊,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories)。當(dāng)A、B兩個(gè)類發(fā)生循環(huán)引用時(shí)镜硕,在A完成實(shí)例化后运翼,就使用實(shí)例化后的對(duì)象去創(chuàng)建一個(gè)對(duì)象工廠,并添加到三級(jí)緩存中兴枯,如果A被AOP代理血淌,那么通過這個(gè)工廠獲取到的就是A代理后的對(duì)象,如果A沒有被AOP代理财剖,那么這個(gè)工廠獲取到的就是A實(shí)例化的對(duì)象悠夯。當(dāng)A進(jìn)行屬性注入時(shí),會(huì)去創(chuàng)建B躺坟,同時(shí)B又依賴了A沦补,所以創(chuàng)建B的同時(shí)又會(huì)去調(diào)用getBean(a)來獲取需要的依賴,此時(shí)的getBean(a)會(huì)從緩存中獲取咪橙,第一步夕膀,先獲取到三級(jí)緩存中的工廠;第二步美侦,調(diào)用對(duì)象工工廠的getObject方法來獲取到對(duì)應(yīng)的對(duì)象产舞,得到這個(gè)對(duì)象后將其注入到B中。緊接著B會(huì)走完它的生命周期流程菠剩,包括初始化易猫、后置處理器等。當(dāng)B創(chuàng)建完后赠叼,會(huì)將B再注入到A中擦囊,此時(shí)A再完成它的整個(gè)生命周期。至此嘴办,循環(huán)依賴結(jié)束瞬场!

2、為啥要用三級(jí)緩存涧郊,是否可以用二級(jí)緩存

在普通的循環(huán)依賴的情況下贯被,三級(jí)緩存沒有任何作用。三級(jí)緩存實(shí)際上跟Spring中的AOP相關(guān)妆艘。AOP場(chǎng)景下的getEarlyBeanReference 會(huì)拿到一個(gè)代理的對(duì)象彤灶,但是不確定有沒有依賴,需不需要用到這個(gè)依賴對(duì)象批旺,所以先給一個(gè)工廠放到三級(jí)緩存里幌陕。

這個(gè)工廠的目的在于延遲對(duì)實(shí)例化階段生成的對(duì)象的代理,只有真正發(fā)生循環(huán)依賴的時(shí)候汽煮,才去提前生成代理對(duì)象搏熄,否則只會(huì)創(chuàng)建一個(gè)工廠并將其放入到三級(jí)緩存中棚唆,但是不會(huì)去通過這個(gè)工廠去真正創(chuàng)建對(duì)象。

二心例、如何手寫一個(gè)Spring框架

1宵凌、一個(gè)手寫IoC容器的思路

IOC的實(shí)現(xiàn)思路如下:

  • 首先有一個(gè)配置文件定義了應(yīng)用的基礎(chǔ)包, 也就是Java源碼路徑.
  • 讀取基礎(chǔ)包名, 然后通過類加載器獲取到應(yīng)用中所有的Class對(duì)象, 存儲(chǔ)到一個(gè)集合中.
  • 獲取應(yīng)用中所有Bean (Controller和Service) 的Class對(duì)象, 通過反射創(chuàng)建實(shí)例, 然后存儲(chǔ)到 Bean容器中.
  • 遍歷Bean容器中的所有Bean, 為所有帶 @Autowired 注解的屬性注入實(shí)例.
  • IOC操作要在應(yīng)用啟動(dòng)時(shí)就完成, 所以必須寫在靜態(tài)代碼塊中.

仿寫spring容器

2、一個(gè)手寫SpringMVC的思路

(1)讀取配置

SpringMVC本質(zhì)上是一個(gè)Servlet,這個(gè) Servlet 繼承自 HttpServlet止后。FrameworkServlet負(fù)責(zé)初始化SpringMVC的容器瞎惫,并將Spring容器設(shè)置為父容器。因?yàn)楸疚闹皇菍?shí)現(xiàn)SpringMVC译株,對(duì)于Spring容器不做過多講解瓜喇。

為了讀取web.xml中的配置,我們用到ServletConfig這個(gè)類古戴,它代表當(dāng)前Servlet在web.xml中的配置信息欠橘。通過web.xml中加載我們自己寫的MyDispatcherServlet和讀取配置文件。

(2)初始化階段

在前面我們提到DispatcherServlet的initStrategies方法會(huì)初始化9大組件现恼,但是這里將實(shí)現(xiàn)一些SpringMVC的最基本的組件而不是全部肃续,按順序包括:

  • 加載配置文件
  • 掃描用戶配置包下面所有的類
  • 拿到掃描到的類,通過反射機(jī)制叉袍,實(shí)例化始锚。并且放到ioc容器中(Map的鍵值對(duì) beanName-bean) beanName默認(rèn)是首字母小寫
  • 初始化HandlerMapping,這里其實(shí)就是把url和method對(duì)應(yīng)起來放在一個(gè)k-v的Map中,在運(yùn)行階段取出
(3)運(yùn)行階段

每一次請(qǐng)求將會(huì)調(diào)用doGet或doPost方法喳逛,所以統(tǒng)一運(yùn)行階段都放在doDispatch方法里處理瞧捌,它會(huì)根據(jù)url請(qǐng)求去HandlerMapping中匹配到對(duì)應(yīng)的Method,然后利用反射機(jī)制調(diào)用Controller中的url對(duì)應(yīng)的方法润文,并得到結(jié)果返回姐呐。按順序包括以下功能:

  • 異常的攔截
  • 獲取請(qǐng)求傳入的參數(shù)并處理參數(shù)
  • 通過初始化好的handlerMapping中拿出url對(duì)應(yīng)的方法名,反射調(diào)用

仿寫springmvc容器

3典蝌、一個(gè)手寫SpringMVC的思路

1 掃描 aop 包曙砂, 獲取 aspect 的類
2 根據(jù) 切點(diǎn) 獲取該切點(diǎn)的 類 和 方法
3 根據(jù)配置的 類 和 方法 為該類生成一個(gè)代理對(duì)象
4 將改代理對(duì)象放入 bean Map 中
5 調(diào)用的時(shí)候 將代理對(duì)象 轉(zhuǎn)換成需要的對(duì)象

仿寫spring aop容器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市骏掀,隨后出現(xiàn)的幾起案子鸠澈,更是在濱河造成了極大的恐慌,老刑警劉巖截驮,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笑陈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡葵袭,警方通過查閱死者的電腦和手機(jī)涵妥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坡锡,“玉大人妹笆,你說我怎么就攤上這事块请。” “怎么了拳缠?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贸弥。 經(jīng)常有香客問我窟坐,道長(zhǎng),這世上最難降的妖魔是什么绵疲? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任哲鸳,我火速辦了婚禮,結(jié)果婚禮上盔憨,老公的妹妹穿的比我還像新娘徙菠。我一直安慰自己,他們只是感情好郁岩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布婿奔。 她就那樣靜靜地躺著,像睡著了一般问慎。 火紅的嫁衣襯著肌膚如雪萍摊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天如叼,我揣著相機(jī)與錄音冰木,去河邊找鬼。 笑死笼恰,一個(gè)胖子當(dāng)著我的面吹牛踊沸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播社证,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼逼龟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了猴仑?” 一聲冷哼從身側(cè)響起审轮,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辽俗,沒想到半個(gè)月后疾渣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崖飘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年榴捡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朱浴。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吊圾,死狀恐怖达椰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情项乒,我是刑警寧澤啰劲,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站檀何,受9級(jí)特大地震影響蝇裤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜频鉴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一栓辜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧垛孔,春花似錦藕甩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羡藐,卻和暖如春贩毕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仆嗦。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工辉阶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘩扼。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓谆甜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親集绰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子规辱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348