自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Dubbo如何通過SPI提高框架的可擴展性?

開發(fā) 架構
最近看了一下Dubbo的源碼,國人寫的框架和國外的果然是兩種不同的風格,Dubbo的源碼還是比較清晰容易懂的。Spring框架一個Bean的初始化過程就能繞死在源碼中.

[[338213]]

介紹

最近看了一下Dubbo的源碼,國人寫的框架和國外的果然是兩種不同的風格,Dubbo的源碼還是比較清晰容易懂的。Spring框架一個Bean的初始化過程就能繞死在源碼中.

Dubbo的架構是基于分層來設計的,每層執(zhí)行固定的功能,上層依賴下層,下層的改變對上層不可見,每層都是可以被替換的組件

 

Service和Config為API接口層,讓Dubbo使用者方便的發(fā)布和引用服務

其他各層均為SPI層,意味著每層都是組件化的,可以被替換

例如,注冊中心可以用Redis,Zookeeper。傳輸協議可以用dubbo,rmi,hessian等。

網絡通信可以用mina,netty。序列化可以用fastjson,hessian2,java原生的方式等

SPI 全稱為 Service Provider Interface,是一種服務發(fā)現機制。SPI 的本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態(tài)為接口替換實現類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能

那么Dubbo的SPI是怎么實現的呢?先來了解一下Java SPI

Java SPI

Java SPI是通過策略模式實現的,一個接口提供多個實現類,而使用哪個實現類不在程序中確定,而是配置文件配置的,具體步驟如下

  • 定義接口及其對應的實現類
  • 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件
  • 文件內容為實現類的全路徑名
  • 在代碼中通過java.util.ServiceLoader#load加載具體的實現類

