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

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

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


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

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

Spring中之所以會(huì)出現(xiàn)循環(huán)依賴跟Bean的生命周期有關(guān)系,在創(chuàng)建一個(gè)Bean的過(guò)程中如果依賴的另外一個(gè)Bean還沒有創(chuàng)建,就會(huì)需要去創(chuàng)建依賴的那個(gè)Bean,而如果兩個(gè)Bean相互依賴的話,就會(huì)出現(xiàn)循環(huán)依賴的問(wè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的順序來(lái)實(shí)例化,Spring創(chuàng)建bean的過(guò)程可以分為三個(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í)例過(guò)程中還有一個(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)在整體來(lái)梳理一下,繼續(xù)走A對(duì)象創(chuàng)建的流程,通過(guò)this.singletonFactories.put(beanName, singletonFactory)這個(gè)方法只是添加了一個(gè)工廠,通過(guò)這個(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ì)通過(guò)調(diào)用三級(jí)緩存的facotry,通過(guò)工廠的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)依賴的呢,這里來(lái)一波文字的總結(jié):

Spring通過(guò)三級(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代理,那么通過(guò)這個(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)來(lái)獲取需要的依賴,此時(shí)的getBean(a)會(huì)從緩存中獲取,第一步,先獲取到三級(jí)緩存中的工廠;第二步,調(diào)用對(duì)象工工廠的getObject方法來(lái)獲取到對(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ì)去通過(guò)這個(gè)工廠去真正創(chuàng)建對(duì)象。

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

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

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

  • 首先有一個(gè)配置文件定義了應(yīng)用的基礎(chǔ)包, 也就是Java源碼路徑.
  • 讀取基礎(chǔ)包名, 然后通過(guò)類加載器獲取到應(yīng)用中所有的Class對(duì)象, 存儲(chǔ)到一個(gè)集合中.
  • 獲取應(yīng)用中所有Bean (Controller和Service) 的Class對(duì)象, 通過(guò)反射創(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容器不做過(guò)多講解。

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

(2)初始化階段

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

  • 加載配置文件
  • 掃描用戶配置包下面所有的類
  • 拿到掃描到的類,通過(guò)反射機(jī)制,實(shí)例化。并且放到ioc容器中(Map的鍵值對(duì) beanName-bean) beanName默認(rèn)是首字母小寫
  • 初始化HandlerMapping,這里其實(shí)就是把url和method對(duì)應(yīng)起來(lái)放在一個(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ù)
  • 通過(guò)初始化好的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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容