【DUBBO】 Dubbo原理解析-Dubbo內核實現之基於SPI思想Dubbo內核實現

轉載:http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235java

SPI接口定義

定義了@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

 

下面咱們來細講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修改字節碼生成,關於設配類功能做用後續介紹

若是類實現沒有打上@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. 下面 給出總體活動圖

相關文章
相關標籤/搜索