溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dubbo中怎么通過SPI提高框架的可擴展性

發(fā)布時間:2021-08-10 16:07:14 來源:億速云 閱讀:162 作者:Leah 欄目:開發(fā)技術(shù)

這篇文章給大家介紹Dubbo中怎么通過SPI提高框架的可擴展性,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

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

Dubbo中怎么通過SPI提高框架的可擴展性

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

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

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

網(wǎng)絡(luò)通信可以用mina,netty。序列化可以用fastjson,hessian2,java原生的方式等

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

那么Dubbo的SPI是怎么實現(xiàn)的呢?先來了解一下Java SPI

Java SPI

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

  • 定義接口及其對應(yīng)的實現(xiàn)類

  • 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件

  • 文件內(nèi)容為實現(xiàn)類的全路徑名

  • 在代碼中通過java.util.ServiceLoader#load加載具體的實現(xiàn)類

寫個Demo演示一下

Dubbo中怎么通過SPI提高框架的可擴展性

public interface Car {      void getBrand(); }
public class BenzCar implements Car {      @Override     public void getBrand() {         System.out.println("benz");     } }
public class BMWCar implements Car {      @Override     public void getBrand() {         System.out.println("bmw");     } }

org.apache.dubbo.Car的內(nèi)容如下

org.apache.dubbo.BenzCar org.apache.dubbo.BMWCar

測試類

public class JavaSpiDemo {      public static void main(String[] args) {         ServiceLoader<Car> carServiceLoader = ServiceLoader.load(Car.class);         // benz         // bmw         carServiceLoader.forEach(Car::getBrand);     } }

Dubbo SPI

Dubbo中怎么通過SPI提高框架的可擴展性

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

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

org.apache.dubbo.quickstart.Car的內(nèi)容如下

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

測試類

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

@SPI標(biāo)記接口是一個Dubbo SPI接口,即是一個擴展點,value屬性可以指定默認(rèn)實現(xiàn)

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

  • JDK標(biāo)準(zhǔn)的SPI會一次性實例化擴展點的所有實現(xiàn)。而Dubbo SPI能實現(xiàn)按需加載

  • Dubbo SPI增加了對擴展點Ioc和Aop的支持

Dubbo SPI的實現(xiàn)步驟如下

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 定義接口及其對應(yīng)的實現(xiàn)類,接口上加@SPI注解,表明這是一個擴展類

  3. 在META-INF/services目錄下創(chuàng)建以接口全路徑命名的文件

  4. 文件內(nèi)容為實現(xiàn)類的全路徑名

