JavaEE 企业级分布式高级架构师(九)Dubbo(2.7)学习笔记(3)

Dubbo的系统架构解析

两大设计原则

Dubbo 框架在设计时遵循了两大设计原则:

  • Dubbo 使用"微内核+插件"的设计模式。内核只负责组装插件(扩展点),Dubbo 的功能都是由插件实现的。Dubbo作为一个优秀的 RPC 框架,一个 Apache 的顶级项目,其最大的亮点就是其优秀的"无限开放性"设计架构——"微内核+插件"的架构设计思想,使得其几乎所有组件均可方便的进行扩展、增强、替换。这是 Dubbo 最重要的设计原则。
  • 采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。标准的URL格式:
protocol://username:password@host:port/path?key1=value1&key2=value2

三大领域模型

为了对 Dubbo 整体架构叙述的方便,Dubbo 抽象出了三大领域模型。

  • Protocol 服务域:是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
  • Invoker 实体域:是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
  • Invocation 会话域:它持有调用过程中的变量,比如方法名,参数等。

四大组件

在这里插入图片描述
Dubbo中存在四大组件:

  • Provider:暴露服务方,亦称为服务提供者。
  • Consumer:调用远程服务方,亦称为服务消费者。
  • Registry:服务注册与发现的中心,提供服务列表,亦称为服务注册中心。
  • Monitor:统计服务的调用次数、调用时间等信息的日志服务,亦称为服务监控中心。

十层架构

在这里插入图片描述

  • Dubbo 的架构设计划分为了 10 层,图中左边淡蓝色背景为服务 Consumer 使用的接口,右边淡绿色背景为服务 Provider 使用的接口,位于中轴线的为双方都要用到的接口。对于这10 层,根据其总体功能划分,可以划分为三大层:
    • Business层
      • Service 服务接口层
    • RPC层
      • Config 配置层
      • Proxy 服务代理层
      • Registry 注册中心层
      • Cluster 路由层
      • Monitor 监控层
      • Protocol 远程调用层
    • Remoting层
      • Exchange 信息交换层
      • Transport 网络传输层
      • Serialize 数据序列化层

Business层

Service 服务层
  • 该层仅包含一个 service 服务层,该层与实际业务逻辑有关,根据服务消费方和服务提供方的业务设计,实现对应的接口。

RPC层

  • 该层主要负责整个分布式系统中各个主机间的通讯,其包含了以下 6 层。
Config 配置层
  • 以 ServiceConfig 和 ReferenceConfig 为中心,用于加载并解析 Spring 配置文件中的 Dubbo标签。

主要是<dubbo:reference/>和<dubbo:service/>标签

Proxy 服务代理层
  • 服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton,以 ServiceProxy 为中心,扩展接口为 ProxyFactory(效果就是客户端和服务端只需要关注Service层定义的接口即可)。
  • Proxy 层封装了所有接口的透明化代理,而在其它层都以 Invoker 为中心,只有到了暴露给用户使用时,才用 Proxy 将Invoker 转成接口,或将接口实现转成 Invoker,也就是去掉 Proxy 层 RPC 是可以运行的,只是不那么透明,不那么看起来像调本地服务一样调远程服务。(PS:接口指的就是我们定义的各种业务接口)
Registry 注册中心层
  • 封装服务地址的注册和发现,以服务 URL 为中心,扩展接口为 RegistryFactory、Registry、RegistryService,可能没有服务注册中心,此时服务提供方直接暴露服务。
Cluster (集群) 路由层
  • 封装多个提供者的路由和负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为Cluster、Directory、Router 和 LoadBalance,将多个服务提供方组合为一个服务提供方,实现对服务消费透明。只需要与一个服务提供方进行交互。
  • Dubbo 官方指出,在 Dubbo 的整体架构中,Cluster 只是一个外围概念。Cluster 的目的是将多个 Invoker 伪装成一个 Invoker,这样用户只需关注 Protocol 层 Invoker 即可,加上Cluster 或者去掉 Cluster 对其它层都不会造成影响,因为只有一个提供者时,是不需要Cluster 的。
Monitor 监控层
  • RPC 调用时间和次数监控,以 Statistics 为中心,扩展接口 MonitorFactory、Monitor 和MonitorService。
Protocol 远程调用层

