参考
SPI的基本概念
SPI和API是不同但是相似的东西,它们最根本的不同就是:接口是使用方来指定的(SPI),还是接口是由实现方来指定的(API)。
API示例
例如我们引入了netty包,netty包本身提供了eventLoop接口,并且提供了具体的实现,像这种接口和实现均在实现方中提供的情况,就是API。我们引入软件包就可以开始使用API进行编程。
SPI示例
例如我们使用JDBC的时候,JDBC提供了一个Connection接口,但是没有提供任何实现。这个实现是如何来的呢,就是通过我们引入的mysql、oracle等的第三方客户端jar包来的。这些第三方jar包并不包含接口的具体内容,而是实现了使用方所声明的接口,这种情况就是SPI。
SPI和API的区别
- API:实现方对使用方说【我提供了XXX功能,要使用该功能请使用IXXX接口进行编程】
- SPI:使用方说【我提供了IXXX接口,你们(实现方)如果想给我提供功能的话,请按照该IXXX接口进行实现】
API是怎么找到具体实现的
- 通常,实现方会提供一个工厂方法,创建出API接口的实现类的具体实例给我们使用,这种情况下实现类是工厂方法确定的。
- 有时,实现方会提供几个不同的实现,然后我们可以自己选择,通过简单的new出某一个具体实现类的方式。这种情况下实现类是由使用者自己手动指定的。
SPI是怎么找到具体实现的
SPI使用ServiceLoader,可以加载jar包中的文件 META-INF/services/接口名称 。然后加载该文件里定义的接口实现类(可以定义多个实现类),最后就加载了很多个具体的实现。
JDBC的基本过程
- 首先我们肯定要调用DriverManager.getConnection(xxx)
- 然后因为我们有上面的调用,因此会加载DriverManager类,从而执行类初始化
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
xxx...
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 关键步骤
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
}
- 比较关键的就是其中使用SPI去加载了 Driver.class 的实现类,然后进行了实例化
- Driver的实现类中,他们将自己注册到DriverManager中
- 最后getConnection的时候,就会在DriverManager中注册的驱动里,一个一个的执行获取连接操作。数据库驱动会根据URL来获取连接,发现不是自己需要处理的数据库,就返回null,进入下一个驱动。一直到某一个驱动可以处理某一种URL,再去实际的连接该数据库
- 也就是说不需要类似于Class.forName("com.mysql.jdbc.Driver")的代码去手动的加载驱动类了。这种加载已经被SPI机制所取代了。