四、Dubbo擴展點加載機制
4.1 加載機制概述
-
Dubbo良好的擴展性與框架中針對不同場景使用合適設計模式、加載機制密不可分
-
Dubbo幾乎所有功能組件都是基于擴展機制(SPI)實現的
-
Dubbo SPI 沒有直接使用 Java SPI,在它思想上進行改進,并兼容 Java SPI
4.1.1 Java SPI -
Java SPI(Service Provider Interface)使用了策略模式,一個接口多種實現,具體實現由程序之外的配置掌控。具體步驟:
- 定義一個接口及對應的方法
- 編寫該接口的一個實現類
- 在 META-INF/services/目錄下,創建一個以接口全路徑命名的文件,如com.test.spi.PrintService
- 文件內容為具體實現類的全路徑名,如果有多個,則用分行符分隔
- 在代碼中通過java.util.ServiceLoader來加載具體的實現類
4.1.2 擴展點加載機制的改進
-
Java SPI加載失敗,可能會因為各種原因導致異常信息被“吞掉”,導致開發人員問題追蹤比較困難。Dubbo SPI在擴展加載失敗的時候會先拋出真實異常并打印日志。擴展點在被動加載的時候,即使有部分擴展加載失敗也不會影響其他擴展點和整個框架的使用
-
Dubbo SPI自己實現了 IoC和AOP機制。一個擴展點可以通過setter方法直接注入其他擴展的方法
-
Dubbo 支持包裝擴展類,推薦把通用的抽象邏輯放到包裝類中,用于實現擴展點的AOP特性
4.1.3 擴展點的配置規范 -
Dubbo SPI 配置規范
-
4.1.4 擴展點的分類與緩存
-
Dubbo SPI 緩存
- Class緩存:Dubbo SPI獲取擴展類時,會先從緩存中讀取。如果緩存中不存在,則加載配置文件,根據配置把Class緩存到內存中,并不會直接全部初始化
- 實例緩存:基于性能考慮,Dubbo框架中不僅緩存Class,也會緩存Class實例化后的對象。每次獲取的時候,會先從緩存中讀取,如果緩存中讀不到,則重新加載并緩存起來。這也是為什么Dubbo SPI相對Java SPI性能上有優勢的原因,因為Dubbo SPI緩存的Class并不會全部實例化,而是按需實例化并緩存,因此性能更好。
4.1.5 擴展點的特性
-
擴展類特性:自動包裝、自動加載、自適應和自動激活
- 自動包裝:ExtensionLoader在加載擴展時,如果發現這個擴展類包含其他擴展點作為構造函數的參數,則這個擴展類就會被認為是Wrapper類
- 自動加載:如果某個擴展類是另外一個擴展點類的成員屬性,并且擁有setter方法,那么框架也會自動注入對應的擴展點實例
- 自適應:使用@Adaptive注解,可以動態地通過URL中的參數來確定要使用哪個具體的實現類。從而解決自動加載中的實例注入問題
- 自動激活:使用@Activate注解,可以標記對應的擴展點默認被激活啟用
4.2 擴展點注解
4.2.1 擴展點注解:@SPI
-
標記這個接口是一個Dubbo SPI接口,即是一個擴展點,可以有多個不同的內置或用戶定義的實現
-
Dubbo中很多地方通過getExtension (Class type, String name)來獲取擴展點接口的具體實現,此時會對傳入的Class做校驗,判斷是否是接口,以及是否有@SPI注解,兩者缺一不可
4.2.2 擴展點自適應注解:@Adaptive -
在整個Dubbo框架中,只有幾個地方使用在類級別上,如AdaptiveExtensionFactory和AdaptiveCompiler,其余都標注在方法上
-
方法級別注解可以通過參數動態獲得實現類,在第一次getExtension時,會自動生成和編譯一個動態的Adaptive類,從而達到動態實現類的效果
4.2.3 擴展點自動激活注解:@Activate -
有多個擴展點實現、需要根據不同條件被激活的場景中,如Filter需要多個同時激活,因為每個Filter實現的是不同的功能
4.3 ExtensionLoader 的工作原理
4.3.1 工作流程 -
ExtensionLoader 的邏輯入口可以分為 getExtension、getAdaptiveExtension、getActivateExtension三個,分別是獲取普通擴展類、獲取自適應擴展類、獲取自動激活的擴展類
-
-
4.4 擴展點動態編譯的實現
4.4.1 總體結構 -
Dubbo中有三種代碼編譯器,分別是JDK編譯器、Javassist編譯器和AdaptiveCompiler編譯器
-
-
- Compiler接口上含有一個SPI注解,注解的默認值是@SPI(”javassist”),即Javassist編譯器將作為默認編譯器
-
AdaptiveCompiler上面@Adaptive注解,說明AdaptiveCompiler會固定為默認實現
4.4.2 Javassist 動態代碼編譯 -
初始化Javassist,設置默認參數,如設置當前的classpath
-
通過正則匹配出所有import的包,并使用Javassist添加import
-
通過正則匹配出所有extends的包,創建Class對象,并使用Javassist添加extends
-
通過正則匹配出所有implements包,并使用Javassist添加implements
-
通過正則匹配出類里面所有內容,即得到{}中的內容,再通過正則匹配出所有方法,并使用Javassist添加類方法
-
生成Class對象
4.4.3 JDK 動態代碼編譯 -
初始化一個JavaFileObject對象,并把代碼字符串作為參數傳入構造方法
-
調用JavaCompiler.CompilationTask方法編譯出具體的類
-
JavaFileManager負責管理類文件的輸入/輸出位置