1. 程式人生 > >Dubbo原理解析-Dubbo核心實現之基於SPI思想Dubbo核心實現(轉)

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. 下面 給出整體活動圖