SPI 机制原理

因 dubbo 框架是建立的 SPI 机制上,因此在探寻 dubbo 框架源码前,我们需要先把 SPI 机制了解透彻

Java SPI 机制

SPI 全称 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。
在这里插入图片描述
可以看到,SPI 的本质,其实是帮助程序,为某个特定的接口寻找它的实现类。而且哪些实现类的会加载,是个动态过程(不是提前预定好的)。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以 SPI 的核心思想就是解耦。

使用介绍

要使用 Java SPI,需要遵循如下约定:

  1. 当服务提供者提供了接口的一种具体实现后,在 jar 包的META-INF/services 目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  2. 接口实现类所在的 jar 包放在主程序的 classpath 中;
  3. 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到 JVM;
  4. SPI 的实现类必须携带一个不带参数的构造方法;

示例

先定义一个接口

public interface InfoService   {
    Object sayHello(String name) ;
}

在定义几个实现类

public class InfoServiceAImpl implements InfoService {
	@Override
	public Object sayHello(String name) {
		System.out.println(name+",你好,调通了 A 实现!");
		return name+",你好,调通了 A 实现!";
	}
}

public class InfoServiceBImpl implements InfoService {
    @Override
    public Object sayHello(String name) {
        System.out.println(name+",你好,调通了 B 实现!");
        return name+",你好,调通了 B 实现!";
    }
}

在这里插入图片描述

在这里插入图片描述

ServiceLoader<InfoService> servicesLoader = ServiceLoader.load(InfoService.class);
for (InfoService infoService : servicesLoader) {
     infoService.sayHello("hello");
 }

程序遍历运行时,可看到只加载配置文件中指定的实现类
在这里插入图片描述
至此,整个 java SPI 的机制使用介绍完毕。

核心功能类
java 之所以能够顺利根据配置加载这个实现类,完全依赖于jdk 内的一个核心类:
在这里插入图片描述

Dubbo SPI 机制

在上一节中,可以看到,java spi 机制非常简单,就是读取指定的配置文件,将所有的类都加载到程序中。而这种机制,存在很多缺陷,比如:

  1. 所有实现类无论是否使用,直接被加载,可能存在浪费
  2. 不能够灵活控制什么时候什么时机,匹配什么实现,功能太弱。Dubbo 基于自己的需要,增强了这套 SPI 机制,下面介绍 Dubbo 中的 SPI用法。

标签@SPI 用法

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们就可以按需加载指定的实现类。另外,需要在接口上标注 @SPI 注解。表明此接口是 SPI 的扩展点:

在这里插入图片描述
配置文件内容
在这里插入图片描述

测试代码

 ExtensionLoader<InfoService> extensionLoader = ExtensionLoader.getExtensionLoader(InfoService.class);
 InfoService defaultExtension = extensionLoader.getDefaultExtension();
 defaultExtension.sayHello("default");

 InfoService a = extensionLoader.getExtension("a");
 a.sayHello("zls");

 InfoService b = extensionLoader.getExtension("b");
 b.sayHello("zwq");

测试结果
在这里插入图片描述

标签@Activate 用法

Dubbo 的 Spi 机制虽然对原生 SPI 有了增强,但功能还远远不够。在工作中,某种时候存在这样的情形,需要同时启用某个接口的多个实现类,如 Filter 过滤器。我们希望某种条件下启用这一批实现,而另一种情况下启用那一批实现,比如:希望的 RPC 调用的消费端和服务端,分别启用不同的两批 Filter,怎么处理呢?

这时我们的条件激活注解@Activate,就派上了用场

Activate 注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,dubbo 用它在 spi 扩展类定义上,表示这个扩展实现激活条件和时机。它有两个设置过滤条件的字段,group,value 都是字符数组。 用来指定这个扩展类在什么条件下激活。

下面以 com.alibaba.dubbo.rpc.filter 接口的几个扩展来说明。

@Activate(group = "zls",order = 1)
public class FilterA implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("您好调通了filterA实现");
        return null;
    }
}

表示如果过滤器使用方(通过 group 指定)属于 zls 时就激活使用这个过滤器

@Activate(group = "james",order = 2,value = "groupId")
public class FilterB implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.println("您好调通了filterB实现");
        return null;
    }
}

表示如果过滤器使用方(通过 group 指定)属于james并且 URL 中有参数 groupId时就激活使用这个过滤器

创建文件
META-INF/dubbo/com.alibaba.dubbo.rpc.Filter

