关于MySQL的JDBC Driver加载过程详解

发布时间 2023-05-26 11:17:03作者: rockdow

在使用JDBC时,都需要提前加载对应数据库的Driver类,MySQL为例:

Class.forName("com.mysql.jdbc.Driver")

但是不写这句代码也可以让 com.mysql.jdbc.Driver 类成功加载,这就涉及到 java.sql.DriverManager 类了,先看一下代码:

public class DriverManager {
// 注册驱动的集合
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers
= new CopyOnWriteArrayList<>();
// 初始化驱动
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

它位于启动类路径之下,由 BootStrap ClassLoader 加载,在加载的初始化过程中,运行 static 代码块里的 loadInitialDrivers() 方法,在该方法里加载了 Driver 类,具体如下:

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;
}
// 1)使用 ServiceLoader 机制加载驱动,即 SPI
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) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
// 2)使用 jdbc.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);
// 这里的 ClassLoader.getSystemClassLoader() 就是 Application ClassLoader 类加载器
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

可以看到,一开始定义了 drivers 属性,通过 System.getPorperty 方法试图获取系统变量中定义的 jdbc.drivers 变量值并赋给 drivers,如果结果不为空,再看标号 2)部分的代码,这段代码是使用 Application ClassLoader 加载 drivers 属性中指定的类。而系统变量中如果没有定义 jdbc.drivers,也不用担心,看标号 1)的代码部分,它使用 SPI 机制加载驱动类,如图:

mysql-connector-java-5.1.47.jar 中,含有 META-INF→services→java.sql.Driver 文件。在 SPI 机制中,只要对应文件夹下的文件名和类接口名对应,就可以通过 ServiceLoader.load() 方法加载对应文件中记录的实现了该接口的类(当然,具体过程比较复杂,有耐心的读者可以自行查看load函数的调用链)。到此,就实现了不显示写出 Class.forName("com.mysql.jdbc.Driver"),也可以加载Driver类的功能。