封装 RPC 调用,以 Invocation 和 Result 为中心,扩展接口为 Protocol、Invoker 和 Exporter。

  • Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理。
  • Invoker 是实体域,它是 Dubbo 的核心模型,其他模型都是向它靠拢,或转换成它,它代表一个可执行体,可向它发起 Invoker 调用,它有可能是一个本地实现,也有可能是一个远程实现,也有可能是一个集群实现。
  • Exporter 代表服务暴露,消费者只能调用提供者暴露的服务

在 RPC 中,Protocol 是核心层,也就是只要有 Protocol + Invoker + Exporter 就可以完成非透明的 RPC 调用,然后在 Invoker 的主过程上加 Filter 拦截点。

Remoting层

  • Remoting 实现是 Dubbo 协议的实现,如果我们选择 RMI 协议,整个 Remoting 都不会用上,Remoting 内部再划为 Transport 传输层和 Exchange 信息交换层,Transport 层只负责单向消息传输,是对 Mina、Netty、Grizzly的抽象,它也可以扩展 UDP 传输,而 Exchange层是在传输层之上封装了 Request-Response 语义。
  • 具体包含以下三层:
Exchange 信息交换层
  • 封装请求响应模式,同步转异步,以 Request 和 Response 为中心,扩展接口为 Exchanger 和 ExchangeChannel,ExchangeClient 和 ExchangeServer。
Transport 网络传输层
  • 抽象和 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel、Transporter、Client、Server 和 Codec。
Serialize 数据序列化层
  • 可复用的一些工具,扩展接口为 Serialization、ObjectInput、ObejctOutput 和 ThreadPool。

框架模块解析

将 Dubbo 源码工程导入 Idea

  • 这里以 dubbo-2.7.3 为例进行源码解析。从官网下载到源码的 zip 包后解压后就可以直接导入到 Idea 中。

Dubbo2.7 版本与 2.6 版本

  • Dubbo2.7 版本需要 Java 8 及以上版本。
  • 2.7.0 版本在改造的过程中遵循了一个原则,即保持与低版本的兼容性,因此从功能层面来说它是与 2.6.x 及更低版本完全兼容的。2.7 与 2.6 版本相比,改动最大的就是包名,由原来的 com.alibaba.dubbo 改为了 org.apache.dubbo。

Dubbo源码模块介绍

在这里插入图片描述

  • dubbo-cluster 集群模块:该模块主要负责服务的提供者列表、路由、负责均衡等功能。
  • directory 列表:封装了现有的所有 Invoker 提供者信息,负载均衡就是从它里面获取到的内容进行负载均衡的。

在这里插入图片描述

  • dubbo-common 通用模块:一些通用功能、内核功能全在这里。

在这里插入图片描述

  • dubbo-compatible 兼容模块:

在这里插入图片描述

  • dubbo-config 配置模块、dubbo-configcenter 配置中心模块、dubbo-container 容器模块:

在这里插入图片描述

  • dubbo-demo 演示模块:

在这里插入图片描述

  • dubbo-filter 过滤器模块:

在这里插入图片描述

  • dubbo-metadata-report 元数据-报告模块:该模块负责两个功能,元数据的定义和元数据的持久化(即将元数据“报告”到哪里)

在这里插入图片描述

  • dubbo-monitor 监控模块、dubbo-plugin 插件模块

在这里插入图片描述

  • dubbo-registry 注册模块:从该模块中可以看到dubbo支持哪些应用作为注册中心

在这里插入图片描述

  • dubbo-remoting 远程通信模块:

在这里插入图片描述

  • dubbo-rpc 远程调用模块:该模块放的是各种远程调用协议(也叫服务暴露协议)的实现。其中有个比较特殊的injvm,它是一个伪协议,本地暴露协议,处理消费者调用自己本地服务的情况(如果调用的服务本地也提供了直接调用本地服务)。

在这里插入图片描述

  • dubbo-serialization 序列化模块:处理数据序列化的。

在这里插入图片描述

Dubbo内核解析

  • 所谓 Dubbo 的内核是指, Dubbo 中所有功能都是基于它之上完成的,都是由它作为基础的。 Dubbo 内核的工作原理由四部分构成:服务发现机制SPI 、自适应机制 Adaptive 、包装机制 Wrapper 与激活机制 Activate。 Dubbo 通过这四种机制实现了对插件的 IOC、AOP,实现了对自动生成类的动态编译 Compile。

服务发现机制SPI

JDK的SPI

JDK的SPI简介

在这里插入图片描述

  • SPI,Service Provider Interface:服务提供者接口,是一种服务发现机制。
JDK的SPI规范

JDK 的 SPI 规范定义

  • 接口名:可随意定义
  • 实现类名:可随意定义
  • 提供者配置文件路径:在依次查找的目录为 META-INF/services 目录
  • 提供者配置文件名称:接口的全限定性类名,没有扩展名
  • 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行
  • 提供者加载:在代码中通过 ServiceLoader 类可以加载 META-INF/services 文件中所有的实现类,并创建响应的实例
    “SPI规范”的说明在 ServiceLoader 类的说明中有具体体现(这是JDK10中的说明,与JDK8中的描述稍有不同,但意思相同)。

【原文】A service provider that is packaged as a JAR file for the class path(被打包为类路径下的一个 Jar 文件) is identified by placing a provider-configuration file in the resource directory {@code META-INF/services}. The name of the provider-configuration file is the fully qualified binary name of the service. The provider-configuration file contains a list of fully qualified binary names of service providers, one per line.
【翻译】通过在资源目录 META-INF/services 中放置一个“提供者配置文件”的方式定义了一个服务提供者。提供者配置文件的名称是 service 的全限定性二进制名称。提供者配置文件包含了一个全限定二进制服务提供者名称列表,一行一个。

代码演示 11-spi-jdk
  • 创建一个 Maven 的 Java 工程,命名为 11-spi-jdk。
  • 创建两个实现类:在 com.yw.dubbo.example.service包下。
public class SomeServiceOneImpl implements SomeService {
    @Override
    public String hello(String name) {
        System.out.println("执行SomeServiceOneImpl的hello()方法");
        return "hello " + name;
    }
}
public class SomeServiceTwoImpl implements SomeService {
    @Override
    public String hello(String name) {
        System.out.println("执行SomeServiceTwoImpl的hello()方法");
        return "hello " + name;
    }
}
  • 在 src/main/resources 中创建目录 META-INF/services,并在其中创建一个文件,名称为接口的全限定性类名 com.yw.dubbo.example.service.SomeService。

在这里插入图片描述

  • 测试:
public class SpiTest {
    public static void main(String[] args) {
        // 加载提供者配置文件,创建提供者类加载器
        ServiceLoader<SomeService> loader = ServiceLoader.load(SomeService.class);
        // ServiceLoader本身就是一个迭代器
        Iterator<SomeService> it = loader.iterator();
        // 迭代加载每一个实现类,并生成每一个提供者对象
        while (it.hasNext()) {
            SomeService service = it.next();
            System.out.println(service.hello(service.getClass().getSimpleName()));
        }
    }
}
// output:
//    执行SomeServiceOneImpl的hello()方法
//    hello SomeServiceOneImpl
//    执行SomeServiceTwoImpl的hello()方法
//    hello SomeServiceTwoImpl

Dubbo的SPI

  • Dubbo 并没有直接使用 JDK 的 SPI,而是在其基础之上对其进行了改进(JDK 的 SPI 存在的问题:无法加载指定的提供者,其会将所有配置文件中的提供者全部加载并创建实例)。
规范说明

Dubbo 的 SPI 规范是:

  • 接口名:可以随意定义,例如 ThreadPool。
  • 实现类名:在接口名前添加一个用于表示自身功能的“标识字符串”,例如 FixedThreadPool、CachedThreadPool、LimitedThreadPool 等。
  • 提供者配置文件路径:依次查找的目录为 META-INF/dubbo/internal 目录META-INF/dubbo 目录META-INF/services 目录
  • 提供者配置文件名称:接口的全限定性类名,没有扩展名。
  • 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识字符串(首字母小写)”。一个类名占一行。
  • 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例。
代码演示 11-spi-dubbo
  • 复制 11-spi-jdk 工程,并重命名为 11-spi-dubbo,在此基础修改。
  • 定义SPI接口:
@SPI("alipay")
public interface Order {
    /**
     * 支付方式
     */
    String way();
}
  • 定义两个Order接口的实现类:
public class AlipayOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用支付宝支付 ---");
        return "支付宝支付";
    }
}
public class WechatOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用微信支付 ---");
        return "微信支付";
    }
}
  • 定义扩展类配置文件:

在这里插入图片描述

  • 测试一:
@Test
public void test01() {
    // 获取SPI接口Order的loader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    // 指定要加载并创建的扩展类实例
    Order alipay = loader.getExtension("alipay");
    System.out.println(alipay.way());
    Order wechat = loader.getExtension("wechat");
    System.out.println(wechat.way());

    Order xxx = loader.getExtension("xxx");
    System.out.println(xxx.way());
}

在这里插入图片描述

  • 测试二:
@Test
public void test02() {
    // 获取SPI接口Order的loader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getExtension(null);
    System.out.println(order.way());
}

在这里插入图片描述

  • 测试三:
@Test
public void test03() {
    // 获取SPI接口Order的loader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getExtension("true");
    System.out.println(order.way());
}

在这里插入图片描述

自适应机制 Adaptive

  • Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive注解实现的。一个 SPI 接口最多只会有一个 Adaptive 类(自定义的或自动生成的)

@Adaptive注解

  • @Adaptive 注解可以修饰类与方法,其作用相差很大。
@Adaptive修饰方法
  • 被@Adapative 修饰的 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive 方法。
@Adaptive修饰类
  • 有些 SPI 接口中的方法不需要 URL 相关的参数,此时就可以直接让@Adaptive 来修饰某个 SPI 接口的实现类,由该类实现对 SPI 扩展类的自适应。
  • 其是装饰者设计模式的应用。

Adaptive方法规范

  • 下面我们准备要定义 Adaptive 方法。那么 Adaptive 方法的定义有什么要求呢?我们通过查看动态生成的 Adaptive 类来总结 Adaptive 方法的要求。
动态生成 Adaptive 类格式
package <SPI 接口所在包>;
public class SPI 接口名$Adpative implements SPI 接口 {
	public adaptiveMethod (arg0, arg1, ...) {
		// 注意,下面的判断仅对 URL 类型,或可以获取到 URL 类型值的参数进行判断
		// 例如,dubbo 的 Invoker 类型中就包含有 URL 属性
		if(arg1==null) throw new IllegalArgumentException(异常信息)if(arg1.getUrl()==null) throw new IllegalArgumentException(异常信息)URL url = arg1.getUrl();
		// 其会根据@Adaptive 注解上声明的 Key 的顺序,从 URL 获取 Value,
		// 作为实际扩展类。若有默认扩展类,则获取默认扩展类名;否则获取
		// 指定扩展名名。
		String extName = url.get 接口名() == null?默认扩展前辍名:url.get 接口名();
		
		if(extName==null) throw new IllegalStateException(异常信息);
		
		SPI 接口 extension = ExtensionLoader.getExtensionLoader(SPI 接口.class).getExtension(extName);
		
		return extension.adaptiveMethod(arg0, arg1, ...);
	}
	//未被标记的方法尝试用自适应类调用会抛异常
	public unAdaptiveMethod( arg0, arg1, ...) {
		throw new UnsupportedOperationException(异常信息);
	}
}
  • 其中,adaptiveMethod表示SPI接口中被@Adaptive标记的方法,unAdaptiveMethod表示没有被标记的方法。
方法规范
  • 从前面的动态生成的 Adaptive 类中的 adaptiveMethod()方法体可知,其对于要加载的扩展名的指定方式是通过 URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅一条:**其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。**方法调用者是通过URL 传递要加载的扩展名的。

Adaptive方法 12-adaptive-method

  • 复制 11-spi-dubbo 工程,并重命名为 12-adaptive-method,在此基础上修改。
  • 修改SPI接口:
@SPI("alipay")
public interface Order {
    String way();
    
    @Adaptive
    String pay(URL url);
}
  • 修改两个扩展类:

在这里插入图片描述

  • 测试一:
@Test
public void test01() {
    // 获取SPI接口Order的loader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    // 获取到Order的自适应实例
    Order order = loader.getAdaptiveExtension();
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx");
    System.out.println(order.pay(url));
    System.out.println(order.way());
}

在这里插入图片描述

  • 测试二:
@Test
public void test02() {
    // 获取SPI接口Order的loader实例
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    // 获取到Order的自适应实例
    Order order = loader.getAdaptiveExtension();
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx?order=wechat");
    System.out.println(order.pay(url));
    System.out.println(order.way());
}

在这里插入图片描述

示例再变一下

  • SPI接口:

在这里插入图片描述

  • 接口实现:

