什么是SPI?
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
在Dubbo、JDBC中都使用到了SPI机制,java就是通过 ServiceLoader.load() 方法获取到实现类的实例的,达到调用外部服务接口的目的。 简单看看在JDBC是如何实现SPI服务的。
JDBC
连接准备
String url = "jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL";
String username = "root";
String password = "root";
String driverClassName = "com.mysql.cj.jdbc.Driver";
Class.forName(driverClassName);
Connection connection = DriverManager.getConnection(url, username, password);
System.out.println(connection);
加载
在DriverManager类中可以看见有一个 loadInitialDrivers() ,这个方法就是查找Driver接口的服务类,所以它的文件路径就是:META-INF/services/java.sql.Driver。
public class DriverManager {
private static void loadInitialDrivers() {
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) {
}
return null;
}
});
}
}
注意这里使用的是Thread.currentThread().getContextClassLoader() 上下文类加载器去加载,说明这里是不符合双亲委派机制的。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
发现connector中 META-INF/services/java.sql.Driver 文件包含的内容如下
创建实例获取连接
接着找到MySQL中的 com.mysql.cj.jdbc.Driver 全限定类名后,就会创建这个类的实例。 getConnection 获取连接对象,其中registeredDrivers(CopyOnWriteArrayList类) 包含com.mysql.cj.jdbc.Driver 实例,通过循环已注册的数据库驱动程序,调用其connect方法,获取连接对象。
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
总结:java 中的SPI服务就包含有JDBC、JNDI等等,由于去加载SPI服务代码,是一种父类加载器请求子类加载器完成类的加载的过程,打破了双亲委派模型,从JDK6开始,JDK提供了java.util.ServiceLoader类,以 META-INF/services中的配置信息,辅以责任链模式,给SPI加载提供了相对合理的解决方案。
|