聊聊Dubbo - Dubbo可擴展機制源碼解析

摘要: 在Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。java

在Dubbo可擴展機制實戰中,咱們瞭解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,並本身實現了一個LoadBalance。是否是以爲Dubbo的擴展機制很不錯呀,接下來,咱們就深刻Dubbo的源碼,一睹廬山真面目。api

ExtensionLoader

ExtentionLoader是最核心的類,負責擴展點的加載和生命週期管理。咱們就以這個類開始吧。
Extension的方法比較多,比較經常使用的方法有:緩存

  • public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type)
  • public T getExtension(String name)
  • public T getAdaptiveExtension()

比較常見的用法有:app

  • LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName)
  • RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getAdaptiveExtension()

說明:在接下來展現的源碼中,我會將無關的代碼(好比日誌,異常捕獲等)去掉,方便你們閱讀和理解。框架

*1. getExtensionLoader方法
這是一個靜態工廠方法,入參是一個可擴展的接口,返回一個該接口的ExtensionLoader實體類。經過這個實體類,能夠根據name得到具體的擴展,也能夠得到一個自適應擴展。jvm

*2. getExtension方法函數

getExtention方法中作了一些判斷和緩存,主要的邏輯在createExtension方法中。咱們繼續看createExtention方法。ui

createExtension方法作了如下事情:
*1. 先根據name來獲得對應的擴展類。從ClassPath下META-INF文件夾下讀取擴展點配置文件。url

*2. 使用反射建立一個擴展類的實例spa

*3. 對擴展類實例的屬性進行依賴注入,即IoC。

*4. 若是有wrapper,添加wrapper,即AoP。

下面咱們來重點看下這4個過程
*1. 根據name獲取對應的擴展類
先看代碼:

過程很簡單,先從緩存中獲取,若是沒有,就從配置文件中加載。配置文件的路徑就是以前提到的:

  • META-INF/dubbo/internal
  • META-INF/dubbo
  • META-INF/services

*2. 使用反射建立擴展實例
這個過程很簡單,使用clazz.newInstance())來完成。建立的擴展實例的屬性都是空值。

*3. 擴展實例自動裝配
在實際的場景中,類之間都是有依賴的。擴展實例中也會引用一些依賴,好比簡單的Java類,另外一個Dubbo的擴展或一個Spring Bean等。依賴的狀況很複雜,Dubbo的處理也相對複雜些。咱們稍後會有專門的章節對其進行說明,如今,咱們只須要知道,Dubbo能夠正確的注入擴展點中的普通依賴,Dubbo擴展依賴或Spring依賴等。

*4. 擴展實例自動包裝
自動包裝就是要實現相似於Spring的AOP功能。Dubbo利用它在內部實現一些通用的功能,好比日誌,監控等。關於擴展實例自動包裝的內容,也會在後面單獨講解。

通過上面的4步,Dubbo就建立並初始化了一個擴展實例。這個實例的依賴被注入了,也根據須要被包裝了。到此爲止,這個擴展實例就能夠被使用了。

Dubbo SPI高級用法之自動裝配

自動裝配的相關代碼在injectExtension方法中:

要實現對擴展實例的依賴的自動裝配,首先須要知道有哪些依賴,這些依賴的類型是什麼。Dubbo的方案是查找Java標準的setter方法。即方法名以set開始,只有一個參數。若是擴展類中有這樣的set方法,Dubbo會對其進行依賴注入,相似於Spring的set方法注入。
可是Dubbo中的依賴注入比Spring要複雜,由於Spring注入的都是Spring bean,都是由Spring容器來管理的。而Dubbo的依賴注入中,須要注入的多是另外一個Dubbo的擴展,也多是一個Spring Bean,或是Google guice的組件,或其餘任何一個框架中的組件。Dubbo須要可以從任何一個場景中加載擴展。在injectExtension方法中,是用Object object = objectFactory.getExtension(pt, property)來實現的。objectFactory是ExtensionFactory類型的,在建立ExtensionLoader時被初始化:

objectFacory自己也是一個擴展,經過ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())來獲取。

ExtensionLoader有三個實現:
*1. SpiExtensionLoader:Dubbo本身的Spi去加載Extension

*2. SpringExtensionLoader:從Spring容器中去加載Extension

*3. AdaptiveExtensionLoader: 自適應的AdaptiveExtensionLoader

這裏要注意AdaptiveExtensionLoader,源碼以下:

AdaptiveExtensionLoader類有@Adaptive註解。前面提到了,Dubbo會爲每個擴展建立一個自適應實例。若是擴展類上有@Adaptive,會使用該類做爲自適應類。若是沒有,Dubbo會爲咱們建立一個。因此ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension())會返回一個AdaptiveExtensionLoader實例,做爲自適應擴展實例。
AdaptiveExtentionLoader會遍歷全部的ExtensionFactory實現,嘗試着去加載擴展。若是找到了,返回。若是沒有,在下一個ExtensionFactory中繼續找。Dubbo內置了兩個ExtensionFactory,分別從Dubbo自身的擴展機制和Spring容器中去尋找。因爲ExtensionFactory自己也是一個擴展點,咱們能夠實現本身的ExtensionFactory,讓Dubbo的自動裝配支持咱們自定義的組件。好比,咱們在項目中使用了Google的guice這個IoC容器。咱們能夠實現本身的GuiceExtensionFactory,讓Dubbo支持從guice容器中加載擴展。

