转载

Java SPI 和 Dubbo SPI

最近在读 Dubbo 源码,可扩展性是 Dubbo 区别于其他 RPC 框架的一大特性,而 Dubbo 的可扩展性又是基于 SPI 去实现的。要知道 SPI 的独特之处,需要深入了解一下。文章会简单记录一些实验,分别为 Java SPI 和 Dubbo SPI 编写一些 Demo,对 SPI 机制有一点感性的认知。

SPI

  • Java SPI :Service Provider Interface 一个 接口 多种实现,通过配置确定使用哪个实现。
  • Dubbo SPI :Java SPI 增强版,但并不是基于 Java SPI 去实现的,而是 Dubbo 自己按照 Java SPI 的功能,重写了一次,并增加了扩展点 IOC 和 AOP 的支持。

Java SPI Demo

Demo 来自 Dubbo 官方文档网站

// 定义一个接口和多个实现
public interface Robot {
    void sayHello();
}

// 大黄蜂 实现
public class Bumblebee implements Robot{
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee");
    }
}

// 擎天柱实现
public class OptimusPrime implements Robot{
    @Override
    public void sayHello() {
        System.out.println("hello, I am Optimus Prime.");
    }
}

// 在 resource/META-INF/services/ 目录下,为 Robot 接口定义 SPI 文件
resource/META-INF/services/com.qpm.learn.spi.Robot 
文件内容:
com.qpm.learn.spi.OptimusPrime
com.qpm.learn.spi.Bumblebee

// 测试用例
public class RobotTest extends TestCase {
    public void testSayHello() {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

console OUPUT:
Java SPI
hello, I am Optimus Prime.
Hello, I am Bumblebee
复制代码

这是一个非常简单的例子,上面的例子看起来假如使用 Spring 的 IOC 容器也是非常容易实现的。但 SPI 并不来源于任何一种框架,而是 Java 语言自带的。

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。 (Java SPI机制详解)

SPI 机制的思想来源正是:开闭原则,对扩展开放,对修改关闭。对于一个特定的程序,如 Java 的 java.sql.Driver,就是一个接口,Mysql 的 Driver 实现就是通过 SPI 机制被程序加载的。不难想到,其他 Driver 的实现也可以依靠 SPI 机制去实现。

Java SPI 和 Dubbo SPI

Dubbo SPI

Dubbo SPI 是 Java SPI 的增强版,但 Dubbo 本身并没使用 Java SPI。也就是说,Dubbo 自己实现了一套和 SPI 几乎一样的功能,使用的步骤和 Java SPI 几乎一致,简单来说,就是

  • 定义接口
  • 编写实现
  • 文件配置使用哪个实现

但 Dubbo SPI 本身做了一些增强和规则的改变:

  • 配置目录位置和 Java SPI 不一样,Dubbo 配置的定义来自:META-INF/dubbo
  • Dubbo 具备类似 IOC 和 AOP 的功能

Dubbo SPI Demo

// 定义一个接口和多个实现
@SPI  // import org.apache.dubbo.common.extension.SPI; Dubbo 的 SPI 是需要注解去实现的
public interface Robot {
    void sayHello();
}

// 大黄蜂 实现
public class Bumblebee implements Robot{
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee");
    }
}

// 擎天柱实现
public class OptimusPrime implements Robot{
    @Override
    public void sayHello() {
        System.out.println("hello, I am Optimus Prime.");
    }
}

// 在 resource/META-INF/dubbo/ 目录下,为 Robot 接口定义 SPI 文件
resource/META-INF/dubbo/com.qpm.dubbo.test.spi.Robot 
文件内容,相比于 Java SPI Dubbo 的 SPI 是一个 K-V 设计
optimusPrime=com.qpm.dubbo.test.spi.OptimusPrime
bumblebee=com.qpm.dubbo.test.spi.Bumblebee

// 测试用例
    @Test
    public void sayHello() throws Exception {
        System.out.println("Dubbo SPI");
        
        ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }

console OUPUT:
Dubbo SPI
hello, I am Optimus Prime.
Hello, I am Bumblebee
复制代码

后记

整体来说 SPI 是一种设计的思想,而且 Spring 的 IOC 容器也一直在使用。要理解 SPI 反而是要先跳出 Spring IOC 的框架,考虑它从代码设计上是引用来那种思想。我简单认为,这是一种开闭原则的实现,即对扩展保持开放,Dubbo 定义 SPI 机制,就是对扩展保持开放。

为什么不用 Spring 协助 Dubbo 实现类似 SPI 的功能呢?我个人认为应该是 Dubbo 作为一个独立的 RPC 产品,首先应该要加入 Spring 的生态,但不是依赖 Spring 的底层框架;其次是 Spring 的 IOC 和 AOP 一定程度上对 Dubbo 来说功能太强大,自己实现 SPI 反而会更加轻量,更加能控制性能,保证 Dubbo 的轻量级和高性能的特点。

熟悉了 Dubbo 的 SPI 机制是什么,简单地思考了一下为什么 Dubbo 自己实现 SPI 机制,接下来就可以阅读一下 Dubbo SPI 的实现源码,还有 Dubbo 利用自己的 SPI 如何为模块保持扩展,并且定制自己的特性。

参考

  • Java SPI机制详解
  • Dubbo SPI 官方文档
  • 极客时间 设计模式之美 开闭原则 (付费阅读链接)
原文  https://juejin.im/post/5eabdfe85188256d9353a2e6
正文到此结束
Loading...