Dubbo原理解析-Dubbo核心實現之基於SPI思想Dubbo核心實現(轉)
SPI介面定義
定義了@SPI註解
public @interface SPI { String value() default ""; //指定預設的擴充套件點 }
只有在介面打了@SPI註解的介面類才會去查詢擴充套件點實現
會依次從這幾個檔案中讀取擴充套件點
META-INF/dubbo/internal/ //dubbo內部實現的各種擴充套件都放在了這個目錄了
META-INF/dubbo/
META-INF/services/
我們以Protocol介面為例, 介面上打上SPI註解,預設擴充套件點名字為dubbo
@SPI("dubbo") public interface Protocol{ }
dubbo中內建實現了各種協議如:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等
Dubbo預設rpc模組預設protocol實現DubboProtocol,key為dubbo
下面我們來細講ExtensionLoader類
1. ExtensionLoader.getExtensionLoader(Protocol.class)
每個定義的spi的介面都會構建一個ExtensionLoader例項,儲存在
ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS 這個map物件中
2. loadExtensionClasses 讀取擴充套件點中的實現類
a) 先讀取SPI註解的value值,有值作為預設擴充套件實現的key
b) 依次讀取路徑的檔案
META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol META-INF/services/ com.alibaba.dubbo.rpc.Protocol
3. loadFile逐行讀取com.alibaba.dubbo.rpc.Protocol檔案中的內容,每行內容以key/value形式儲存的。
a) 判斷類實現(如:DubboProtocol)上有米有打上@Adaptive註解,如果打上了註解,將此類作為Protocol協議的設配類快取起來,
讀取下一行;否則適配類通過javasisit修改位元組碼生成,關於設配類功能作用後續介紹
b) 如果類實現沒有打上@Adaptive, 判斷實現類是否存在入參為介面的構造器(就是DubbboProtocol類是否還有入參為Protocol的構造器),
有的話作為包裝類快取到此ExtensionLoader的Set<Class<?>>集合中,這個其實是個裝飾模式
c) 如果即不是設配物件也不是wrapped的物件,那就是擴充套件點的具體實現物件
查詢實現類上有沒有打上@Activate註解,有快取到變數cachedActivates的map中
將實現類快取到cachedClasses中,以便於使用時獲取
4. 獲取或者建立設配物件getAdaptiveExtension
a)如果cachedAdaptiveClass有值,說明有且僅有一個實現類打了@Adaptive, 例項化這個物件返回
b) 如果cachedAdaptiveClass為空, 建立設配類位元組碼。
為什麼要建立設配類,一個介面多種實現,SPI機制也是如此,這是策略模式,但是我們在程式碼執行過程中選擇哪種具體的策略呢。Dubbo採用統一資料模式com.alibaba.dubbo.common.URL(它是dubbo定義的資料模型不是jdk的類),它會穿插於系統的整個執行過程,URL中定義的協議型別欄位protocol,會根據具體業務設定不同的協議。url.getProtocol()值可以是dubbo也是可以webservice, 可以是zookeeper也可以是redis。
設配類的作用是根據url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)選取具體的擴充套件點實現。
所以能夠利用javasist生成設配類的條件
1)介面方法中必須至少有一個方法打上了@Adaptive註解
2)打上了@Adaptive註解的方法引數必須有URL型別引數或者有引數中存在getURL()方法
下面給出createAdaptiveExtensionClassCode()方法生成javasist用來生成Protocol適配類後的程式碼
1 package com.alibaba.dubbo.demo.rayhong.test; 2 3 import com.alibaba.dubbo.common.extension.ExtensionLoader; 4 5 public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { 6 7 // 沒有打上@Adaptive的方法如果被調到拋異常 8 public void destroy() { 9 throw new UnsupportedOperationException( 10 "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() " 11 + "of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 12 13 } 14 15 // 沒有打上@Adaptive的方法如果被調到拋異常 16 public int getDefaultPort() { 17 throw new UnsupportedOperationException( 18 "method public abstractint com.alibaba.dubbo.rpc.Protocol.getDefaultPort() " 19 + "of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 20 } 21 22 // 介面中export方法打上@Adaptive註冊 23 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) { 24 if (arg0 == null) 25 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invokerargument == null"); 26 27 // 引數類中要有URL屬性 28 if (arg0.getUrl() == null) 29 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invokerargument getUrl() == null"); 30 31 // 從入參獲取統一資料模型URL 32 com.alibaba.dubbo.common.URL url = arg0.getUrl(); 33 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); 34 35 // 從統一資料模型URL獲取協議,協議名就是spi擴充套件點實現類的key 36 if (extName == null) 37 throw new IllegalStateException("Fail to getextension(com.alibaba.dubbo.rpc.Protocol) " 38 + "name from url(" + url.toString() + ") usekeys([protocol])"); 39 40 // 利用dubbo服務查詢機制根據名稱找到具體的擴充套件點實現 41 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) 42 ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); 43 44 // 調具體擴充套件點的方法 45 return extension.export(arg0); 46 } 47 48 // 介面中refer方法打上@Adaptive註冊 49 public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) { 50 51 // 統一資料模型URL不能為空 52 if (arg1 == null) 53 throw new IllegalArgumentException("url == null"); 54 55 com.alibaba.dubbo.common.URL url = arg1; 56 57 // 從統一資料模型URL獲取協議,協議名就是spi擴充套件點實現類的key 58 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); 59 if (extName == null) 60 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) " 61 + "name from url(" + url.toString() + ") use keys([protocol])"); 62 63 // 利用dubbo服務查詢機制根據名稱找到具體的擴充套件點實現 64 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) 65 ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); 66 // 調具體擴充套件點的方法 67 68 return extension.refer(arg0, arg1); 69 70 } 71 72 }
5. 通過createAdaptiveExtensionClassCode() 生成如上的java原始碼程式碼,
要被java虛擬機器載入執行必須得編譯成位元組碼,dubbo提供兩種方式去執行程式碼的編譯:
1)利用JDK工具類編譯
2)利用javassit根據原始碼生成位元組碼。
如上圖:
1)生成Adaptive程式碼code
2)利用dubbo的spi擴充套件機制獲取compiler的設配類
3)編譯生成的adaptive程式碼
在此順便介紹下 @Adaptive註解打在實現類上跟打在介面方法上的區別
1)如果有打在介面方法上,調ExtensionLoader.getAdaptiveExtension()獲取設配類,會先通過前面的過程生成java的原始碼,
在通過編譯器編譯成class載入。但是Compiler的實現策略選擇也是通過ExtensionLoader.getAdaptiveExtension(),
如果也通過編譯器編譯成class檔案那豈不是要死迴圈下去了嗎?
ExtensionLoader.getAdaptiveExtension(),對於有實現類上去打了註解@Adaptive的dubbo spi擴充套件機制,它獲取設配類不在通過前面過程生成設配類java原始碼,而是在讀取擴充套件檔案的時候遇到實現類打了註解@Adaptive就把這個類作為設配類快取在ExtensionLoader中,呼叫是直接返回
6. 自動Wrap上擴充套件點的Wrap類
這是一種裝飾模式的實現,在jdk的輸入輸出流實現中有很多這種設計,在於增強擴充套件點功能。這裡我們拿對於Protocol介面的擴充套件點實現作為例項講解。
如圖Protocol繼承關係ProtocolFilterWrapper, ProtocolListenerWrapper這個兩個類是裝飾物件用來增強其他擴充套件點實現的功能。ProtocolFilterWrapper功能主要是在refer 引用遠端服務的中透明的設定一系列的過濾器鏈用來記錄日誌,處理超時,許可權控制等等功能;ProtocolListenerWrapper在provider的exporter,unporter服務和consumer 的refer服務,destory呼叫時新增監聽器,dubbo提供了擴充套件但是沒有預設實現哪些監聽器。
Dubbo是如何自動的給擴充套件點wrap上裝飾物件的呢?
1)在ExtensionLoader.loadFile載入擴充套件點配置檔案的時候
對擴充套件點類有介面型別為引數的構造器就是包轉物件,快取到集合中去
2)在調ExtensionLoader的createExtension(name)根據擴充套件點key建立擴充套件的時候, 先例項化擴充套件點的實現,
在判斷時候有此擴充套件時候有包裝類快取,有的話利用包轉器增強這個擴充套件點實現的功能。如下圖是實現流程
7. IOC 大家所熟知的ioc是spring的三大基礎功能之一, dubbo的ExtensionLoader在載入擴充套件實現的時候
內部實現了個簡單的ioc機制來實現對擴充套件實現所依賴的引數的注入,dubbo對擴充套件實現中公有的set方法且入參個數為一個的方法,
嘗試從物件工廠ObjectFactory獲取值注入到擴充套件點實現中去。
上圖程式碼應該不能理解,下面我們來看看ObjectFactory是如何根據型別和名字來獲取物件的,ObjectFactory也是基於dubbo的spi擴充套件機制的
它跟Compiler介面一樣設配類註解@Adaptive是打在類AdaptiveExtensionFactory上的不是通過javassist編譯生成的。
AdaptiveExtensionFactory持有所有ExtensionFactory物件的集合,dubbo內部預設實現的物件工廠是SpiExtensionFactory和SrpingExtensionFactory,
他們經過TreeMap排好序的查詢順序是優先先從SpiExtensionFactory獲取,如果返回空在從SpringExtensionFactory獲取。
1) SpiExtensionFactory工廠獲取要被注入的物件,就是要獲取dubbo spi擴充套件的實現,
所以傳入的引數型別必須是介面型別並且介面上打上了@SPI註解,返回的是一個設配類物件。
2) SpringExtensionFactory,Dubbo利用spring的擴充套件機制跟spring做了很好的融合。在釋出或者去引用一個服務的時候,會把spring的容器新增到SpringExtensionFactory工廠集合中去, 當SpiExtensionFactory沒有獲取到物件的時候會遍歷SpringExtensionFactory中的spring容器來獲取要注入的物件
8. 下面 給出整體活動圖