1、Class.forName()加載
這是最常見的一種方式,也是從早期沿用下來的方式。下面看一下在這背後發生的事情。
Driver接口
先來了解下java.sql.Driver接口,java.sql.Driver是所有JDBC驅動程序需要實現的接口。這個接口是提供給資料庫廠商使用的,不同廠商實現該接口的類名是不同的,例如MySQL 8.x的JDBC驅動的類名是:com.mysql.cj.jdbc.Driver。
Driver接口接口中一個connect方法,用來建立到資料庫的連接,,該方法的籤名如下所示:
Connection connect(String url, Properties info) throws SQLException在程序中不需要直接去訪問這些實現了Driver接口的類,而是由驅動程序管理器去調用這些驅動。我們通過JDBC驅動程序管理器註冊每個驅動程序,使用驅動程序管理器類提供的方法來建立資料庫連接,而驅動程序管理器類的連接方法則調用驅動程序類的connect方法建立資料庫連接,如下圖所示:
加載JDBC驅動是調用Class類的靜態方法forName,向其傳遞要加載的JDBC驅動的類名。在運行時,類加載器從CLASSPATH路徑中定位和加載JDBC驅動類。在加載驅動程序類後,需要註冊驅動程序類的一個實例。
DriverManager類是驅動程序管理器類,負責管理驅動程序,這個類中所有的方法都是靜態的。在DriverManager類中提供了registerDriver方法來註冊驅動程序類的實例,該方法的籤名如下所示:
public static void registerDriver(Driver driver) throws SQLException加載與註冊JDBC驅動
加載JDBC驅動是調用Class類的靜態方法forName,向其傳遞要加載的JDBC驅動的類名。在運行時,類加載器從CLASSPATH路徑中定位和加載JDBC驅動類。在加載驅動程序類後,需要註冊驅動程序類的一個實例。
DriverManager類是驅動程序管理器類,負責管理驅動程序,這個類中所有的方法都是靜態的。在DriverManager類中提供了registerDriver方法來註冊驅動程序類的實例,該方法的籤名如下所示:
public static void registerDriver(Driver driver) throws SQLException通常不需要我們親自去調用registerDriver方法來註冊驅動程序類的實例,因為實現Driver接口的驅動程序類都包含了靜態代碼塊,在這個靜態代碼塊中,會調用DriverManager.registerDriver方法來註冊自身的一個實例。當這個類被加載時(調用Class.forName),類加載器會執行該類的靜態代碼塊,從而註冊驅動程序類的一個實例。
下面我們看一下MySQL 8.0.20的驅動程序類對自身實例註冊的代碼片段。
MySQL 8.0.20 JDBC驅動程序類的代碼片段// com.mysql.cj.jdbc.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
...
}
可以看到Driver類所包含的靜態代碼塊是調用DriverManager類的靜態方法registerDriver來註冊Driver類的實例。
2、服務加載
JDBC API定義了Driver接口,不同的資料庫廠商給出該接口的實現,如果廠商遵循SPI機制,那麼就可以利用服務加載器來找到並加載Driver實現類。
用解壓縮軟體打開mysql-connector-java-8.0.20.jar,可以看到在METF-INF\services目錄下有一個java.sql.Driver文件,文件內容如下:
com.mysql.cj.jdbc.Driver
這正是MySQL的JDBC驅動的類名。
查看java.sql.DriverManager類的原始碼,可以發現在ensureDriversInitialized方法中,實現了JDBC驅動類的加載,如以下代碼所示。
DriverManager類的ensureDriversInitialized方法// java.sql.DriverManager
private static void ensureDriversInitialized() {
if (driversInitialized) {
return;
}
synchronized (lockForInitDrivers) {
if (driversInitialized) {
return;
}
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty(JDBC_DRIVERS_PROPERTY);
}
});
} catch (Exception ex) {
drivers = null;
}
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);
if (drivers != null && !drivers.equals("")) {
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
driversInitialized = true;
println("JDBC DriverManager initialized");
}
}
熟悉的配方,熟悉的味道。只要mysql-connector-java-8.0.20.jar在類路徑中能夠找到,DriverManager就可以利用SPI機制找到並加載MySQL的JDBC驅動類。換句話說,對於支持SPI機制的JDBC驅動,可以不用調用Class.forName來加載驅動類。
3、系統變量
上述代碼中有如下一句代碼:
System.getProperty(JDBC_DRIVERS_PROPERTY);
JDBC_DRIVERS_PROPERTY是DriverManager類中定義的一個私有的靜態字符串常量,值為:jdbc.drivers。
還有一種設置JDBC驅動類名的方式就是利用系統屬性jdbc.drivers來設置,在執行程序時,使用-D選項給出JDBC驅動的類名,如下所示:
java -Djdbc.drivers=com.mysql.cj.jdbc.Driverch24.GetDBInfo
如果要設置多個JDBC驅動類名,以冒號(:)分隔即可。採用這種方式,也不需要調用Class.forName。這種方式並不常用。
由於歷史遺留原因,大多數的資料庫訪問程序中,仍然習慣使用Class.forName來加載JDBC驅動類。
我是專注於軟體開發和IT教育的孫鑫老師,喜歡我的文章歡迎關注、轉發、評論、點讚和收藏,我會經常與大家分享IT技術、程式語言的文章和教學視頻。目前已發布完整的《Vue.js從入門到實戰》教學視頻,正在發布《Java無難事》教學視頻。
#Java#