a=com.enjoy.filter.FilterA
b=com.enjoy.filter.FilterB
c=com.enjoy.filter.FilterC

测试代码如下:

ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
//随便写		
URL url = URL.valueOf("test://localhost/test");
url = url.addParameter("groupId","aaa");
List<Filter> filterList = extensionLoader.getActivateExtension(url, "", "james");
for (Filter filter : filterList) {
    filter.invoke(null,null);
}

在这里插入图片描述

下面这段代码表示: 添加c过滤器

ExtensionLoader<Filter> extensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);

URL url = URL.valueOf("test://localhost/test");
 url = url.addParameter("groupId","aaa");
 url = url.addParameter("myfilter","c");
 List<Filter> filterList = extensionLoader.getActivateExtension(url, "myfilter", "james");
 for (Filter filter : filterList) {
     filter.invoke(null,null);
 }

如果要去除过滤器
url = url.addParameter(“myfilter”,"-a");

添加c过滤器,删除a过滤器
url = url.addParameter(“myfilter”,"-a,c");

标签@Adaptive 用法

我们在前面演示了 dubbo SPI 的使用,但是有一个问题,扩展点对应的实现类不能在程序运行时动态指定,就是 extensionLoader.getExtension 方法写死了扩展点对应的实现类,不能在程序运行期间根据运行时参数进行动态改变。

而我们希望在程序使用时,对实现类进行懒加载,并且能根据运行时情况来决定,应该启用哪个扩展类。为了解决这个问题,dubbo 引入了 Adaptive注解,也就是 dubbo 的自适应机制。

在方法上添加@Adaptive注解

@SPI("b")
public interface InfoService   {
    Object sayHello(String name) ;

    @Adaptive
    Object passInfo(String name, URL url) ;
}

测试代码

 ExtensionLoader<InfoService> loader =
                ExtensionLoader.getExtensionLoader(InfoService.class);
 InfoService adaptiveExtension = loader.getAdaptiveExtension();
 URL url = URL.valueOf("test://localhost/test?info.service=b");
 adaptiveExtension.passInfo("james", url);

要使用dubbo的自适应机制,入参必须带有URL参数。

使用重点,URL 的格式:
info.service=a 的参数名格式,是接口类 InfoService 的驼峰大小写拆分

总结

Dubbo SPI 的核心实现类为 ExtensionLoader,此类的使用几乎遍及 Dubbo 的整个源
码体系。
ExtensionLoader 有三个重要的入口方法,分别与@SPI、@Activate、@Adaptive 注
解对应。
getExtension 方法,对应加载所有的实现
getActivateExtension 方法,对应解析加载@Activate 注解对应的实现
getAdaptiveExtension 方法,对应解析加载@Adaptive 注解对应的实现
其中,
@Adaptive 注解作的自适应功能,还涉及到了代理对象(而 Dubbo 的代理机制,有两种选择,jdk 动态代理和 javassist 动态编译类)。我们将后后续篇章对此进行说明。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
I2C(Inter-Integrated Circuit)和SPI(Serial Peripheral Interface)都是常用的串行通信接口,在微控制器和外围设备之间传输数据。 **I2C通信原理:** - **双向总线结构**:I2C使用两条信号线,一条是SCL(时钟线),用于同步数据传输;另一条是SDA(数据线),用于发送和接收数据。 - **主从模式**:通常有一个主设备控制通信,其他是从设备。主设备发送起始信号,从设备响应应答信号。 - **仲裁机制**:当多个从设备连接时,主设备通过SCL和SDA的组合信号来决定与哪个设备通信,非被选中的设备会自动进入低电平状态,等待下一次通信机会。 - **停止和重置**:通过SCL拉低并保持一定时间来结束一次传输,重新开始则需要主设备再次发出起始信号。 **SPI通信原理:** - **单线或多线模式**:SPI支持单线或多线模式,最常见的有四线(MISO、MOSI、SCK、CS)和三线(MOSI、SCK、CS)。 - **全双工**:每个连接的一对设备都可以同时发送和接收数据,通过CS信号线选择一个设备进行通信。 - **异步通信**:通过SCK(时钟线)同步数据传输,数据以时钟周期为单位发送。 - **波特率可配置**:可以通过改变SCK频率调整数据传输速率。 - **数据帧结构**:SPI传输是以字节为单位,每个数据包由起始位、数据位、停止位组成,并可能包含奇偶校验或帧间隔。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值