最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

幾個直擊靈魂的Spring拷問

2020-12-21 16:41 作者:編程大戰(zhàn)  | 我要投稿

今天這一篇主要想圍繞著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的過程可以分為三個階段:

  1. 實例化,對應(yīng)方法:AbstractAutowireCapableBeanFactory # createBeanInstance方法

  2. 屬性注入,對應(yīng)方法:AbstractAutowireCapableBeanFactory # populateBean方法

  3. 初始化,對應(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)的流程就是這樣子了,一個清晰的流程圖如下:

非AOP的普通循環(huán)依賴

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

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

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

AOP的循環(huán)依賴

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)換成需要的對象



幾個直擊靈魂的Spring拷問的評論 (共 條)

分享到微博請遵守國家法律
福海县| 霍林郭勒市| 平顶山市| 灌云县| 诏安县| 武功县| 旬邑县| 贡觉县| 菏泽市| 咸阳市| 比如县| 金秀| 象山县| 龙海市| 隆安县| 囊谦县| 锦屏县| 柘城县| 资兴市| 营山县| 永顺县| 阿克苏市| 林周县| 灌云县| 通江县| 龙岩市| 拜泉县| 托克逊县| 克山县| 昂仁县| 财经| 永兴县| 旬邑县| 肃北| 甘孜县| 宝应县| 海阳市| 吉木萨尔县| 阜阳市| 大邑县| 凤山市|