Dubbo源码剖析 -- SPI

文章介绍了JDK的SPI机制,包括其使用场景和原理,并详细阐述了Dubbo为何引入SPI以及DubboSPI的特点和优势。DubboSPI允许按需加载实现,支持Wrapper增强和自动装配。此外,文中还讨论了DubboSPI的自适应功能以及如何实现自动激活扩展点。
摘要由CSDN通过智能技术生成

一.JDK SPI 的使用

1.什么是spi:SPI 全称为 Service Provider Interface,一种解耦接口和实现的手段,其实现原理是将接口的实现类全名称配置在配置文件中,程序运行阶段去读取配置文件加载实现类
在这里插入图片描述

这个机制为程序带来了很强的扩展性,使得我们可以很方便的基于某接口规范去使用任何第三方的实现

例如:

1.JDBC规范中 java.sql.Driver 只是一个接口,具体实现还得看我们使用什么数据库,导入对应数据库的驱动jar包
2.在将驱动更换之后,该代码仍然能够正常运行,这就是spi带来的好处

代码如下:

public class TestDB {
    public static void main(String[] args) throws Exception {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/study_test?serverTimezone=UTC","root","root");
        PreparedStatement preparedStatement = conn.prepareStatement("select * from emp");
        preparedStatement.execute();
        ResultSet resultSet = preparedStatement.getResultSet();
        while (resultSet.next()) {
            System.out.println(resultSet.getLong("id"));
            System.out.println(resultSet.getString("ename"));
        }
    }
}

pom.xml依赖

<dependency>
 	<groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.7</version>
</dependency>
<dependency>
 	<groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.31</version>
</dependency>

2.JDK SPI使用

  • 在 META-INF/services 目录下创建一个以接口全限定名为名称的文件
  • 文件中按行配置该实现类的全限定类名
  • 使用 java.util.ServiceLoader#load 去加载获取

在这里插入图片描述
3.测试
在这里插入图片描述

原则上:由谁提供接口的实现,就由谁提供该配置文件,所以如果是外部第三方提供接口实现,它就要在它的项目 中提供好配置,我们的项目只需引入它的坐标即可

二.Dubbo 为何引入SPI

1,dubbo作为一个rpc框架,在它发送RPC请求时,整个过程会经历很多个关键事件节点,比如集群容错,负载均衡,数据序列化,通信协议编码,网络传输等,每个关键节点都有抽象出对应的接口,而且有多种不同的实现,且用户可自行扩展,那实际运行阶段 dubbo如何根据用户的配置参数来选择具体的实现呢,这就促使dubbo需要一种可插拔的接口实现发现机制
在这里插入图片描述

2,dubbo采用微内核架构,将每一个功能接口当作一个可插拔的扩展点接口,内核层面只负责按流程组装并引导执行每个扩展点接口 ,具体的功能和逻辑由具体的扩展点实现来完成,提高了系统的扩展性和灵活性,而spi就是实现微内核的手段

3,dubbo的spi机制是沿用jdk-spi还是需要重新设计?
重新设计
原因如下:

1.jdk-spi 虽然实现了解耦,能够做到接口与实现分离但在使用上只能迭代获取所有实现,无法按需获取
2.jdk-spi 在迭代加载的过程中就创建了所有接口实现的实例对象,也不管当前是否使用
3.jdk-spi 在并发安全处理上并未做任何设计
4.除了想要使用方便,按需获取等特性之外,dubbo还想根据自己的场景添加更多功能特性在里面

4.dubbo的spi在设计上有什么思路?
先解决按需获取的问题

1.将配置文件改成K=V的形式,在程序中通过K来获取V对应的实例
2.将K作为配置参数共使用者选择,用户配置什么实际执行的就是什么

# 配置
AndyLinux=com.andy.spi.jdk.impl.AndyLinux
AndyMac=com.andy.spi.jdk.impl.AndyMac
AndyWindow=com.andy.spi.jdk.impl.AndyWindow

三.Dubbo SPI的基本使用

主方法

public class DubboTest {
    public static void main(String[] args) {
        ExtensionLoader<AndyJdkApi> extensionLoader = ExtensionLoader.getExtensionLoader(AndyJdkApi.class);
        // 基本测试
        baseDubbo(extensionLoader);
        // wrapper基本测试
        baseWrapper(extensionLoader);
        // 包装wrapper基本测试
        baseWrapper2(extensionLoader);
        // protocol方式
        protocolTest(extensionLoader);
    }

1.入门案例

1.导入dubbo坐标

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.23</version>
</dependency>

2.在接口上添加 @SPI 注解,告诉dubbo该接口可用其 SPI 机制来加载
在这里插入图片描述
3.在META-INF/dubbo,META-INF/dubbo/internal,META-INF/services中任何一个目录下创建一个以接口全限定名称为文件名的文件,在文件中按K=V的形式配置每个扩展点实现的全限定名称,其中K是自定义的
在这里插入图片描述
4.使用ExtensionLoader相关API去获取

private static void baseDubbo(ExtensionLoader<AndyJdkApi> extensionLoader) {
    AndyJdkApi andyLinux = extensionLoader.getExtension("AndyLinux");
    andyLinux.printInfo();
}

在这里插入图片描述

2. 自动包装Wrapper

Dubbo SPI 提供了类似Spring AOP的功能,可以对扩展点进行增强,称之为Wrapper

1,提供wrapper类,wrapper类与扩展点实现同一接口,并提供扩展点接口参数类型的构造函数【装饰者模式】
2,配置文件中同样需要把wrapper类配置进去,则所有的扩展点都会用该wrapper进行装饰增强
3,如果有多个wrapper,则会对扩展点进行层层包装增强
4,wrapper的作用是为了去增强其他扩展点实现,本身则不能作为扩展点

2.代码:


public class MyWrapper implements AndyJdkApi {
    public final Logger log = LoggerFactory.getLogger(this.getClass().getName());
    private final AndyJdkApi api;