在这里插入图片描述

  • 测试:SPI接口如果是多个单词拼接的驼峰命令,则需要拆分单词并用“.”分隔。
@Test
public void test03() {
    // 获取SPI接口GoodsOrder的loader实例
    ExtensionLoader<GoodsOrder> loader = ExtensionLoader.getExtensionLoader(GoodsOrder.class);
    // 获取到GoodsOrder的自适应实例
    GoodsOrder order = loader.getAdaptiveExtension();
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx?goods.order=wechat");
    System.out.println(order.pay(url));
    System.out.println(order.way());
}

Adaptive类 12-adaptive-class

  • 复制 12-adaptive-method 工程,并重命名为 12-adaptive-class,在此基础上修改。
  • SPI接口和实现类:
@SPI("alipay")
public interface Order {
    String way();
}
public class AlipayOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用支付宝支付 ---");
        return "支付宝支付";
    }
}
public class WechatOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用微信支付 ---");
        return "微信支付";
    }
}
  • 定义 Adaptive 类:
@Adaptive
public class AdaptiveOrder implements Order {
    private String defaultName;

    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

    @Override
    public String way() {
        ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
        Order order = StringUtils.isEmpty(defaultName) ? loader.getDefaultExtension()
                : loader.getExtension(defaultName);
        return order.way();
    }
}
  • 注意:AdaptiveOrder 也需要注册:

在这里插入图片描述

  • 测试一:
@Test
public void test01() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getAdaptiveExtension();
    System.out.println(order.way());
}
  • 测试二:
@Test
public void test02() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getAdaptiveExtension();
    ((AdaptiveOrder) order).setDefaultName("wechat");
    System.out.println(order.way());
}
  • 在 Dubbo 框架中,一共就两个Adaptive类:AdaptiveExtensionFactory、AdaptiveCompiler

在这里插入图片描述

在这里插入图片描述

包装机制 Wrapper

  • Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是 Wrapper 设计模式的应用。一个 SPI 可以包含多个 Wrapper。

Wrapper 类规范

  • Wrapper 机制不是通过注解实现的,而是通过一套 Wrapper 规范实现的。Wrapper 类在定义时需要遵循如下规范:
    • 该类要实现 SPI 接口
    • 该类中要有 SPI 接口的引用
    • 该类中 SPI 接口实例是通过仅包含一个 SPI 接口参数的带参构造器传的
    • 在接口实现方法中要调用 SPI 接口引用对象的相应方法
    • 该类名称以 Wrapper 结尾

代码演示 13-wrapper

  • 复制 12-adaptive-method 工程,并重命名为 13-wrapper,在此基础上修改。
  • 定义两个 wrapper 类:
public class OneOrderWrapper implements Order {
    private Order order;

    public OneOrderWrapper(Order order) {
        this.order = order;
    }

    @Override
    public String way() {
        System.out.println("Before OneOrderWrapper 对 way() 增强");
        String result = order.way();
        System.out.println("After OneOrderWrapper 对 way() 增强");
        return result;
    }

    @Override
    public String pay(URL url) {
        System.out.println("Before OneOrderWrapper 对 pay() 增强");
        String result = order.pay(url);
        System.out.println("After OneOrderWrapper 对 pay() 增强");
        return result;
    }
}
public class TwoOrderWrapper implements Order {
    private Order order;

    public TwoOrderWrapper(Order order) {
        this.order = order;
    }

    @Override
    public String way() {
        System.out.println("Before TwoOrderWrapper 对 way() 增强");
        String result = order.way();
        System.out.println("After TwoOrderWrapper 对 way() 增强");
        return result;
    }

    @Override
    public String pay(URL url) {
        System.out.println("Before TwoOrderWrapper 对 pay() 增强");
        String result = order.pay(url);
        System.out.println("After TwoOrderWrapper 对 pay() 增强");
        return result;
    }
}
  • 修改扩展类配置文件:将这两个 wrapper 注册到扩展类配置文件中。

在这里插入图片描述

  • 测试一:
@Test
public void test01() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getAdaptiveExtension();
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx");
    System.out.println(order.pay(url));
//        System.out.println(order.way());  // 会报错
}

在这里插入图片描述

  • 测试二:
@Test
public void test02() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    Order order = loader.getAdaptiveExtension();
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx?order=wechat");
    System.out.println(order.pay(url));
}

在这里插入图片描述

  • 测试三:
