轉載自 斬秋的專欄 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. 下面 給出整體活動圖