一、SPI是什么?
SPI 的全称为 (Service Provider Interface),是 JDK 内置的一种服务提供发现机制。比如JAVA中定义了jdbc的规范,然后由不同的厂商去落地实现该规范(也就是服务),然后可以通过ServiceLoader去加载这些服务。通过SPI机制可以实现模块插拔,比如当前使用的数据库是oracle,如果想切换为MySQL的话只需要将对应的jar包切换为MySQL的即可。(当然对应的sql语句可能会出问题,除非使用的是Hibernate这种不依赖具体关系型数据库的框架)

二、自己实现一个SPI
1.定义接口(规范):
package com.xiezy.spi;
public interface SPITest {
public String say(String str);
}
2.实现接口(服务)
package com.xiezy.spi;
public class SPITestImpl implements SPITest {
@Override
public String say(String str) {
return "SPI---" + str;
}
}
3.在服务的META-INF/Services目录下创建一个名称为接口全限定名,内容为实现类的全限定名。在本例中名称为:com.xiezy.spi.SPITest
com.xiezy.spi.SPITestImpl
4.测试类
package com.xiezy.spi;
import java.util.ServiceLoader;
public class SPITestMain {
public static void main(String[] args) {
ServiceLoader<SPITest> spiTests = ServiceLoader.load(SPITest.class);
for (SPITest spiTest : spiTests) {
System.out.println(spiTest.say("haha~"));
}
}
}
三、实际例子(JDBC)
相信大家在刚学习jdbc时的使用步骤都是先通过Class.forName("dirver")将对应的Driver加载进JVM才可以使用,但是现在是不需要的。现在我们可以像下面直接使用:

这是因为SPI机制已经将需要的Driver自动加载了,可以看一下DriverManager.getConnection的源码(只截取重要部分):
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/**
...
**/
//遍历已经注册的Dirver,并尝试去获取连接
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
}
可以看到是遍历registeredDrivers,如何去获取连接。那么这个registeredDrivers是什么时候赋值的呢?通过源码可以看到有如下方法:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
但是没看到在什么地方调用了该方法,在了解该方法究竟在哪调用前。我们先看一下DriverManager的static块
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
/**
...
**/
public Void run() {
//通过SPI机制加载Driver
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;
/**
...
**/
}
可以看到DriverManager在初始化的时候去加载了Driver的实现类,比如本例子中使用的MySql包

我们在看一下com.mysql.cj.jdbc.Driver初始化过程
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到在初始化的时候调用了registeredDrivers方法,所以我们知道了DriverManager中的registeredDrivers属性是在这时候赋值的。
