目录
什么是SPI?
SPI全称Service Provider Interface,是JDK内置的服务动态发现和替换机制,比如调用方提供一套标准,服务提供方提供了实现,想要在运行时动态的发现替换某一实现就需要用到Java的SPI机制。
示意图
如上图所示SPI的定义在调用方,实现位于具体实现提供方的包里。当服务提供者提供了一种接口的实现后,需要在classpath的META-INF/services目录下创建一个以服务接口命名的文件(如下图),文件的内容是该接口对应的具体实现类的全路径。当需要使用该实现时可以通过java提供的ServLoader进行加载初始化。
上图是mysql驱动提供的实现
SPI常见使用场景
SPI使用场景非常多,比如DriverManager,Spring等,本文将从数据库驱动的使用上来分析一下具体实现。
下面是一个常见的加载mysql驱动的代码:
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection cn = DriverManager.getConnection("url","user","pwd");
System.out.println(cn);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
在执行Class.forName的时候,会执行com.mysql.cj.jdbc.Driver类中的静态代码块将mysql的Driver驱动注册到DriverManager中,DriverManager是java.sql包下提供的管理Driver的工具类,静待代码块如下所示:
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
DriverManager实现
上面registerDriver方法只是在DriverManager内部维护了一个List,将new 出来的Driver对象放入其中,具体加载的过程也是通过静态代码块来实现的看下面的代码:
/**
* 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() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
loadInitialDrivers该方法中可以看到是通过ServiceLoader对具体实现进行加载的,在ServiceLoader内部定义了加载的路径“META-INF/services”,且在内部实现了一个LazyIterator,所以上面代码中跟迭代相关的需要查看ServiceLoader的源码
private static final String PREFIX = "META-INF/services/";
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
至此可以看出SPI在DriverManager中的具体实现细节了,其他的应用场景大家如果感兴趣可以自行去琢磨研究一下哈!