探究一下JDBC中的Class.forName

引言

若是熟悉使用JDBC來鏈接數據庫的同窗必定很清楚鏈接數據庫的代碼中必定會有依據Class.forName("com.mysql.jdbc.Driver");java

public static Connection getConnection() throws ClassNotFoundException, SQLException {
        if(connection == null){
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
        }
        return connection;
    }
複製代碼

以前沒有想過爲何須要有這麼一個語句,都是按照文檔直接這麼作的,在這篇文章中我來試着解釋這麼作的緣由。mysql

類加載機制

在這以前咱們先來講下Java中的類加載機制。sql

在Java中若是想要使用一個類,則必需要求該類已經被加載到Jvm中,加載的過程實際上就是經過類的全限定名來獲取定義該類二進制字節流,而後將這個字節流所表示的靜態存儲結構轉換爲方法去的動態運行時數據結構。同時在在內存中實例化一個java.lang.Class對象,做爲方法區中該類的數據訪問入口(供咱們使用)。數據庫

而會觸發類加載的會有以下幾種狀況(引用自<<深刻理解Java虛擬機>>):bash

  1. 遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。生成這4條指令的最多見的Java代碼場景是:使用new關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。
  2. 使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。
  3. 當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化。
  4. 當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。

Class.forName

在Java官方文檔中對Class.forName的解釋爲在運行時動態的加載一個類,返回值爲生成的Class對象。數據結構

那麼很明顯在jdbc中使用Class.forName("com.mysql.jdbc.Driver");僅僅就是將com.mysql.jdbc.Driver類加載到Jvm中了,這個緣由大多數人應該都知道。函數

可是咱們要知道Class.forName貌似只是對類進行了加載,咱們甚至都沒有對返回的Class對象作任何操做,那麼咱們爲何後面就能夠直接用了呢?測試

首先看Class.forName調用了native方法forName0(...);優化

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

private static native Class<?> forName0(String name, boolean initialize,ClassLoader loader,Class<?> caller);
複製代碼

要注意forName0中有一個關鍵的參數boolean initialize,;該參數用來標識在將該類加載後是否進行初始化操做。能夠看到代碼中是true,就表示會進行初始化操做。ui

初始化過程實際上就是對變量賦值(不是賦初值,不會調用構造函數)的過程。包含全部類變量的賦值以及靜態代碼語句塊的執行代碼,包括對父類的初始化。

再看com.mysql.jdbc.Driver驅動類:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
複製代碼

該類中定義了一個靜態代碼塊,靜態代碼快中建立了一個驅動類實例註冊給了DriverManager,而靜態代碼塊的內容會在初始化的過程當中執行,因此才能經過DriverManager.getConnection直接獲取一個鏈接。

其餘加載類方法

咱們須要明白的是在Java中並非只有經過Class.forName()才能顯示的加載類。那麼爲何不使用其餘的加載方法而恰恰選擇Class.forName()呢?

ClassLoader.getSystemClassLoader().loadClass()

經過類加載器也能夠將一個類加載到Jvm中。經過ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");也能夠加載驅動類。

可是若是咱們深刻看下loadClass的實現:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve);
複製代碼

能夠看到其調用了一個重載的方法,該方法也有一個boolean類型的變量boolean resolve,調用時默認爲false。該參數用於標識是否對加載後的類進行連接操做,若是不進行鏈接操做則不會有初始化的操做。

因此若是使用這種加載類方式的話理論上來講是沒發使用該驅動類的。

new關鍵字

也可使用new關鍵字進行加載操做,在使用new關鍵字時會查看該類是否已經被加載,若是沒有被加載的話則會進行加載操做。因此咱們的類中也能夠這樣寫:

public static Connection getConnection() throws ClassNotFoundException, SQLException {
    if(connection == null){
        new Driver();//會自動調用靜態代碼塊
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxx");
    }
    return connection;
}
複製代碼

可是實際上由於在驅動類的靜態代碼快中實際上已經有了實例化對象並註冊到DriverMananger中的操做。因此這裏根本就沒有在實例化一個對象的過程。使用Class.forName便可,這也算是一個優化的過程吧。

能夠不使用Class.forName("com.mysql.jdbc.Driver")

在測試的過程當中發現即便不顯示的使用Class.forName("com.mysql.jdbc.Driver")也可以鏈接到數據庫,一時間以爲很奇怪。

深刻跟蹤代碼後發現實際上只要咱們引入了mysql的驅動包,那麼在使用時會根據驅動包下提供的配置文件默認的建立一個類。

driver

因此實際上只要引入了該驅動包,那麼使用jdbc是能夠直接經過DriverManage來獲取鏈接。(其實是spi機制)

public static Connection getConnection() SQLException {
    return DriverManager.getConnection("jdbc:mysql://localhost:3306/xxx?serverTimezone=UTC", "root", "xxxxxx");
}
複製代碼
相關文章
相關標籤/搜索