Spring-JDBC 原始碼學習(3) —— DriverManager
DriverManager
上面提到 DataSource 獲取連線(Connection) 的操作實質上將委託具體的 Driver 來提供 Connection 。有兩種不同的方式,包括經由 DriverManager 遍歷所有處於管理下的 Driver 嘗試獲取連線,或者在 DataSource 例項中直接宣告一個特定的 Driver 來獲取連線。
對於獲取連線的具體操作,挖坑-待填。只描述簡單的資料庫供應商提供的 Driver 如何與 java 相聯絡。
在 DriverManager 中註冊 Driver 例項
通常在與資料庫互動邏輯的 Java 程式碼中,都會有 Class.forName("com.mysql.jdbc.Driver")
當然,在 JDBC 4.0 標準下,可以不必再顯示宣告 Class.forName("")
語句,Driver 也同樣會在 DriverManager 初始化時自動註冊。
// Class 類中對於 forName(String className) 的方法
// 作用為返回一個 java.lang.Class 例項。
public static Class<?> forName (String className) throws ClassNotFoundException {...}
同時, JVM 在載入類的過程中會執行類中的 static 程式碼塊。下述 row 10 ~ 16 的程式碼片段將被執行。唯一的邏輯就是 new 一個 com.mysql.jdbc.Driver 例項,並將例項註冊(registerDriver) 到 java.sql.DriverManager 中。
package com.mysql.jdbc;
public class Driver extends NonRegisteringDriver implements java .sql.Driver {
// ~ Static fields/initializers
// ---------------------------------------------
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
// ~ Constructors
// -----------------------------------------------------------
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
下面再來看一下 DriverManager 中的 registerDriver() 方法。
public class DriverManager {
// DriverManager 維護一個執行緒安全的 Driver 列表
// 此處的 DriverInfo 裡面即包裝了 Driver
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers =
new CopyOnWriteArrayList<>();
// 在 DriverManager 中註冊 Driver
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* 如果當前 Driver 不在列表中,即新增到列表。 */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
}
通過 DriverManager 獲取連線(Connection)
上一節有提到過可以通過 DriverManager 來遍歷獲取連線,也可以直接宣告具體 Driver 並獲取連線。下面程式碼展示的是通過 DriverManager 獲取連線的操作。 哈哈哈,反正最後都是由具體驅動實現獲取連線。
public class DriverManager {
// 獲取連線的 public 介面 (1)
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連線的 public 介面 (2)
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連線的 public 介面 (3)
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 獲取連線的內部邏輯實現
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller)
throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
// url 是定位 DBMS 最重要的引數,不能為空
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// 遍歷所有註冊的 Driver ,並都嘗試獲取連線(Connection)
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// 判斷註冊的 Driver 是否由 ClassLoader callerCL 載入,不是則跳過
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 獲取連線,:) 還是由 driver 例項自行提供
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
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");
}
}
簡單的提一嘴,Connection 仍然只是一個針對 Java -> DB Server 的上層介面,如果想要更深層次地瞭解 Connection 與 DB Server 的互動,可以嘗試去看一下 com.mysql.jdbc.MysqlIO 類,MySQL 實現的 JDBC4Connection 類也是在使用該類來實現對 DB Server 互動。(哈哈,只看過 MySQL 提供的 Driver 包)。