轉載:http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235java
定義了@SPI註解web
public @interface SPI {redis
Stringvalue() default ""; //指定默認的擴展點spring
} 緩存
只有在接口打了@SPI註解的接口類纔會去查找擴展點實現app
會依次從這幾個文件中讀取擴展點jvm
META-INF/dubbo/internal/ //dubbo內部實現的各類擴展都放在了這個目錄了ide
META-INF/dubbo/工具
META-INF/services/url
咱們以Protocol接口爲例, 接口上打上SPI註解,默認擴展點名字爲dubbo
@SPI("dubbo")
public interface Protocol{
}
dubbo中內置實現了各類協議如:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等
Dubbo默認rpc模塊默認protocol實現DubboProtocol,key爲dubbo
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修改字節碼生成,關於設配類功能做用後續介紹
若是類實現沒有打上@Adaptive, 判斷實現類是否存在入參爲接口的構造器(就是DubbboProtocol類是否還有入參爲Protocol的構造器),有的話做爲包裝類緩存到此ExtensionLoader的Set<Class<?>>集合中,這個實際上是個裝飾模式
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適配類後的代碼
import com.alibaba.dubbo.common.extension;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
//沒有打上@Adaptive的方法若是被調到拋異常
public void destroy() {
throw new UnsupportedOperationException(
"methodpublic abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!")
}
//沒有打上@Adaptive的方法若是被調到拋異常
public int getDefaultPort() {
throw newUnsupportedOperationException(
"method public abstractint com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
//接口中export方法打上@Adaptive註冊
publiccom.alibaba.dubbo.rpc.Exporter export(
com.alibaba.dubbo.rpc.Invokerarg0) throws com.alibaba.dubbo.rpc.Invoker{
if (arg0 == null)
throw newIllegalArgumentException("com.alibaba.dubbo.rpc.Invokerargument == null");
//參數類中要有URL屬性
if(arg0.getUrl() == null)
throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invokerargument getUrl() == null");
//從入參獲取統一數據模型URL
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName =(url.getProtocol() == null ? "dubbo" : url.getProtocol());
//從統一數據模型URL獲取協議,協議名就是spi擴展點實現類的key
if (extName == null) throw new IllegalStateException( "Fail to getextension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") usekeys([protocol])");
//利用dubbo服務查找機制根據名稱找到具體的擴展點實現
com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
//調具體擴展點的方法
return extension.export(arg0);
}
//接口中refer方法打上@Adaptive註冊
publiccom.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
com.alibaba.dubbo.common.URLarg1) throws java.lang.Class {
//統一數據模型URL不能爲空
if (arg1 == null)
throw newIllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url =arg1;
//從統一數據模型URL獲取協議,協議名就是spi擴展點實現類的key
String extName = (url.getProtocol() == null ?"dubbo" : url.getProtocol());
if (extName == null)
thrownewIllegalStateException("Failtogetextension(com.alibaba.dubbo.rpc.Protocol)name from url("+ url.toString() + ") use keys([protocol])");
//利用dubbo服務查找機制根據名稱找到具體的擴展點實現
com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
.getExtension(extName);
//調具體擴展點的方法
return extension.refer(arg0, arg1);
}
}
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和SpringExtensionFactory,他們通過TreeMap排好序的查找順序是優先先從SpiExtensionFactory獲取,若是返回空在從SpringExtensionFactory獲取。
1) SpiExtensionFactory工廠獲取要被注入的對象,就是要獲取dubbo spi擴展的實現,因此傳入的參數類型必須是接口類型而且接口上打上了@SPI註解,返回的是一個設配類對象。
2) SpringExtensionFactory,Dubbo利用spring的擴展機制跟spring作了很好的融合。在發佈或者去引用一個服務的時候,會把spring的容器添加到SpringExtensionFactory工廠集合中去, 當SpiExtensionFactory沒有獲取到對象的時候會遍歷SpringExtensionFactory中的spring容器來獲取要注入的對象
8. 下面 給出總體活動圖