[轉]dubbo學習


Comsumer:

 <dubbo:application name="dubbo_consumer"></dubbo:application>

   <!-- 使用zookeeper注冊中心暴露服務地址 -->  

   <dubbo:registry address="zookeeper://192.168.74.129:2181" check="false"></dubbo:registry> 

     <!-- 要引用的服務 -->  

   <dubbo:reference interface="cn.test.dubbo.registry.service.TestRegistryService" id="testRegistryService"></dubbo:reference>

Provider:

 <!-- 提供方應用名稱信息,這個相當於起一個名字,我們dubbo管理頁面比較清晰是哪個應用暴露出來的 -->

   <dubbo:application name="dubbo_provider"></dubbo:application>

   <!-- 使用zookeeper注冊中心暴露服務地址 -->  

   <dubbo:registry address="zookeeper://127.0.0.1:2181" check="false" subscribe="false" register=""></dubbo:registry>

  <!-- 要暴露的服務接口 -->  

  <dubbo:service interface="cn.test.dubbo.registry.service.TestRegistryService" ref="testRegistryService" /> 

 

實現Dubbo的機制:

1, SPI

SPI即service provider interface,為了去除代碼注入,只用提供一個接口,因此,為了在程序中無需動態指明實現類,需要一種服務發現機制,SPI約定如下:

當服務的提供者,提供了服務接口的一種實現之后,在jar包的META-INF/services/目錄里同時創建一個以服務接口命名的文件,該文件包含實現該服務接口的具體實現類的名字。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。 

示例:

1)接口

[java] view plain copy

 

  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public interface Command {  
  7.     public void execute();  
  8. }  

 

2)實現類

[java] view plain copy

 

  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public class ShutdownCommand implements Command{  
  7.     public void execute() {  
  8.         System.out.println("shutdown....");  
  9.     }  
  10. }  

 

[java] view plain copy

 

  1. package com.unei.serviceloader;  
  2.   
  3. /** 
  4.  * Created by sun on 2015/7/25. 
  5.  */  
  6. public class StartCommand implements Command{  
  7.     public void execute() {  
  8.         System.out.println("start....");  
  9.     }  
  10. }  

 

3)配置文件

由於是使用maven構建的項目,所以就在resources下面新建目錄META-INF/services,在該目錄下新建文件com.unei.serviceloader.Command,即完整的接口名

文件內容如下:

[plain] view plain copy

 

  1. com.unei.serviceloader.ShutdownCommand  
  2. com.unei.serviceloader.StartCommand  

 

4)main方法

[java] view plain copy

 

  1. package com.unei.serviceloader;  
  2.   
  3. import java.util.ServiceLoader;  
  4.   
  5. /** 
  6.  * Created by sun on 2015/7/25. 
  7.  */  
  8. public class Main {  
  9.     public static void main(String[] args) {  
  10.         ServiceLoader<Command> serviceLoader=ServiceLoader.load(Command.class);  
  11.         for(Command command:serviceLoader){  
  12.             command.execute();  
  13.         }  
  14.     }  
  15.   
  16. }  

Dubbo底層實現概述:

http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

 

SPI接口定義

定義了@SPI注解

public @interface SPI {

       Stringvalue() 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修改字節碼生成,關於設配類功能作用后續介紹

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

 


免責聲明!

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



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