@Test
public void test03() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    // 获取该SPI接口的所有直接扩展类:即该扩展类直接对该SPI接口进行业务功能上的扩展,可以单独使用
    Set<String> extensions = loader.getSupportedExtensions();
    System.out.println(extensions);
}

在这里插入图片描述

激活机制 Activate

  • 用于一次激活多个扩展类的。
  • Activate 机制,即扩展类的激活机制。通过指定的条件来激活当前的扩展类。其是通过 @Activate 注解实现的。

@Activate 注解

  • 在 @Activate 注解中共有五个属性,其中 before、after 两个属性已经过时,剩余有效属性还有三个。它们的意义为:
    • group:为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以属于多个组。
    • value:为当前扩展类指定的 key,是当前扩展类的一个标识。String[]类型,表示一个扩展类可以有多个指定的 key。
    • order:指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高。默认值为 0。

代码演示 14-activate

  • 复制 11-spi-dubbo 工程,并重命名为 14-activate,在此基础上修改。
  • 修改扩展类:

在这里插入图片描述

  • 再定义三个扩展类:
// order属性的默认值为0,值越小,激活的优先级越高
@Activate(group = {"online", "offline"}, order = 3)
public class CardOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用银联卡支付 ---");
        return "银联卡支付";
    }
}
@Activate(group = "offline", order = 4)
public class CashOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用现金支付 ---");
        return "现金支付";
    }
}
@Activate(group = "offline", order = 5)
public class CouponOrder implements Order {
    @Override
    public String way() {
        System.out.println("--- 使用购物券支付 ---");
        return "购物券支付";
    }
}
  • 注册扩展类:

在这里插入图片描述

  • 测试一:
@Test
public void test01() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx");
    // 激活所有线上支付方式(group为online的扩展类)
    List<Order> orders = loader.getActivateExtension(url, "", "online");
    for (Order order : orders) {
        System.out.println(order.way());
    }
}

在这里插入图片描述

  • 测试二:
@Test
public void test02() {
     ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
     URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx");
     // 激活所有线下支付方式(group为offline的扩展类)
     List<Order> orders = loader.getActivateExtension(url, "", "offline");
     for (Order order : orders) {
         System.out.println(order.way());
     }
 }

在这里插入图片描述

  • 测试三:
@Test
public void test03() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx?order=alipay");
    // 激活所有线上支付方式(group为online的扩展类)
    List<Order> orders = loader.getActivateExtension(url, "order", "online");
    for (Order order : orders) {
        System.out.println(order.way());
    }
}

在这里插入图片描述

  • 测试四:
@Test
public void test04() {
    ExtensionLoader<Order> loader = ExtensionLoader.getExtensionLoader(Order.class);
    URL url = URL.valueOf("xxx://localhost:8080/ooo/xxx?order=alipay");
    // 激活所有线下支付方式(group为offline的扩展类)
    // getActivateExtension 的后两个参数是选择激活类的两个条件,是 或 的关系
    List<Order> orders = loader.getActivateExtension(url, "order", "offline");
    for (Order order : orders) {
        System.out.println(order.way());
    }
}

在这里插入图片描述

  • 测试五:@Adaptive类、wrapper类都不是直接扩展类,@Activate类是直接扩展类

在这里插入图片描述

结论

  • @Activate 的 group 与 value 属性表示该扩展类的两种不同的标识。它们的用法是:
    • 当仅指定了 group 这个大范围标识,当前扩展类可以通过 group 来激活,也可以通过扩展名来激活。
    • 一旦指定了 value 这个小范围标识,其就会将 group 这个大范围标识给屏蔽,即其只能通过扩展名来激活,不能通过 group 来激活。
    • 若 group 属性和 value 属性同时存在,则是“与”的结果。

总结

  • 配置文件中可能会存在四种类:普通扩展类、Adaptive 类、Wrapper 类 和 Activate 类。它们的共同点是:都实现了 SPI 接口。它们的不同点有:

    • 定义方式:Adaptive 类与 Activate 类都是通过注解定义的。
    • 数量:一个SPI接口的Adaptive类最多只能有一个(无论是自定义的,还是自动生成的),而 Wrapper 类与 Activate 类可以有多个。
    • 直接扩展类:Adaptive 类与 Wrapper 类不是直接扩展类。
  • 示例代码github地址:https://github.com/shouwangyw/dubbo/tree/main/dubbo-example-parent

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值