一. SPI 中角色
先上结论
-
DriverManager.getConnection() 内部运用了SPI机制,扫描mysql的jar包的
META-INF/services/
获取全路径名并使用Class.forName(cn, false, loader),c.newInstance()
加载目标驱动。 -
另外一方面,也解决了为什么不使用Class.forName() 也可以破坏双亲委派,因为getConnection内部封装了
Class.forName(cn, false, loader);
-
false
表示 加载字节码返回class对象.但并不去初始化,也就是不触发static代码块 -
c.newInstance
则一定会实例化对象,触发static代码块
前言
以下概念拓展至Effective java page 6 (中文版)
-
SPI (Service Provider Interface),用于拓展工程实例的接口
-
对于JDBC ,Connection 就是其服务接口的一部分
1.1 服务提供者面向的JDK接口 Driver
Service Interface
JDK 提供了标准,具体的数据库驱动由各大数据库厂商提供
1.2 提供者注册 API registerDriver(new Driver)
Provider Registration API
JDK 提供注册用的API,注册的API会调用new Driver()
Driver必须有空参构造方法,为了支持Class.forName().newInstance()
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
1.3 用户调用服务的API getConnection(url)
Provider access API
开发人员使用的代码
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
二. DriverManager 衔接SPI
2.1. DriverManager getConnection的底层源码
private static Connection getConnection(
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
// 注册SPI目录下的所有驱动
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
return (con);
}
}
}
}
}
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass
= Class.forName(driver.getClass().getName(), true, classLoader);
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
最调用了Class.forName
2.2 DriverManager 使用Class.forName
关于getContextClassLoader,点击此链接,拉至文章底部
Thread.currentThread().getContextClassLoader(); // 获取的是AppClassLoader
在加载核心类DriverManager后,可以使用通过线程上下文的 AppClassLoader 加载 SPI实现类
Class.forName(driver.getClass().getName(), true, classLoader);
Class.forName 使用的是 getContextClassLoader() 中的AppClassLoader加载
2.3 DriverManager.getConnection触发静态方法
第一次使用getConnection(String url)
会触发 DriverManager 的 static 方法
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
2.4 DriverManager.loadInitialDrivers()
ServiceLoader<Driver>
就是SPI的正在执行容器
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
driversIterator.next(); // next() 方法是SPI的最终实现
}
return null;
}
});
}
三. SPI 最终实现
物理支持
public final class ServiceLoader<S>implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";
}
ServiceLoader.nextService() 和 com.mysql.jdbc.Driver 的静态方法
ServiceLoader.nextService
用于遍历所有SPI(Iterable.next封装了这个方法)
private S nextService() {
String cn = nextName;//cn是SPI实现者全限定名
nextName = null;
Class<?> c = null;
try {
// 加载字节码返回class对象.但并不去初始化,也就是不触发static代码块
c = Class.forName(cn, false, loader);
}
try {
// cast 不是主要业务逻辑。 c.newInstance触发了 com.mysql.jdbc.Driver的静态方法
// 静态方法完成了Driver的实例化与注册
S p = service.cast(c.newInstance());
providers.put(cn, p);//本地缓存 (全限定名,实现类对象)
return p;
}
}
Driver 的静态方法被触发,完成将SPI实现类注册到DriverManager
因为是由c.newInstance
触发的,这里的new Driver() 已经被替换成了SPI实现类,注册完成
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
四. 梳理getConnection(url) 的注册逻辑
DriverManager.getConnection(url) // 如何取到驱动
-
步骤一,线程开启, 双亲委派指派核心类加载,DriverManager没有被加载时
- Bootstrap ClassLoader 加载
- Launcher 加载 AppClassLoader、ExtClassLoader加载
- 加载类的**线程通过setContextClassLoader(this.loader)**把 AppClassLoader 设置进上下文
public class Launcher { static class AppClassLoader extends URLClassLoader {} static class ExtClassLoader extends URLClassLoader {} public Launcher() { try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } // 把AppClassLoader设置为本线程上下文的类加载器 Thread.currentThread().setContextClassLoader(this.loader); } }
-
步骤二,线程继续执行,DriverManager被加载, 触发loadInitialDrivers(),直接触发SPI读取的工具类实例化
static { loadInitialDrivers(); // 内部使用的是ServiceLoader<Driver> 的api println("JDBC DriverManager initialized"); }
// loadInitialDrivers(); 内初始化ServiceLoader ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
-
步骤三,ServiceLoader< Driver> 初始化,一路携带AppClassLoader
public static <S> ServiceLoader<S> load(Class<S> service) { // 获得的是【步骤一】中设置的AppClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } // 把中设置的AppClassLoader 赋值给 loader private ServiceLoader(Class<S> svc, ClassLoader cl) { loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; }
-
步骤四,ServiceLoader< Driver> 扫描目录,初始化SPI实现类
public final class ServiceLoader<S>implements Iterable<S>{ // 规范目录,MySQL驱动的Jar包下一定有此文件,里面是SPI实现类的全路径名 private static final String PREFIX = "META-INF/services/"; private S nextService() { String cn = nextName;//SPI实现者全限定名 nextName = null; Class<?> c = null; try { // 加载字节码返回class对象.但并不去初始化,也就是不触发static代码块 c = Class.forName(cn, false, loader); } try { // cast 不是主要业务逻辑。 c.newInstance触发了 Driver的静态方法 S p = service.cast(c.newInstance()); providers.put(cn, p);//本地缓存 (全限定名,实现类对象) return p; } } }
-
步骤五,SPI实现类 com.mysql.jdbc.Driver 初始化后,用 static 调用 DriverManager 注册驱动自身,也就是注册com.mysql.jdbc.Driver完成
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
五. 综上,一句话概括
-
DriverManager.getConnection() 内部运用了SPI机制,扫描mysql的jar包的
META-INF/services/
获取全路径名并使用Class.forName(cn, false, loader),c.newInstance()
加载目标驱动。 -
另外一方面,也解决了为什么不使用Class.forName() 也可以破坏双亲委派,因为getConnection内部封装了
Class.forName(cn, false, loader);
-
jvm刚启动的时候不会去加载对应的数据库厂商的驱动,调用第一个获取数据库连接的时候才找对应的Driver实现。用到是扫描SPI的机制,在classpath指定目录下。
DriverManager
来自于Boostrap的classloder,直接调用class.forName("");
这种情况如果要走双亲委派。**如何调整?**当前的用户线程一定持有的ApplicationClassLoder
,由于JDBC4.0规范问题,jvm大胆得不再询问父类,而是直接用ApplicationClassLoder
和Class.forName(cn, false, loader)
加载classpath下面的class,也就打破了双亲委派。