  5. 在代碼中通過ExtensionLoader加載具體的實現(xiàn)類

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

擴展類的構(gòu)造函數(shù)是一個擴展點,則認(rèn)為這個類是一個Wrapper類,即AOP

用例子演示一下

@SPI public interface Car {      void getBrand(); }
public class BenzCar implements Car {     @Override     public void getBrand() {         System.out.println("benz");     } }
public class CarWrapper implements Car {      private Car car;      public CarWrapper(Car car) {         this.car = car;     }      @Override     public void getBrand() {         System.out.println("start");         car.getBrand();         System.out.println("end");     } }

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

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

測試類

public class DubboSpiAopDemo {      public static void main(String[] args) {         ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);         Car car = extensionLoader.getExtension("benz");         // start         // benz         // end         car.getBrand();     } }

BenzCar是一個擴展類,CarWrapper是一個包裝類,當(dāng)獲取BenzCar的時候?qū)嶋H獲取的是被CarWrapper包裝后的對象,類似代理模式

自動加載

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

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

benz=org.apache.dubbo.ioc.BenzCar

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

benz=org.apache.dubbo.ioc.BenzWheel @SPI public interface Wheel {      void getBrandByUrl(); }
public class BenzWheel implements Wheel {      @Override     public void getBrandByUrl() {         System.out.println("benzWheel");     } }
@SPI public interface Car {      void getBrandByUrl(); }
public class BenzCar implements Car {      private Wheel wheel;      public void setWheel(Wheel wheel) {         this.wheel = wheel;     }      @Override     public void getBrandByUrl() {         System.out.println("benzCar");         wheel.getBrandByUrl();     } }

測試demo

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

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

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

自適應(yīng)

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

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

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

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

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

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

生成的Wheel

package org.apache.dubbo.adaptive;  import org.apache.dubbo.adaptive.Wheel; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader;  public class Wheel$Adaptive implements Wheel {     public void getBrandByUrl(URL uRL) {         if (uRL == null) {             throw new IllegalArgumentException("url == null");         }         URL uRL2 = uRL;         String string = uRL2.getParameter("wheel");         if (string == null) {             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());         }         Wheel wheel = (Wheel)ExtensionLoader.getExtensionLoader(Wheel.class).getExtension(string);         wheel.getBrandByUrl(uRL);     } }

@Adaptive可以標(biāo)記在類上或者方法上

標(biāo)記在類上:將該實現(xiàn)類直接作為默認(rèn)實現(xiàn),不再自動生成代碼

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

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

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

<dubbo:application compiler="jdk" />

Compiler類圖如下

Dubbo中怎么通過SPI提高框架的可擴展性

@SPI("javassist") public interface Compiler {      Class<?> compile(String code, ClassLoader classLoader);  }

Compiler用@SPI指定了默認(rèn)實現(xiàn)類為javassist

源碼中獲取Compiler調(diào)用了如下方法

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

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

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

@Adaptive public class AdaptiveCompiler implements Compiler {      private static volatile String DEFAULT_COMPILER;      public static void setDefaultCompiler(String compiler) {         DEFAULT_COMPILER = compiler;     }      /**      * 獲取對應(yīng)的Compiler,并調(diào)用compile做編譯      * 用戶設(shè)置了compiler,就用設(shè)置了的,不然就用默認(rèn)的      */     @Override     public Class<?> compile(String code, ClassLoader classLoader) {         Compiler compiler;         ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);         String name = DEFAULT_COMPILER; // copy reference         if (name != null && name.length() > 0) {             // 用用戶設(shè)置的             compiler = loader.getExtension(name);         } else {             // 用默認(rèn)的             compiler = loader.getDefaultExtension();         }         return compiler.compile(code, classLoader);     }  }

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

自動激活

使用@Activate注解,可以標(biāo)記對應(yīng)的擴展點默認(rèn)被激活使用

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Activate {      // 所屬組,例如消費端,服務(wù)端     String[] group() default {};      // URL中包含屬性名為value的鍵值對,過濾器才處于激活狀態(tài)     String[] value() default {};      // 指定執(zhí)行順序,before指定的過濾器在該過濾器之前執(zhí)行     @Deprecated     String[] before() default {};      // 指定執(zhí)行順序,after指定的過濾器在該過濾器之后執(zhí)行     @Deprecated     String[] after() default {};      // 指定執(zhí)行順序,值越小,越先執(zhí)行     int order() default 0; }

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

Dubbo Filter就是基于這個來實現(xiàn)的。Dubbo Filter是Dubbo可擴展性的一個體現(xiàn),可以在調(diào)用過程中對請求進(jìn)行進(jìn)行增強

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

@SPI public interface MyFilter {      void filter(); }

consumer組能激活這個filter

@Activate(group = {"consumer"}) public class MyConsumerFilter implements MyFilter {     @Override     public void filter() {      } }

provider組能激活這個filter

@Activate(group = {"provider"}) public class MyProviderFilter implements MyFilter {     @Override     public void filter() {      } }

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

@Activate(group = {"consumer", "provider"}) public class MyLogFilter implements MyFilter {     @Override     public void filter() {      } }

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

@Activate(group = {"consumer", "provider"}, value = "cache") public class MyCacheFilter implements MyFilter {     @Override     public void filter() {      } }

測試類如下

getActivateExtension有3個參數(shù),依次為url, key, group

public class ActivateDemo {      public static void main(String[] args) {         ExtensionLoader<MyFilter> extensionLoader = ExtensionLoader.getExtensionLoader(MyFilter.class);         // url中沒有參數(shù)         URL url = URL.valueOf("test://localhost");         List<MyFilter> allFilterList = extensionLoader.getActivateExtension(url, "", null);         /**          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76          * org.apache.dubbo.activate.MyProviderFilter@73a8dfcc          * org.apache.dubbo.activate.MyLogFilter@ea30797          *          * 不指定組則所有的Filter都被激活          */         allFilterList.forEach(item -> System.out.println(item));         System.out.println();          List<MyFilter> consumerFilterList = extensionLoader.getActivateExtension(url, "", "consumer");         /**          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76          * org.apache.dubbo.activate.MyLogFilter@ea30797          *          * 指定consumer組,則只有consumer組的Filter被激活          */         consumerFilterList.forEach(item -> System.out.println(item));         System.out.println();          // url中有參數(shù)myfilter         url = URL.valueOf("test://localhost?myfilter=cache");         List<MyFilter> customerFilter = extensionLoader.getActivateExtension(url, "myfilter", "consumer");         /**          * org.apache.dubbo.activate.MyConsumerFilter@53e25b76          * org.apache.dubbo.activate.MyLogFilter@ea30797          * org.apache.dubbo.activate.MyCacheFilter@aec6354          *          * 指定key在consumer組的基礎(chǔ)上,MyCacheFilter被激活          */         customerFilter.forEach(item -> System.out.println(item));         System.out.println();     } }

總結(jié)一下就是,getActivateExtension不指定組就是激活所有的Filter,指定組則激活指定組的Filter。指定key則從Url中根據(jù)key取到對應(yīng)的value,假設(shè)為cache,然后把@Activate注解中value=cache的Filter激活

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

ExtensionLoader的工作原理

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

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

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. getExtension(),獲取普通擴展類

  3. getAdaptiveExtension(),獲取自適應(yīng)擴展類

  4. getActivateExtension(),獲取自動激活的擴展類

關(guān)于Dubbo中怎么通過SPI提高框架的可擴展性就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI