SPI(Service Provider Interface)是 JDK 内置的一种服务发现机制,可以用来启动框架扩展和替换组件,主要是写框架和扩展框架的开发人员使用。
SPI 实现
比如我们要调用打车接口,实际可能下单的平台包括滴滴、曹操出行、出租车平台等等,我们可以先定义一个接口
- 打车接口定义
public interface CallCar {
/**
* 叫车接口
*/
void call();
}
- 滴滴约车实现
public class DidiCall implements CallCar {
/**
* 叫车接口
*/
@Override
public void call() {
System.out.println("滴滴约车中。。。");
}
}
- 曹操出行约车实现
public class CaoCaoCall implements CallCar {
/**
* 叫车接口
*/
@Override
public void call() {
System.out.println("曹操出行约车中。。。");
}
}
- 出租车约车实现
public class TaxiCall implements CallCar {
/**
* 叫车接口
*/
@Override
public void call() {
System.out.println("出租车约车中。。。");
}
}
接下来、还需要在 resources目录下新建 META-INF/services 目录,然后新建一个与约车接口全称命名相同的文件,文件的位置及内容如下
com.knowledge.spi.car.CaoCaoCall
com.knowledge.spi.car.DidiCall
- 编写测试用例
public class CaseDemo {
public static void main(String[] args) {
ServiceLoader<CallCar> serviceLoader = ServiceLoader.load(CallCar.class);
final Iterator<CallCar> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
final CallCar callCar = iterator.next();
callCar.call();
}
}
}
- 运行结果
曹操出行约车中。。。
滴滴约车中。。。
Process finished with exit code 0
实际上我们调用 ServiceLoader.load() 方法加载某接口时,会去 META-INF/services 下找接口的全称命名文件,再根据文件内定义的实现类路径加载所有的类。这就是 SPI 的思想,接口的实现由 provider 实现,我们在扩展框架的功能时、也可以用这种思想添加我们自己的扩展实现类。
SPI 在JDBC中的应用
在 JDBC4.0 之前我们要想连接数据库,通常会调用Class.forName("com.mysql.jdbc.Driver") 来加载驱动,但是在 JDBC4.0 之后,不需要手动加载驱动,直接获取连接就可以了,因为新版本的 JDBC 使用了 SPI 扩展机制来实现。
- 接口定义
在 jdk 中定义了接口java.sql.Driver,但是并没有具体实现
- mysql 实现
当我们引入mysql-connector-java-6.0.6.jar后,可以发现在 jar 包中有一个 META-INF/services 目录,可以发现有一个 java.sql.Driver 的文件,里边的内容就是com.mysql.cj.jdbc.Driver。根据 SPI 的服务发现机制,数据库驱动就会加载。
- postgresql 实现
同样的,当我们引入postgresql-42.0.0.jar后,也可以发现在 jar 包中有一个 META-INF/services 目录,可以发现有一个 org.postgresql.Driver 的文件
- 使用方法
因为有了 SPI 的服务发现机制,我们在写代码连接数据库的时候,不需要再写Class.forName("com.mysql.jdbc.Driver"),直接这样写即可
String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url,username,password);
- 源码实现
在java.sql.DriverManager中,有个loadInitialDrivers方法
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
当DriverManager中加载的时候就会调用下边这行,使用 SPI 机制加载我们 jar 包中或者我们自己扩展的驱动实现。当然、实现可以有多个。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);