2. Dubbo原理解析-Dubbo內核實現之基於SPI思想Dubbo內核實現(轉)


轉載自  斬秋的專欄  http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577159


 

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

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM