文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Dubbo如何通过SPI提高框架的可扩展性?

2024-12-24 16:39

关注

介绍

最近看了一下Dubbo的源码,国人写的框架和国外的果然是两种不同的风格,Dubbo的源码还是比较清晰容易懂的。Spring框架一个Bean的初始化过程就能绕死在源码中.

Dubbo的架构是基于分层来设计的,每层执行固定的功能,上层依赖下层,下层的改变对上层不可见,每层都是可以被替换的组件

 

Service和Config为API接口层,让Dubbo使用者方便的发布和引用服务

其他各层均为SPI层,意味着每层都是组件化的,可以被替换

例如,注册中心可以用Redis,Zookeeper。传输协议可以用dubbo,rmi,hessian等。

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

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能

那么Dubbo的SPI是怎么实现的呢?先来了解一下Java SPI

Java SPI

Java SPI是通过策略模式实现的,一个接口提供多个实现类,而使用哪个实现类不在程序中确定,而是配置文件配置的,具体步骤如下

写个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 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 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的优点如下

Dubbo SPI的实现步骤如下

  1. 定义接口及其对应的实现类,接口上加@SPI注解,表明这是一个扩展类
  2. 在META-INF/services目录下创建以接口全路径命名的文件
  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 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 extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         car.getBrandByUrl(); 
  7.     } 

我跑这个代码的时候直接报异常,看了一下官网才发现dubbo是可以注入接口的实现的,但不像spring那么智能,

dubbo必须用URL(类似总线)来指定扩展类对应的实现类.。这就不得不提到@Adaptive注解了

自适应

使用@Adaptive注解,动态的通过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 extensionLoader = ExtensionLoader.getExtensionLoader(Car.class); 
  5.         Car car = extensionLoader.getExtension("benz"); 
  6.         Map 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. # 根据前面的序号选择进入的进程,然后执行下面的命令 
  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可以标记在类上或者方法上

标记在类上:将该实现类直接作为默认实现,不再自动生成代码

标记在方法上:通过参数动态获得实现类,比如上面的例子

用源码演示一下用在类上的@Adaptiv,Dubbo为自适应扩展点生成代码,如我们上面的WheelAdaptive类如下所示¨G30G∗∗@Adaptive可以标记在类上或者方法上∗∗标记在类上:将该实现类直接作为默认实现,不再自动生成代码标记在方法上:通过参数动态获得实现类,比如上面的例子用源码演示一下用在类上的@Adaptiv,Dubbo为自适应扩展点生成代码,如我们上面的WheelAdaptive,但生成的代码还需要编译才能生成class文件。我们可以用JavassistCompiler(默认的)或者JdkCompiler来编译(需要配置),这个小小的功能就用到了@Adaptive

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

  1. "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.     @Override 
  12.     public Class compile(String code, ClassLoader classLoader) { 
  13.         Compiler compiler; 
  14.         ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Compiler.class); 
  15.         String name = DEFAULT_COMPILER; // copy reference 
  16.         if (name != null && name.length() > 0) { 
  17.             // 用用户设置的 
  18.             compiler = loader.getExtension(name); 
  19.         } else { 
  20.             // 用默认的 
  21.             compiler = loader.getDefaultExtension(); 
  22.         } 
  23.         return compiler.compile(code, classLoader); 
  24.     } 
  25.  

从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的键值对,过滤器才处于激活状态 
  10.     String[] value() default {}; 
  11.  
  12.     // 指定执行顺序,before指定的过滤器在该过滤器之前执行 
  13.     @Deprecated 
  14.     String[] before() default {}; 
  15.  
  16.     // 指定执行顺序,after指定的过滤器在该过滤器之后执行 
  17.     @Deprecated 
  18.     String[] after() default {}; 
  19.  
  20.     // 指定执行顺序,值越小,越先执行 
  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 extensionLoader = ExtensionLoader.getExtensionLoader(MyFilter.class); 
  5.         // url中没有参数 
  6.         URL url = URL.valueOf("test://localhost"); 
  7.         List allFilterList = extensionLoader.getActivateExtension(url, ""null); 
  8.          
  9.         allFilterList.forEach(item -> System.out.println(item)); 
  10.         System.out.println(); 
  11.  
  12.         List consumerFilterList = extensionLoader.getActivateExtension(url, """consumer"); 
  13.          
  14.         consumerFilterList.forEach(item -> System.out.println(item)); 
  15.         System.out.println(); 
  16.  
  17.         // url中有参数myfilter 
  18.         url = URL.valueOf("test://localhost?myfilter=cache"); 
  19.         List customerFilter = extensionLoader.getActivateExtension(url, "myfilter""consumer"); 
  20.          
  21.         customerFilter.forEach(item -> System.out.println(item)); 
  22.         System.out.println(); 
  23.     } 

总结一下就是,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个方法的执行过程还是比较简单的,如果你有看不懂的,可以看我给源码加的注释。

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

理解了Dubbo SPI你应该就把Dubbo搞懂一半了,剩下就是一些服务导出,服务引入,服务调用的过程了

本文转载自微信公众号「Java识堂」,可以通过以下二维码关注。转载本文请联系Java识堂公众号。

 

来源: Java识堂内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