1、SPI是什么
SPI全称Service Provider Interface(服务提供接口),是专门被第三方实现或者扩展的API,可以用来启用框架扩展和替换组件,提供了为某个接口寻找对应服务类的机制。
整体机制如下:
(图片来源:https://www.jianshu.com/p/46b42f7f593c)
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
Java SPI的实现要素:
- 首先定义服务和接口名;
- 不同服务提供者实现了接口的实现类后,需要在jar包的META-INF/services目录下创建一个"接口全限定名"为命名的文件,内容对应即为该实现类的全限定名;
- 接口实现类所在jar包放在主程序classpath下;
- 服务调用方通过该接口时,引入提供者jar包 ,通过
java.util.ServiceLoder
动态装载提供者的实现模块,扫描提供者的META-INF/services目录下的配置文件找到实现类的全限定名,之后再将该类加载到JVM; - SPI实现类必须携带一个不带参数的构造方法;
2、Java SPI demo
举个例子看看SPI的应用。
在应用Java SPI之前,假设有个mq消息处理服务:
public interface KafkaProcessService {
void processOrderMsg(Object msg);
}
我们提供服务实现后,打包,假设版本号为version=1.0;团队1使用了引入该jar并且直接调用服务方法processOrderMsg()
。
之后团队2想调用一个支付消息处理的方法,那么我们就需要提供一个对应实现,并且将版本号升到version=2.0;;团队2使用了引入该jar并且直接调用服务方法processPayMsg()
。
public interface KafkaProcessService {
void processPayMsg(Object msg);
}
试想如果每增加一个实现,我们就要升一个版本,这会带来很大麻烦。
如何应用用Java SPI是如何处理这种麻烦?
(1)首先定义接口:
public interface KafkaProcessService {
void processMsg(Object msg);
}
具体实现类
public class KafkaOrderProcessServiceImpl implements KafkaProcessService {
@Override
public void processMsg(Object msg) {
System.out.println("Order kafka msg process");
}
}
public class KafkaPayProcessServiceImpl implements KafkaProcessService {
@Override
public void processMsg(Object msg) {
System.out.println("Pay kafka msg process");
}
}
(2)resources目录下新建/META_INF/services目录,并创建一个与接口全限定名相同的文件,在文件中输入接口对应两个实现类的全限定名:
(3)执行程序:
public class Invoker {
public static void main(String[] args) {
ServiceLoader<KafkaProcessService> processServices = ServiceLoader.load(KafkaProcessService.class);
for (KafkaProcessService s : processServices) {
s.processMsg(new Object());
}
}
}
运行项目后可以看到能够两个实现类均执行了。
3、Java SPI 在框架中的应用
有人会说上面的例子看起来和本地应用策略模式实现的没有什么不同。实际上,SPI更多的应用场景:团队1定义接口且可以提供默认实现,团队2、3…引用接口,并且可以自行拓展接口的实现。
下面通过具体实例来体会SPI的应用。
3.1 JDBC加载不同类型数据库驱动
JDBC数据库驱动的设计就是应用Java SPI机制。首先定义统一的规范:java.sql.Driver
, 各大厂商(MySQL、Oracle)都会根据这个规范去实现各自的驱动逻辑。我们在使用JDBC客户端时候不需要改变代码,直接引入对应jar包,实际调用即使用对应的驱动实现。
分析下源码:
我们引入MySQL驱动后,JDBC连接数据库java.sql.DriverManager
中就会使用SPI机制来加载具体的驱动实现:
public class DriverManager {
// 省略部分代码
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
// 使用SPI机制初始化驱动
private static void loadInitialDrivers() {
// 省略部分代码
// 使用ServiceLoader找到实现Driver的接口,进行实例化
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver></