幾個直擊靈魂的Spring拷問
今天這一篇主要想圍繞著Spring的循環(huán)依賴問題以及終極靈魂拷問如何手寫Spring的問題講講。
一、Spring循環(huán)依賴
1、什么是循環(huán)依賴
Spring中的循環(huán)依賴一直是Spring中一個很重要的話題,一方面是因為源碼中為了解決循環(huán)依賴做了很多處理,另外一方面是因為面試的時候,如果問到Spring中比較高階的問題,那么循環(huán)依賴必定逃不掉。所以還是可以看一下這塊的源碼,看看Spring是如何解決循環(huán)依賴的問題的。
Spring中之所以會出現(xiàn)循環(huán)依賴跟Bean的生命周期有關(guān)系,在創(chuàng)建一個Bean的過程中如果依賴的另外一個Bean還沒有創(chuàng)建,就會需要去創(chuàng)建依賴的那個Bean,而如果兩個Bean相互依賴的話,就會出現(xiàn)循環(huán)依賴的問題。體現(xiàn)到代碼層次就是像下面這個樣子的,比如兩個對象相互依賴:

或者自己依賴自己

2、三級緩存方案
假設(shè)按照上面代碼Class A 和 B,按照從A->B的順序來實例化,Spring創(chuàng)建bean的過程可以分為三個階段:
實例化,對應(yīng)方法:
AbstractAutowireCapableBeanFactory # createBeanInstance
方法屬性注入,對應(yīng)方法:
AbstractAutowireCapableBeanFactory # populateBean
方法初始化,對應(yīng)方法:
AbstractAutowireCapableBeanFactory # initializeBean
所以執(zhí)行順序是先在這個類中的 AbstractBeanFactory 按調(diào)用鏈執(zhí)行如下三個方法:

在調(diào)用getSingleton(a)
方法,這個方法又會調(diào)用getSingleton(beanName, true)
,所以才進入到下面這個方法:

getSingleton(beanName, true)
這是個重點方法,該方法實際上就是到緩存中嘗試去獲取Bean,整個緩存分為三級
singletonObjects,一級緩存,存儲的是所有創(chuàng)建好了的單例Bean
earlySingletonObjects,完成實例化,但是還未進行屬性注入及初始化的對象
singletonFactories,提前暴露的一個單例工廠,二級緩存中存儲的就是從這個工廠中獲取到的對象。

因為是第一次創(chuàng)建,因此上面的三級緩存都未命中,此時會進入getSingleton的另外一個重載方法getSingleton(beanName, singletonFactory)。這里我們知道 singletonFactory 是需要等待createBean(beanName, mbd, args) 方法的返回,然后作為第二個輸入?yún)?shù)給到下面 getSingleton 方法。

createBean 方法的返回將作為 getSingleton 的輸入,然后會進入到下面這段代碼中:

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

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

那么getEarlyBeanReference方法又做了什么呢?進入源碼看下:

非AOP的二級緩存
這個地方的BeanPostProcessor后置處理器,只在處理AOP的實例對象時才會發(fā)揮作用,如果不考慮AOP,代碼就是:

可見,對于非Aop實例對象,這個工廠直接將實例化階段創(chuàng)建的對象返回了!
現(xiàn)在整體來梳理一下,繼續(xù)走A對象創(chuàng)建的流程,通過this.singletonFactories.put(beanName, singletonFactory)
這個方法只是添加了一個工廠,通過這個工廠(ObjectFactory)的getObject方法可以得到一個對象。當A完成了實例化并添加進了三級緩存后,就要開始為A進行屬性注入了,在注入時發(fā)現(xiàn)A依賴了B,那么這個時候Spring又會去getBean(b),然后反射調(diào)用setter方法完成屬性注入。因為B需要注入A,所以在創(chuàng)建B的時候,又會去調(diào)用getBean(a),這個時候就又回到之前的流程了,但是不同的是,之前的getBean是為了創(chuàng)建Bean,而此時再調(diào)用getBean不是為了創(chuàng)建了,而是要從緩存中獲取,因為之前A在實例化后已經(jīng)將其放入了三級緩存singletonFactories中,此時getBean(a)的二級緩存會通過調(diào)用三級緩存的facotry,通過工廠的getObject方法將對象放入到二級緩存中并返回,所以此時getBean(a)的流程就是這樣子了,一個清晰的流程圖如下:

結(jié)合了AOP的循環(huán)依賴
如果在開啟AOP的情況下,那么就是調(diào)用 getEarlyBeanReference 方法對應(yīng)的源碼如下:

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

3、循環(huán)依賴的總結(jié)
1、Spring到底是如何解決循環(huán)依賴的呢,這里來一波文字的總結(jié):
Spring通過三級緩存解決了循環(huán)依賴,其中一級緩存為單例池(singletonObjects),二級緩存為早期曝光對象earlySingletonObjects,三級緩存為早期曝光對象工廠(singletonFactories)。當A、B兩個類發(fā)生循環(huán)引用時,在A完成實例化后,就使用實例化后的對象去創(chuàng)建一個對象工廠,并添加到三級緩存中,如果A被AOP代理,那么通過這個工廠獲取到的就是A代理后的對象,如果A沒有被AOP代理,那么這個工廠獲取到的就是A實例化的對象。當A進行屬性注入時,會去創(chuàng)建B,同時B又依賴了A,所以創(chuàng)建B的同時又會去調(diào)用getBean(a)來獲取需要的依賴,此時的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調(diào)用對象工工廠的getObject方法來獲取到對應(yīng)的對象,得到這個對象后將其注入到B中。緊接著B會走完它的生命周期流程,包括初始化、后置處理器等。當B創(chuàng)建完后,會將B再注入到A中,此時A再完成它的整個生命周期。至此,循環(huán)依賴結(jié)束!
2、為啥要用三級緩存,是否可以用二級緩存
在普通的循環(huán)依賴的情況下,三級緩存沒有任何作用。三級緩存實際上跟Spring中的AOP相關(guān)。AOP場景下的getEarlyBeanReference 會拿到一個代理的對象,但是不確定有沒有依賴,需不需要用到這個依賴對象,所以先給一個工廠放到三級緩存里。
這個工廠的目的在于延遲對實例化階段生成的對象的代理,只有真正發(fā)生循環(huán)依賴的時候,才去提前生成代理對象,否則只會創(chuàng)建一個工廠并將其放入到三級緩存中,但是不會去通過這個工廠去真正創(chuàng)建對象。
二、如何手寫一個Spring框架
1、一個手寫IoC容器的思路
IOC的實現(xiàn)思路如下:
首先有一個配置文件定義了應(yīng)用的基礎(chǔ)包, 也就是Java源碼路徑。
讀取基礎(chǔ)包名, 然后通過類加載器獲取到應(yīng)用中所有的Class對象, 存儲到一個集合中。
獲取應(yīng)用中所有Bean (Controller和Service) 的Class對象, 通過反射創(chuàng)建實例, 然后存儲到 Bean容器中。
遍歷Bean容器中的所有Bean, 為所有帶 @Autowired 注解的屬性注入實例。
IOC操作要在應(yīng)用啟動時就完成, 所以必須寫在靜態(tài)代碼塊中。
2、一個手寫SpringMVC的思路
(1)讀取配置
SpringMVC本質(zhì)上是一個Servlet,這個 Servlet 繼承自 HttpServlet。FrameworkServlet負責(zé)初始化SpringMVC的容器,并將Spring容器設(shè)置為父容器。因為本文只是實現(xiàn)SpringMVC,對于Spring容器不做過多講解。
為了讀取web.xml中的配置,我們用到ServletConfig這個類,它代表當前Servlet在web.xml中的配置信息。通過web.xml中加載我們自己寫的MyDispatcherServlet和讀取配置文件。
(2)初始化階段
在前面我們提到DispatcherServlet的initStrategies方法會初始化9大組件,但是這里將實現(xiàn)一些SpringMVC的最基本的組件而不是全部,按順序包括:
加載配置文件
掃描用戶配置包下面所有的類
拿到掃描到的類,通過反射機制,實例化。并且放到ioc容器中(Map的鍵值對 beanName-bean) beanName默認是首字母小寫
初始化HandlerMapping,這里其實就是把url和method對應(yīng)起來放在一個k-v的Map中,在運行階段取出
(3)運行階段
每一次請求將會調(diào)用doGet或doPost方法,所以統(tǒng)一運行階段都放在doDispatch方法里處理,它會根據(jù)url請求去HandlerMapping中匹配到對應(yīng)的Method,然后利用反射機制調(diào)用Controller中的url對應(yīng)的方法,并得到結(jié)果返回。按順序包括以下功能:
異常的攔截
獲取請求傳入的參數(shù)并處理參數(shù)
通過初始化好的handlerMapping中拿出url對應(yīng)的方法名,反射調(diào)用
3、一個手寫SpringMVC的思路
1)掃描 aop 包, 獲取 aspect 的類
2)根據(jù) 切點 獲取該切點的 類 和 方法
3)根據(jù)配置的 類 和 方法 為該類生成一個代理對象
4)將改代理對象放入 bean Map 中
5)調(diào)用的時候 將代理對象 轉(zhuǎn)換成需要的對象