Dubbo SPI高級用法之AoP

在用Spring的時候,咱們常常會用到AOP功能。在目標類的方法先後插入其餘邏輯。好比一般使用Spring AOP來實現日誌,監控和鑑權等功能。
Dubbo的擴展機制,是否也支持相似的功能呢?答案是yes。在Dubbo中,有一種特殊的類,被稱爲Wrapper類。經過裝飾者模式,使用包裝類包裝原始的擴展點實例。在原始擴展點實現先後插入其餘邏輯,實現AOP功能。

什麼是Wrapper類

那什麼樣類的纔是Dubbo擴展機制中的Wrapper類呢?Wrapper類是一個有複製構造函數的類,也是典型的裝飾者模式。下面就是一個Wrapper類:

類A有一個構造函數

public A(A a)

,構造函數的參數是A自己。這樣的類就能夠成爲Dubbo擴展機制中的一個Wrapper類。Dubbo中這樣的Wrapper類有ProtocolFilterWrapper, ProtocolListenerWrapper等, 你們能夠查看源碼加深理解。

怎麼配置Wrapper類

在Dubbo中Wrapper類也是一個擴展點,和其餘的擴展點同樣,也是在META-INF文件夾中配置的。好比前面舉例的ProtocolFilterWrapper和ProtocolListenerWrapper就是在路徑dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中配置的:

在Dubbo加載擴展配置文件時,有一段以下的代碼:

這段代碼的意思是,若是擴展類有複製構造函數,就把該類存起來,供之後使用。有複製構造函數的類就是Wrapper類。經過clazz.getConstructor(type)來獲取參數是擴展點接口的構造函數。注意構造函數的參數類型是擴展點接口,而不是擴展類。
以Protocol爲例。配置文件dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol中定義了filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
ProtocolFilterWrapper代碼以下:

ProtocolFilterWrapper有一個構造函數public ProtocolFilterWrapper(Protocol protocol),參數是擴展點Protocol,因此它是一個Dubbo擴展機制中的Wrapper類。ExtensionLoader會把它緩存起來,供之後建立Extension實例的時候,使用這些包裝類依次包裝原始擴展點。

擴展點自適應

前面講到過,Dubbo須要在運行時根據方法參數來決定該使用哪一個擴展,因此有了擴展點自適應實例。實際上是一個擴展點的代理,將擴展的選擇從Dubbo啓動時,延遲到RPC調用時。Dubbo中每個擴展點都有一個自適應類,若是沒有顯式提供,Dubbo會自動爲咱們建立一個,默認使用Javaassist。
先來看下建立自適應擴展類的代碼:

繼續看createAdaptiveExtension方法

繼續看getAdaptiveExtensionClass方法

繼續看createAdaptiveExtensionClass方法,繞了一大圈,終於來到了具體的實現了。看這個createAdaptiveExtensionClass方法,它首先會生成自適應類的Java源碼,而後再將源碼編譯成Java的字節碼,加載到JVM中。

Compiler的代碼,默認實現是javassist。

createAdaptiveExtensionClassCode()方法中使用一個StringBuilder來構建自適應類的Java源碼。方法實現比較長,這裏就不貼代碼了。這種生成字節碼的方式也挺有意思的,先生成Java源代碼,而後編譯,加載到jvm中。經過這種方式,能夠更好的控制生成的Java類。並且這樣也不用care各個字節碼生成框架的api等。由於xxx.java文件是Java通用的,也是咱們最熟悉的。只是代碼的可讀性不強,須要一點一點構建xx.java的內容。
下面是使用createAdaptiveExtensionClassCode方法爲Protocol建立的自適應類的Java代碼範例:

大體的邏輯和開始說的同樣,經過url解析出參數,解析的邏輯由@Adaptive的value參數控制,而後再根據獲得的擴展點名獲取擴展點實現,而後進行調用。若是你們想知道具體的構建.java代碼的邏輯,能夠看createAdaptiveExtensionClassCode的完整實現。
在生成的Protocol$Adpative中,發現getDefaultPort和destroy方法都是直接拋出異常的,這是爲何呢?來看看Protocol的源碼:

能夠看到Protocol接口中有4個方法,但只有export和refer兩個方法使用了@Adaptive註解。Dubbo自動生成的自適應實例,只有@Adaptive修飾的方法纔有具體的實現。因此,Protocol$Adpative類中,也只有export和refer這兩個方法有具體的實現,其他方法都是拋出異常。

原文連接

相關文章
相關標籤/搜索