共計(jì) 2946 個(gè)字符,預(yù)計(jì)需要花費(fèi) 8 分鐘才能閱讀完成。
這篇文章主要介紹“Dubbo 怎么實(shí)現(xiàn)擴(kuò)展機(jī)制”,在日常操作中,相信很多人在 Dubbo 怎么實(shí)現(xiàn)擴(kuò)展機(jī)制問題上存在疑惑,丸趣 TV 小編查閱了各式資料,整理出簡單好用的操作方法,希望對(duì)大家解答”Dubbo 怎么實(shí)現(xiàn)擴(kuò)展機(jī)制”的疑惑有所幫助!接下來,請(qǐng)跟著丸趣 TV 小編一起來學(xué)習(xí)吧!
SPI
Dubbo 就是利用 SPI(Service Provider Interface)來實(shí)現(xiàn)擴(kuò)展機(jī)制的。
這個(gè) SPI 想必你們都很熟悉,在大學(xué)寫數(shù)據(jù)庫大作業(yè)的時(shí)候就碰到了,訪問數(shù)據(jù)庫需要用到 java.sql.Driver。
市面上的數(shù)據(jù)庫五花八門,每個(gè)數(shù)據(jù)庫廠商都有自己的實(shí)現(xiàn),所以肯定需要定制一個(gè)接口,這樣我們面向接口編程即可。
而具體的實(shí)現(xiàn)則可以通過配置來加載,JDK SPI 這時(shí)候就派上用場(chǎng)了。
其實(shí)一點(diǎn)都不神奇,就是約定一個(gè)地方,加載的時(shí)候就去那個(gè)地方找實(shí)現(xiàn)類。
約定一個(gè)地方直白點(diǎn)說就是代碼里面寫死了一個(gè)目錄,這個(gè)目錄就是 META-INF/services/。
然后在這個(gè)目錄下創(chuàng)建一個(gè)文件,用接口全限定名來命名,文件內(nèi)容就是實(shí)現(xiàn)類的全限定名。
到時(shí)候要實(shí)現(xiàn)類就根據(jù)接口名來這里找,然后實(shí)例化就行了。
挺簡單的吧,這就是 JDK SPI,但是它不滿足 Dubbo 的需求。
因?yàn)?Dubbo 把自身的一些實(shí)現(xiàn)也剝離出來成為擴(kuò)展,而這些實(shí)現(xiàn)還是有點(diǎn)多的,也不需要全部用上。
如果用 JDK SPI 會(huì)把配置文件里面的類全部加載,這就導(dǎo)致資源的浪費(fèi)。用的時(shí)候還需要遍歷過去才能找到對(duì)應(yīng)的實(shí)現(xiàn)。
所以 Dubbo 就在 JDK SPI 的基礎(chǔ)上實(shí)現(xiàn)了個(gè) Dubbo 的 SPI,可以根據(jù)指定的名稱按需加載實(shí)現(xiàn)類,比如拿 Cluster 來說就有這么多實(shí)現(xiàn)類。
約定的地方改了一下,一共有三個(gè)目錄。
META-INF/dubbo/internal/:這里是存放 Dubbo 內(nèi)部使用的 SPI 配置文件。
META-INF/dubbo/:這里是存放用戶自定義 SPI 配置文件。
META-INF/services/:兼容 JDK SPI
然后文件里面的內(nèi)容是 key=value 形式,這樣就可以根據(jù) key 找到對(duì)應(yīng)的實(shí)現(xiàn)類。
然后在注解上可以配置默認(rèn)的 key 來選擇默認(rèn)的實(shí)現(xiàn)類,比如 Cluster 默認(rèn)的實(shí)現(xiàn)是 failover。
也可以通過 URL 參數(shù)來選擇實(shí)現(xiàn)類。
還有像 JDK SPI 擴(kuò)展點(diǎn)加載失敗的話,連擴(kuò)展點(diǎn)名稱都拿不到,到時(shí)候報(bào)錯(cuò)也不知道哪里出問題。
而 Dubbo SPI 則不會(huì)吃了錯(cuò)誤,并且還提供了擴(kuò)展點(diǎn)的自動(dòng)注入和 AOP 功能。
大致了解了 Dubbo SPI 之后,我們?cè)賮砩钊肟纯磳?shí)現(xiàn)細(xì)節(jié)。
Dubbo SPI 實(shí)現(xiàn)細(xì)節(jié)
Dubbo SPI 的核心實(shí)現(xiàn)在 ExtensionLoader 中,它負(fù)責(zé)擴(kuò)展點(diǎn)的加載和生命周期的維護(hù),類似 JDK SPI 的 ServiceLoader。
這里要先提一點(diǎn)看源碼的小技巧了。
開源框架都會(huì)有單元測(cè)試,而單元測(cè)試?yán)锩婢蜁?huì)有我們看源碼時(shí)候想要的各種功能實(shí)現(xiàn),我們就可以從單元測(cè)試入手得知一些功能的劃分,然后斷點(diǎn)調(diào)試逐漸深入。
比如今天文章的 ExtensionLoader,它在 dubbo-common 模塊中,咱們就進(jìn)入 test 來看看它測(cè)試用例怎么寫的。
當(dāng)然除了通過文件夾來找,直接用文件名搜也行。
找到了就好辦了,數(shù)據(jù)都是造好的,找到你想要調(diào)試的方法,斷點(diǎn)一設(shè),箭頭一點(diǎn),這不就美滋滋了嗎?
好了,小技巧分享完畢,回到 ExtensionLoader,我們簡單點(diǎn)就用 Dubbo 單元測(cè)試的數(shù)據(jù)來看看實(shí)現(xiàn)。
有個(gè)叫 SimpleExt 的類,有三個(gè)實(shí)現(xiàn),默認(rèn)的實(shí)現(xiàn)是 impl1。
再來看看 SPI 配置文件的內(nèi)容,可以看到為了測(cè)試還故意寫了一些空格在配置文件中。
然后現(xiàn)在如果要找 impl2 這個(gè)實(shí)現(xiàn),通過以下代碼調(diào)用即可。
SimpleExt ext = ExtensionLoader .getExtensionLoader(SimpleExt.class).getExtension(impl2)
一個(gè)擴(kuò)展接口對(duì)應(yīng)有個(gè) ExtensionLoader,找到對(duì)應(yīng)的 ExtensionLoader,然后再加載對(duì)應(yīng)名字的實(shí)現(xiàn)類。
接下來會(huì)有源碼,不過沒關(guān)系,還是很簡單的,想要深入源碼這關(guān)必須過。
可以看到 getExtensionLoader 是靜態(tài)的,里面邏輯也很簡單就是從緩存找接口對(duì)應(yīng)的 ExtensionLoader,找不到就新建一個(gè)返回。
現(xiàn)在有了 ExtensionLoader,咱們?cè)賮砜纯?getExtension 的邏輯,來看看是如何通過擴(kuò)展點(diǎn) name 找到對(duì)應(yīng)的實(shí)現(xiàn)類的。
可以看到又是有個(gè)緩存操作,邏輯非常簡單,先去緩存找實(shí)例,如果沒有則創(chuàng)建實(shí)例。
要說細(xì)節(jié)就是用到了雙檢鎖,然后用 holder 來保證可見性和防止指令重排。應(yīng)該看到注釋上的 holder 構(gòu)造了吧,volatile 和雙檢鎖的搭配,這里就不深入了。
我們來看看 createExtension,這是要?jiǎng)?chuàng)建擴(kuò)展點(diǎn)了,代碼有點(diǎn)長,但是我都做了相應(yīng)的注釋,包括綠色的注釋。
邏輯還是很簡單的,詳細(xì)的代碼沒有具體展示,我先口述一下。
通過接口類名去三個(gè)目錄找到對(duì)應(yīng)的文件。
解析文件內(nèi)容生成 class 對(duì)象,然后緩存到 cachedClasses 中。
然后通過 name 去 cachedClasses 中找到對(duì)應(yīng)的 class 對(duì)象。
去緩存 EXTENSION_INSTANCES 看看是否已經(jīng)實(shí)例化過了。
沒有的話就實(shí)例化,然后調(diào)用 injectExtension 實(shí)現(xiàn)自動(dòng)注入。
再通過 cachedWrapperClasses 實(shí)現(xiàn)包裝,將最后的包裝類返回。
有幾點(diǎn)不清晰沒關(guān)系,咱們接著分析,腦海中先大概知道要做什么,然后再來看看具體是怎么做的。
源碼中的 loadDirectory 就是去目錄找文件,然后解析,最終會(huì)調(diào)用 loadClass,這個(gè)方法很關(guān)鍵,我們?cè)敿?xì)分析一下,為了便于觀看,刪除了一些代碼。
自適應(yīng)咱們先略過,只要知道是在這里記錄的即可。
然后上面提到的 AOP 相關(guān)的 cachedWrapperClasses 就是在這里記錄的,如果判斷它是包裝類呢?
簡單粗暴但是有效,只要有當(dāng)前類作為構(gòu)造器參數(shù)的類就是包裝類,有點(diǎn)拗口,多讀幾遍就理解了。
現(xiàn)在我們?cè)倩剡^頭來看看這段代碼,Dubbo 的 AOP。
把擴(kuò)展類對(duì)應(yīng)的包裝類都記錄下來放在 cachedWrapperClasses 中,然后在實(shí)例化擴(kuò)展類的時(shí)候就一層一層的把擴(kuò)展類包起來,最終返回的就是包裝類。
為什么說這就是 AOP 呢?因?yàn)榈扔诎岩恍┻壿嬊羞M(jìn)了擴(kuò)展實(shí)現(xiàn)類中。
其實(shí)就是把擴(kuò)展對(duì)象的公共邏輯移到包裝類中,我們看下單元測(cè)試的例子就很清晰了。
從圖中可以看到有兩個(gè)擴(kuò)展實(shí)現(xiàn)類,兩個(gè)包裝類,具體邏輯就不看了,不是重點(diǎn),配置文件如下:
然后再看一下單元測(cè)試的運(yùn)行結(jié)果,可以看到最終返回的其實(shí)是 Ext5Wrapper1 對(duì)象,并且它還包著 wrapper2 對(duì)象。
所以 echo 方法的調(diào)用鏈就是:Ext5Wrapper1 – Ext5Wrapper2- Ext5impl1
也就起到了 AOP 的效果。
接下來我們?cè)賮砜纯?injectExtension,是如何實(shí)現(xiàn) Dubbo 的自動(dòng)注入。
到此,關(guān)于“Dubbo 怎么實(shí)現(xiàn)擴(kuò)展機(jī)制”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注丸趣 TV 網(wǎng)站,丸趣 TV 小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!