为什么要写 Class.forName
先看下为什么以前要写 Class.forName("com.mysql.cj.jdbc.Driver")
学了类加载流程后知道 Class.forName()
是将某个包下的类进行 ”加载-连接-初始化“,其中初始化阶段就是执行类的初始化方法,也就是 static 代码块里的方法,看下 com.mysql.cj.jdbc.Driver
的类初始化方法做了什么
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
Driver 将 new 了一个自己,并传递给了 DriverManager.registerDriver() 方法,DriverManager 这个方法中:
public static void registerDriver(java.sql.Driver driver, DriverAction da)throws SQLException {
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
}
将 Driver 添加到了 registeredDrivers 里,registeredDrivers 是一个线程安全的 ArrayList:
private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
registeredDrivers 怎么是个数组?
因为系统中可能既用到了 MySQL 数据库也用到了 Oracle 数据库,或者还有其他数据库,这些数据库的驱动,也就是 Driver 类,实现都是不一样的,所以使用个数组将加载的所有驱动都存起来。
也就是说执行了 Class.forName("com.mysql.cj.jdbc.Driver")
后,DriverManager 就拿到了 com.mysql.cj.jdbc.Driver
对象,存到内部的一个数组中。
获取数据库连接流程
再看一下我们是怎么获取数据库连接的,一段标准的 JDBC 代码:
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "select * from user";
PreparedStatement prep = conn.prepareStatement(sql);
// do something..
其中 DriverManager.getConnection() 的方法最后会调用这个重载方法:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// .... 代码做了精简
// 枚举 registeredDrivers 这个 ArrayList
for(DriverInfo aDriver : registeredDrivers) {
// aDriver.driver 就是 com.mysql.cj.jdbc.Driver
if(isDriverAllowed(aDriver.driver, callerCL)) {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return con;
}
}
}
throw new SQLException("No suitable driver found for "+ url, "08001");
}
DriverManager.getConnection() 会从所有的注册的数据库驱动中尝试连接到目标 URL,调用它的 Driver.connect(url, info)
方法拿到数据库连接,返回给调用者。只有 MySQL 使用 com.mysql.cj.jdbc.Driver.connect(url, info)
, Oracle 使用 oracle.jdbc.driver.OracleDriver.connect(url, info)
才能连接成功。
结论
也就是说调用 DriverManager.getConnection() 就是调用 com.mysql.cj.jdbc.Driver.connect() 。
如果没有执行 Class.forName("com.mysql.cj.jdbc.Driver")
将 MySQL 的驱动注册给 DriverManager 的话,DriverManager 内部的 ArrayList<Driver>
就是空的,执行DriverManager.getConnection()
就什么也得不到。
为什么 JDBC 现在不用写 Class.forName() 加载驱动
不写 Class.forName("com.mysql.cj.jdbc.Driver")
也能获取到 Connection,说明 MySQL 的 Driver 驱动类自己自动注册到了 DriverManager 内部的数组中,有东西帮我们执行了:Class.forName("com.mysql.cj.jdbc.Driver")
,怎么做到的呢?
我们在执行:DriverManager.getConnection(url, username, password)
时,会触发 DriverManager 的类加载流程,也就是会执行 DriverManager 的类初始化方法,看下这个方法:
// 源码做了简化调整
static {
loadInitialDrivers();
}
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) {
// Do nothing
}
return null;
}
});
// ----------------------------分割线------------------------------
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
for (String aDriver : driversList) {
try {
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
}
}
}
先看分割线下面的部分,它会从系统属性 "jdbc.drivers" 中获取数据库驱动 Driver 的全限定名,使用 : 冒号分割,如果系统中同时使用了 MySQL 和 Oracle,那就可以在启动时加上属性:
jdbc.drivers = com.mysql.cj.jdbc.Driver:oracle.jdbc.driver.OracleDriver
接着会执行:
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
也就是在这里帮我们执行了 Class.forName("com.mysql.cj.jdbc.Driver")
但是平时我们写 JDBC 程序时也没有加这个参数啊,它又是怎么自动注册的呢?
ServiceLoader
SPI
先说下 SPI 服务提供者接口(Service Provider Interface)(更具体的涵义查阅其他资料)
我们写的 JDBC 代码都是按照 JDK 提供的接口进行编程的,比如 DriverManager、Driver、Connection、PreparedStatement、ResultSet,它们都是 java.sql 下的包,也就是 JDK 提供的。
这些接口具体的实现类是要由数据库厂商自己写的,我们只需要面向接口编程,而不用直接操作实现类。
在JDBC 环境下,SPI 就是各个 MySQL、Oracle 等数据库厂商提供的 Driver 实现类。
ServiceLoader 就是用来实现服务提供者接口 SPI 的加载
再看分割线上面的部分:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 加载驱动程序,并执行类加载的整个流程:加载-连接-初始化
try{
while(driversIterator.hasNext()) {
driversIterator.next(); //执行Class.forName("com.mysql.cj.jdbc.Driver")
}
} catch(Throwable t) {
// Do nothing
}
以上代码就是在所有加载的 jar 包中(包括启动类加载器和扩展类加载器加载的 jar 包)搜索 META-INF/services
文件夹,看这个文件夹下面有没有java.sql.Driver
的文件,有就将文件里的类加载到内存中并初始化,初始化就意味着会执行类初始化方法。
ServiceLoader 是在使用 LazyIterator 迭代的时候完成类的初始化,内部使用 newInstance() 创建了一个 Driver 实例对象来进行类初始化,而不是 Class.forName
总结
不用写 Class.forName() 是因为使用了 SPI 机制,由 ServiceLoader 加载 com.mysql.cj.jdbc.Driver,
ServiceLoader 帮我们执行了 Class.forName("com.mysql.cj.jdbc.Driver"),触发类加载流程,执行 Driver 的类初始化。
Driver 的类初始化方法中将自己注册给了 DriverManager,
问 DriverManager 要 Connection 就是调用它加载的驱动程序 Driver.connect(),
由 Driver.connect() 拿到 Connection
网友评论