    public MyWrapper(AndyJdkApi api) {
        this.api = api;
    }

    @Override
    public void printInfo() {
        log.info("wrapper前置执行");
        api.printInfo();
        log.info("wrapper后置执行");
    }
}

2.配置文件新增:

wrapper=com.andy.spi.dubbo.MyWrapper

在这里插入图片描述

3.测试代码

private static void baseWrapper(ExtensionLoader<AndyJdkApi> extensionLoader) {
    AndyJdkApi andyLinux = extensionLoader.getExtension("AndyMac");
    andyLinux.printInfo();
}

在这里插入图片描述

3. 多层包装Wrapper

1.在配置文件中新增如下内容:

wrapper=com.andy.spi.dubbo.MyWrapper2

在这里插入图片描述
2.代码:
将MyWrapper代码复制一份命名为MyWraaper2
在这里插入图片描述
3.测试代码

private static void baseWrapper2(ExtensionLoader<AndyJdkApi> extensionLoader) {
    AndyJdkApi andyLinux = extensionLoader.getExtension("AndyMac");
    andyLinux.printInfo();
}

在这里插入图片描述

4.自动装配

1.Dubbo SPI 提供了类似Spring IOC 的功能,可以对扩展点进行依赖注入

1.在一个扩展点中通过set方法去注入另外 一个扩展点
2.在wrapper中也同样支持通过set进行依赖注入

2.修改AndyLinux代码

public class AndyLinux  implements AndyJdkApi {
    public final Logger log = LoggerFactory.getLogger(this.getClass().getName());

    private Protocol protocol;

    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }

    @Override
    public void printInfo() {
        log.info("protocol"+protocol);
        log.info("Linux系统在实现功能");
    }
}

在这里插入图片描述
3.测试代码:

private static void protocolTest(ExtensionLoader<AndyJdkApi> extensionLoader) {
    AndyJdkApi andyLinux = extensionLoader.getExtension("AndyLinux");
    andyLinux.printInfo();
}

在这里插入图片描述

四.Dubbo SPI 自适应

1.图示
在这里插入图片描述

2.在Dubbo中,自适应扩展点可以由用户编写,也可由Dubbo框架自动生成

3.自适应案例[用户定义]

1.核心要素就是在自适应类上标注@Adaptive注解
2.配置文件中同样需要把自适应类配置进去
3.每个接口的自适应类只能有一个
4.扩展点注入时注入的都是自适应实例

4.自适应案例【框架生成】

1.在扩展点接口方法上标注@Adaptive注解,哪个方法需要在运行时自适应执行则添加
2.方法必须要有URL类型的参数,或者参数有getUrl()方法返回一个URL对象,URL是dubbo存储配置的容器
3.Adaptive注解的value属性指定需要从URL中获取哪个配置信息,dubbo根据此配置值决定执行哪个扩展点
4.可通过日志查看框架生成的自适应扩展点的代码

5.DubboSPI自适应由几种用法

  • 开发者自行编写自适应类,自适应类也是接口的一个实现类,并在自适应类上添加@Adaptive注解,然后将自适应类配置到配置文件
  • 由框架生成,需要在接口方法上添加@Adaptive注解,方法需要有URL类型的参数或者参数有getUrl方法,并通过Adaptive的value属性指定要从URL中获取哪个配置信息,框架根据此配置信息获取对应的扩展点

五.DubboSPI自动激活

1.DubboSPI自动激活

在前面讨论的所有特性中大都是集中在一个场景:需要从众多扩展点实现中根据name挑出一个,但是有一些场景并不是万里挑一的,而是可以多个扩展点实现共存并且项目初始化后就可以直接使用的,比如:Filter,Listener等.

所谓自动激活其实就是让某些扩展点默认就可以获取并使用,并不需要根据它们的key来逐一获取,这样可以拿到已激活的扩展点集合

  • 想要默认激活某扩展点,需要给它添加@Activate注解
  • 由于dubbo的主要场景是RPC,分为消费端和提供方,所以可以通过Activate的group属性指定该扩展点在哪一端激活
  • 通过Activate的value属性指定更多激活的判断条件

2.DubboSPI自动激活如何使用

  • 在扩展点上添加@Activate注解
  • 通过group属性指定该扩展点是在消费方还是提供方激活3.通过value属性指定更多激活条件

3.DubboSPI如何指定默认扩展点

  • 通过SPI注解的value属性指定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值