Spring-JDBC 源碼學習(3) —— DriverManager

DriverManager

上面提到 DataSource 獲取連接(Connection) 的操作實質上將委託具體的 Driver 來提供 Connection 。有兩種不同的方式,包括經由 DriverManager 遍歷所有處於管理下的 Driver 嘗試獲取連接,或者在 DataSource 實例中直接聲明一個特定的 Driver 來獲取連接。

對於獲取連接的具體操作,挖坑-待填。只描述簡單的數據庫供應商提供的 Driver 如何與 java 相聯繫。

在 DriverManager 中註冊 Driver 實例

通常在與數據庫交互邏輯的 Java 代碼中,都會有 Class.forName("com.mysql.jdbc.Driver") (此處以 MySQL 提供的 mysql-connector-java-XXX.jar 爲例,下同)的代碼塊,加載指定的 com.mysql.jdbc.Driver 爲 java.lang.Class 類。

當然,在 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 包)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章