相关博客:
Dubbo的SPI机制(二)(Dubbo优化后的SPI实现)
Dubbo 的 SPI 机制(三)(Extension 扩展点补充)
在Dubbo中,SPI是一个非常核心的机制,贯穿在几乎所有的流程中。
Java的SPI
SPI全称(service provider interface),是JDK内置的一种服务提供发现机制,目前市面上有很多框架都是用它来做服务的扩展发现,大家耳熟能详的如JDBC、日志框架都有用到。
SPI具体约定: Java SPI的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。
演示示例
官方定义了数据库驱动规范API,各大数据库厂商自己定义自己的规范实现:
首先定义一个官方规范API:
将jar安装到本地仓库:
MySql厂商依赖官方规范API:
根据SPI规范,先新建一个resources,在resource下新建一个META-INF目录,在META-INF目录下创建一个services目录,在services下新建一个文件名为DataBaseDriver全类名一样的文件:
在dongguabai.dubbo.spi.DataBaseDriver文件中增加实现类的全类名:
在应用中添加MySql规范的依赖:
接下来就可以使用驱动了:
package test.demo2;
import com.spi.DataBaseDriver;
import java.util.ServiceLoader;
/**
* Hello world!
*/
public class App {
public static void main(String[] args) {
ServiceLoader<DataBaseDriver> loader = ServiceLoader.load(DataBaseDriver.class);
for (DataBaseDriver driver : loader) {
System.out.println(driver.connect("127.0.0.1"));
}
}
}
运行结果:
接下来实现一个Oracle的驱动工程:
同样需要引入官方规范的依赖:
Oracle的实现:
添加相应的META-INF文件:
接下来打包。
在应用中使用也很方便,刚刚依赖的是MySql的包,直接替换成Oracle的就行了:
运行代码不用变:
可以看出基于SPI我们可以非常方便的实现可插拔式的扩展。
也许会有人说,那如果我两个都要用怎么办,那就两个都依赖即可:
发现两个都出现了,也就是都加载了。这个的确是一个问题,这个在后面再详细说明。
再来总结一下SPI规范实现:
- 需要在classpath下创建一个目录,该目录命名必须是:META-INF/services
- 在该目录下创建一个properties文件,该文件需要满足以下几个条件
- 文件名必须是扩展的接口的全路径名称
- 文件内部描述的是该扩展接口的所有实现类
- 文件的编码格式是UTF-8
- 通过java.util.ServiceLoader的加载机制来发现
SPI的实际应用
SPI在很多地方有应用,比如最常用的java.sql.Driver驱动。JDK官方提供了java.sql.Driver这个驱动扩展点,但是并没有看到JDK中有对应的Driver实现:
这里依赖一个MySql的实现:
可以发现在META-INF文件下有services/java.sql.Driver文件:
实现类为:
SPI的缺点
1. JDK标准的SPI会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在META-INF/service下的文件里面加了N个实现类,那么JDK启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到,那么会很浪费资源。
2. 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因。
参考资料: