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层:
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