Dubbo SPI实战
1. SPI 概述
在具体分析之前还是先了解下 SPI 是什么?
首先它其实是 Service provider interface 的简写,翻译成中文就是服务提供发现接口。
不过这里不要被这个名词搞混了,这里的服务发现和我们常听到的微服务中的服务发现并不能划等号。
就如同对一个接口的多种实现方式 A、B、C(可以把它们理解为服务),我需要在运行时知道应该使用哪一种具体的实现。
其实本质上来说这就是一种典型的面向接口编程,这一点在我们刚开始学习编程的时候就被反复强调了。
2. SPI 实践
接下来看一个例子,是如何实现SPI的。
定义接口
既然刚才都提到了 SPI 的本质就是面向接口编程,所以自然我们需要定义一个接口: BeanFactory
为了让其他人也能实现自己的 BeanFactory,所以我们将这个接口单独放到一个 Module: spi-beanfactory中,可供他人引入实现。
实现接口
新建一个 Module:spi-mybeanfactory
通过maven引入刚才的模块
<dependency>
<groupId>org.example</groupId>
<artifactId>spi-beanfactory</artifactId>
<version>1.0.0</version>
</dependency>
实现 BeanFactory
在 resources 目录下新建一个 META-INF/services/com.lg.benfactory.BeanFactory 文件
注意文件名必须得是我们之前定义接口的全限定名(SPI 规范)。
其中的内容便是我们自己实现类的全限定名,如下:
com.lg.mybeanfactory.MyBeanFactory
可以想象最终会通过这里的全限定名来反射创建对象。
当 classpath 中存在我们刚才的实现类(引入实现类的 jar 包),便可以通过 java.util.ServiceLoader 工具类来找到所有的实现类(可以有多个实现类同时存在,只不过通常我们只需要一个)。
我们只需要引入这个依赖便能使用它的实现,当我们想换一种实现方式时只需要更换一个依赖即可。
测试
public class App
{
public static void main( String[] args ) throws Exception {
ServiceLoader<BeanFactory> factories = ServiceLoader.load(BeanFactory.class);
for (BeanFactory factory : factories) {
factory.getBean("");
}
}
}
最后输出的结果如下:
3. SPI 的优缺点
优点
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
多个并发多线程使用ServiceLoader类的实例是不安全的。
4. 鸣谢
高级开发必须理解的Java中SPI机制