寫個Demo演示一下

  1. public interface Car { 
  2.  
  3.     void getBrand(); 
  1. public class BenzCar implements Car { 
  2.  
  3.     @Override 
  4.     public void getBrand() { 
  5.         System.out.println("benz"); 
  6.     } 
  1. public class BMWCar implements Car { 
  2.  
  3.     @Override 
  4.     public void getBrand() { 
  5.         System.out.println("bmw"); 
  6.     } 

org.apache.dubbo.Car的內容如下

  1. org.apache.dubbo.BenzCar 
  2. org.apache.dubbo.BMWCar 

測試類

  1. public class JavaSpiDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ServiceLoader<Car> carServiceLoader = ServiceLoader.load(Car.class); 
  5.         // benz 
  6.         // bmw 
  7.         carServiceLoader.forEach(Car::getBrand); 
  8.     } 

Dubbo SPI

 

用Dubbo SPI將上面的例子改造一下

  1. @SPI 
  2. public interface Car { 
  3.  
  4.     void getBrand(); 
  1. public class BenzCar implements Car { 
  2.  
  3.     @Override 
  4.     public void getBrand() { 
  5.         System.out.println("benz"); 
  6.     } 
  1. public class BMWCar implements Car { 
  2.     @Override 
  3.     public void getBrand() { 
  4.         System.out.println("bmw"); 
  5.     } 

org.apache.dubbo.quickstart.Car的內容如下

  1. benz=org.apache.dubbo.quickstart.BenzCar 
  2. bmw=org.apache.dubbo.quickstart.BMWCar 

測試類

  1. public class DubboSpiDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         car.getBrand(); 
  7.     } 
  1. @Documented 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Target({ElementType.TYPE}) 
  4. public @interface SPI { 
  5.  
  6.     String value() default ""
  7.  

@SPI標記接口是一個Dubbo SPI接口,即是一個擴展點,value屬性可以指定默認實現

Dubbo 并未使用 Java 原生的 SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。Dubbo SPI的優(yōu)點如下

  • JDK標準的SPI會一次性實例化擴展點的所有實現。而Dubbo SPI能實現按需加載
  • Dubbo SPI增加了對擴展點Ioc和Aop的支持

Dubbo SPI的實現步驟如下

  1. 定義接口及其對應的實現類,接口上加@SPI注解,表明這是一個擴展類
  2. 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件
  3. 文件內容為實現類的全路徑名
  4. 在代碼中通過ExtensionLoader加載具體的實現類

Dubbo SPI 擴展點的特性自動包裝

擴展類的構造函數是一個擴展點,則認為這個類是一個Wrapper類,即AOP

用例子演示一下

  1. @SPI 
  2. public interface Car { 
  3.  
  4.     void getBrand(); 
  1. public class BenzCar implements Car { 
  2.     @Override 
  3.     public void getBrand() { 
  4.         System.out.println("benz"); 
  5.     } 
  1. public class CarWrapper implements Car { 
  2.  
  3.     private Car car; 
  4.  
  5.     public CarWrapper(Car car) { 
  6.         this.car = car; 
  7.     } 
  8.  
  9.     @Override 
  10.     public void getBrand() { 
  11.         System.out.println("start"); 
  12.         car.getBrand(); 
  13.         System.out.println("end"); 
  14.     } 

org.apache.dubbo.aop.Car內容如下(resources\META-INF\services目錄下)

  1. benz=org.apache.dubbo.aop.BenzCar 
  2. org.apache.dubbo.aop.CarWrapper 

測試類

  1. public class DubboSpiAopDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         // start 
  7.         // benz 
  8.         // end 
  9.         car.getBrand(); 
  10.     } 

BenzCar是一個擴展類,CarWrapper是一個包裝類,當獲取BenzCar的時候實際獲取的是被CarWrapper包裝后的對象,類似代理模式

自動加載

如果一個擴展類是另一個擴展類的成員變量,并且擁有set方法,框架會自動注入這個擴展點的實例,即IOC。先定義2個擴展點

org.apache.dubbo.ioc.Car(resources\META-INF\services目錄下)

  1. benz=org.apache.dubbo.ioc.BenzCar 

org.apache.dubbo.ioc.Wheel(resources\META-INF\services目錄下)

  1. benz=org.apache.dubbo.ioc.BenzWheel 
  2. @SPI 
  3. public interface Wheel { 
  4.  
  5.     void getBrandByUrl(); 
  1. public class BenzWheel implements Wheel { 
  2.  
  3.     @Override 
  4.     public void getBrandByUrl() { 
  5.         System.out.println("benzWheel"); 
  6.     } 
  1. @SPI 
  2. public interface Car { 
  3.  
  4.     void getBrandByUrl(); 
  1. public class BenzCar implements Car { 
  2.  
  3.     private Wheel wheel; 
  4.  
  5.     public void setWheel(Wheel wheel) { 
  6.         this.wheel = wheel; 
  7.     } 
  8.  
  9.     @Override 
  10.     public void getBrandByUrl() { 
  11.         System.out.println("benzCar"); 
  12.         wheel.getBrandByUrl(); 
  13.     } 

測試demo

  1. public class DubboSpiIocDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         car.getBrandByUrl(); 
  7.     } 

我跑這個代碼的時候直接報異常,看了一下官網才發(fā)現dubbo是可以注入接口的實現的,但不像spring那么智能,

dubbo必須用URL(類似總線)來指定擴展類對應的實現類.。這就不得不提到@Adaptive注解了

自適應

使用@Adaptive注解,動態(tài)的通過URL中的參數來確定要使用哪個具體的實現類

  1. @Documented 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Target({ElementType.TYPE, ElementType.METHOD}) 
  4. public @interface Adaptive { 
  5.  
  6.     String[] value() default {}; 
  7.  
  1. @SPI 
  2. public interface Wheel { 
  3.  
  4.     @Adaptive("wheel"
  5.     void getBrandByUrl(URL url); 
  1. public class BenzWheel implements Wheel { 
  2.  
  3.     @Override 
  4.     public void getBrandByUrl(URL url) { 
  5.         System.out.println("benzWheel"); 
  6.     } 
  1. @SPI 
  2. public interface Car { 
  3.  
  4.     void getBrandByUrl(URL url); 
  1. public class BenzCar implements Car { 
  2.  
  3.     // 這個里面存的是代理對象 
  4.     private Wheel wheel; 
  5.  
  6.     public void setWheel(Wheel wheel) { 
  7.         this.wheel = wheel; 
  8.     } 
  9.  
  10.     @Override 
  11.     public void getBrandByUrl(URL url) { 
  12.         System.out.println("benzCar"); 
  13.         // 代理類根據URL找到實現類,然后再調用實現類 
  14.         wheel.getBrandByUrl(url); 
  15.     } 
  1. public class DubboSpiIocDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         Map<String, String> map = new HashMap<>(); 
  7.         // 指定wheel的實現類為benz 
  8.         map.put("wheel""benz"); 
  9.         URL url = new URL("""", 1, map); 
  10.         // benzCar 
  11.         // benzWheel 
  12.         car.getBrandByUrl(url); 
  13.     } 

可以看到BenzCar對象成功注入了BenzWheel。BenzCar中其實注入的是BenzWheel的代碼對象,這個代理對象會根據@Adaptive("wheel")獲取到wheel,然后從url中找到key為wheel的值,這個值即為實現類對應的key。

上面的注釋提到BenzCar里面注入的Wheel其實是一個代理對象(框架幫我們生成),在代理對象中根據url找到相應的實現類,然后調用實現類。

因為代理對象是框架在運行過程中幫我們生成的,沒有文件可以查看,所以用Arthas來查看一下生成的代理類

  1. curl -O https://alibaba.github.io/arthas/arthas-boot.jar 
  2. java -jar arthas-boot.jar 
  3. # 根據前面的序號選擇進入的進程,然后執(zhí)行下面的命令 
  4. jad org.apache.dubbo.adaptive.Wheel$Adaptive 

生成的Wheel

  1. package org.apache.dubbo.adaptive; 
  2.  
  3. import org.apache.dubbo.adaptive.Wheel; 
  4. import org.apache.dubbo.common.URL; 
  5. import org.apache.dubbo.common.extension.ExtensionLoader; 
  6.  
  7. public class Wheel$Adaptive 
  8. implements Wheel { 
  9.     public void getBrandByUrl(URL uRL) { 
  10.         if (uRL == null) { 
  11.             throw new IllegalArgumentException("url == null"); 
  12.         } 
  13.         URL uRL2 = uRL; 
  14.         String string = uRL2.getParameter("wheel"); 
  15.         if (string == null) { 
  16.             throw new IllegalStateException(new StringBuffer().append("Failed to get extension (org.apache.dubbo.adaptive.Wheel) name from url (").append(uRL2.toString()).append(") use keys([wheel])").toString()); 
  17.         } 
  18.         Wheel wheel = (Wheel)ExtensionLoader.getExtensionLoader(Wheel.class).getExtension(string); 
  19.         wheel.getBrandByUrl(uRL); 
  20.     } 

@Adaptive可以標記在類上或者方法上

標記在類上:將該實現類直接作為默認實現,不再自動生成代碼

標記在方法上:通過參數動態(tài)獲得實現類,比如上面的例子

用源碼演示一下用在類上的@Adaptiv,Dubbo為自適應擴展點生成代碼,如我們上面的WheelAdaptive類如下所示¨G30G∗∗@Adaptive可以標記在類上或者方法上∗∗標記在類上:將該實現類直接作為默認實現,不再自動生成代碼標記在方法上:通過參數動態(tài)獲得實現類,比如上面的例子用源碼演示一下用在類上的@Adaptiv,Dubbo為自適應擴展點生成代碼,如我們上面的WheelAdaptive,但生成的代碼還需要編譯才能生成class文件。我們可以用JavassistCompiler(默認的)或者JdkCompiler來編譯(需要配置),這個小小的功能就用到了@Adaptive

如果想用JdkCompiler需要做如下配置

  1. <dubbo:application compiler="jdk" /> 

Compiler類圖如下

  1. @SPI("javassist"
  2. public interface Compiler { 
  3.  
  4.     Class<?> compile(String code, ClassLoader classLoader); 
  5.  

Compiler用@SPI指定了默認實現類為javassist

源碼中獲取Compiler調用了如下方法

  1. org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 

getAdaptiveExtension()會獲取自適應擴展類,那么這個自適應擴展類是誰呢?

是AdaptiveCompiler,因為類上有@Adaptive注解

  1. @Adaptive 
  2. public class AdaptiveCompiler implements Compiler { 
  3.  
  4.     private static volatile String DEFAULT_COMPILER; 
  5.  
  6.     public static void setDefaultCompiler(String compiler) { 
  7.         DEFAULT_COMPILER = compiler; 
  8.     } 
  9.  
  10.     /** 
  11.      * 獲取對應的Compiler,并調用compile做編譯 
  12.      * 用戶設置了compiler,就用設置了的,不然就用默認的 
  13.      */ 
  14.     @Override 
  15.     public Class<?> compile(String code, ClassLoader classLoader) { 
  16.         Compiler compiler; 
  17.         ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); 
  18.         String name = DEFAULT_COMPILER; // copy reference 
  19.         if (name != null && name.length() > 0) { 
  20.             // 用用戶設置的 
  21.             compiler = loader.getExtension(name); 
  22.         } else { 
  23.             // 用默認的 
  24.             compiler = loader.getDefaultExtension(); 
  25.         } 
  26.         return compiler.compile(code, classLoader); 
  27.     } 
  28.  

從compile方法可以看到,如果用戶設置了編譯方式,則用用戶設置的,如果沒有設置則用默認的,即JavassistCompiler

自動激活

使用@Activate注解,可以標記對應的擴展點默認被激活使用

  1. @Documented 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Target({ElementType.TYPE, ElementType.METHOD}) 
  4. public @interface Activate { 
  5.  
  6.     // 所屬組,例如消費端,服務端 
  7.     String[] group() default {}; 
  8.  
  9.     // URL中包含屬性名為value的鍵值對,過濾器才處于激活狀態(tài) 
  10.     String[] value() default {}; 
  11.  
  12.     // 指定執(zhí)行順序,before指定的過濾器在該過濾器之前執(zhí)行 
  13.     @Deprecated 
  14.     String[] before() default {}; 
  15.  
  16.     // 指定執(zhí)行順序,after指定的過濾器在該過濾器之后執(zhí)行 
  17.     @Deprecated 
  18.     String[] after() default {}; 
  19.  
  20.     // 指定執(zhí)行順序,值越小,越先執(zhí)行 
  21.     int order() default 0; 

可以通過指定group或者value,在不同條件下獲取自動激活的擴展點。before,after,order是用來排序的,感覺一個order參數就可以搞定排序的功能,所以官方把before,after標記為@Deprecated

Dubbo Filter就是基于這個來實現的。Dubbo Filter是Dubbo可擴展性的一個體現,可以在調用過程中對請求進行進行增強

我寫個demo演示一下這個自動激活是怎么工作的

  1. @SPI 
  2. public interface MyFilter {  
  3.     void filter(); 

consumer組能激活這個filter

  1. @Activate(group = {"consumer"}) 
  2. public class MyConsumerFilter implements MyFilter { 
  3.     @Override 
  4.     public void filter() { 
  5.  
  6.     } 

provider組能激活這個filter

  1. @Activate(group = {"provider"}) 
  2. public class MyProviderFilter implements MyFilter { 
  3.     @Override 
  4.     public void filter() { 
  5.  
  6.     } 

consumer組和provide組都能激活這個filter

  1. @Activate(group = {"consumer""provider"}) 
  2. public class MyLogFilter implements MyFilter { 
  3.     @Override 
  4.     public void filter() { 
  5.  
  6.     } 

consumer組和provide組都能激活這個filter,同時url中指定key的value為cache

  1. @Activate(group = {"consumer""provider"}, value = "cache"
  2. public class MyCacheFilter implements MyFilter { 
  3.     @Override 
  4.     public void filter() { 
  5.  
  6.     } 

測試類如下

getActivateExtension有3個參數,依次為url, key, group

  1. public class ActivateDemo { 
  2.  
  3.     public static void main(String[] args) { 
  4.         ExtensionLoader<MyFilter> extensionLoader = ExtensionLoader.getExtensionLoader(MyFilter.class); 
  5.         // url中沒有參數 
  6.         URL url = URL.valueOf("test://localhost"); 
  7.         List<MyFilter> allFilterList = extensionLoader.getActivateExtension(url, ""null); 
  8.         /** 
  9.          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76 
  10.          * org.apache.dubbo.activate.MyProviderFilter@73a8dfcc 
  11.          * org.apache.dubbo.activate.MyLogFilter@ea30797 
  12.          * 
  13.          * 不指定組則所有的Filter都被激活 
  14.          */ 
  15.         allFilterList.forEach(item -> System.out.println(item)); 
  16.         System.out.println(); 
  17.  
  18.         List<MyFilter> consumerFilterList = extensionLoader.getActivateExtension(url, """consumer"); 
  19.         /** 
  20.          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76 
  21.          * org.apache.dubbo.activate.MyLogFilter@ea30797 
  22.          * 
  23.          * 指定consumer組,則只有consumer組的Filter被激活 
  24.          */ 
  25.         consumerFilterList.forEach(item -> System.out.println(item)); 
  26.         System.out.println(); 
  27.  
  28.         // url中有參數myfilter 
  29.         url = URL.valueOf("test://localhost?myfilter=cache"); 
  30.         List<MyFilter> customerFilter = extensionLoader.getActivateExtension(url, "myfilter""consumer"); 
  31.         /** 
  32.          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76 
  33.          * org.apache.dubbo.activate.MyLogFilter@ea30797 
  34.          * org.apache.dubbo.activate.MyCacheFilter@aec6354 
  35.          * 
  36.          * 指定key在consumer組的基礎上,MyCacheFilter被激活 
  37.          */ 
  38.         customerFilter.forEach(item -> System.out.println(item)); 
  39.         System.out.println(); 
  40.     } 

總結一下就是,getActivateExtension不指定組就是激活所有的Filter,指定組則激活指定組的Filter。指定key則從Url中根據key取到對應的value,假設為cache,然后把@Activate注解中value=cache的Filter激活

即group用來篩選,value用來追加,Dubbo Filter就是靠這個屬性激活不同的Filter的

ExtensionLoader的工作原理

ExtensionLoader是整個Dubbo SPI的主要實現類,有如下三個重要方法,搞懂這3個方法基本上就搞懂Dubbo SPI了。

加載擴展類的三種方法如下

  1. getExtension(),獲取普通擴展類
  2. getAdaptiveExtension(),獲取自適應擴展類
  3. getActivateExtension(),獲取自動激活的擴展類

getExtension()上面的例子中已經有了。自適應的特性上面已經演示過了,當獲取Wheel的實現類是框架會調用getAdaptiveExtension()方法。

代碼就不放了,這3個方法的執(zhí)行過程還是比較簡單的,如果你有看不懂的,可以看我給源碼加的注釋。

https://github.com/erlieStar/dubbo-analysis

理解了Dubbo SPI你應該就把Dubbo搞懂一半了,剩下就是一些服務導出,服務引入,服務調用的過程了

本文轉載自微信公眾號「Java識堂」,可以通過以下二維碼關注。轉載本文請聯系Java識堂公眾號。

 

責任編輯:武曉燕 來源: Java識堂
相關推薦

2023-05-17 15:53:21

2021-09-02 09:42:11

測試軟件可擴展性開發(fā)

2022-09-05 15:17:34

區(qū)塊鏈比特幣可擴展性

2021-12-03 14:41:00

云存儲可擴展性存儲

2021-05-17 07:28:23

Spring可擴展性項目

2009-11-30 17:47:24

2010-01-12 09:10:31

Java EE 6Servlet 3.0Web分片

2024-10-10 14:01:34

2012-06-04 11:04:46

虛擬化

2016-10-13 14:38:51

OpenStack可擴展性IT人員

2021-12-09 05:36:16

云存儲可擴展性數據存儲云存儲

2022-05-13 16:05:03

區(qū)塊鏈比特幣可擴展性

2009-04-20 11:33:47

光網絡動態(tài)擴展

2017-01-05 19:29:10

公共云云存儲微軟

2023-10-11 13:46:26

緩存Web應用程序

2010-02-26 15:07:20

WCF單例服務

2020-04-18 11:04:35

物聯網工業(yè)物聯網技術

2020-09-09 14:32:13

邊緣計算

2013-03-19 10:50:38

2018-01-31 07:09:57

數據中心虛擬化可擴展性
點贊
收藏

51CTO技術棧公眾號