最近在读 Dubbo 源码,可扩展性是 Dubbo 区别于其他 RPC 框架的一大特性,而 Dubbo 的可扩展性又是基于 SPI 去实现的。要知道 SPI 的独特之处,需要深入了解一下。文章会简单记录一些实验,分别为 Java SPI 和 Dubbo SPI 编写一些 Demo,对 SPI 机制有一点感性的认知。
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 机制去实现。
Dubbo SPI 是 Java SPI 的增强版,但 Dubbo 本身并没使用 Java SPI。也就是说,Dubbo 自己实现了一套和 SPI 几乎一样的功能,使用的步骤和 Java SPI 几乎一致,简单来说,就是
但 Dubbo SPI 本身做了一些增强和规则的改变:
// 定义一个接口和多个实现 @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 如何为模块保持扩展,并且定制自己的特性。