橋接模式在JDBC原始碼中的應用

2020-10-13 01:00:24
在 JDBC API 中,大家非常熟悉的 Driver 類就是橋接物件。使用 JDBC 時通過 Class.forName() 方法可以動態載入各個資料庫廠商實現的 Driver 類。

下面以 MySQL 的實現為例,具體使用者端應用程式碼如下:
// 1. 註冊JDBC驅動
// 反射機制載入驅動類
Class.forName("com.mysql.jdbc.Driver");
// 2. 獲取連線Connection
conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 3. 獲取sql語句的物件Statement
Statement stmt = conn.createStatement();
// 4. 執行sql語句,並返回結果
ResultSet rs = stmt.executeQuery(sql);
以下為 Driver 介面的定義。
public interface Driver {

    Connection connect(String url, java.util.Properties info) throws SQLException;

    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

    int getMajorVersion();

    int getMinorVersion();

    boolean jdbcCompliant();

    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
Driver 在 JDBC 中並沒有具體實現,具體的功能實現由各廠商完成,下面是 MySQL 中 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!");
        }
    }
}
Driver 實現類的程式碼特別簡短,其中只呼叫了 DriverManager 中的 registerDriver 方法來註冊驅動。當驅動註冊完成後,就會開始呼叫 DriverManager 中的 getConnection 方法了。

當我們執行到 Class.forName("com.mysql.jdbc.Driver") 方法的時候,就會執行 com.mysql.jdbc.Driver 類的靜態塊中的程式碼。靜態塊中的程式碼只是呼叫了一下 DriverManager 的 registerDriver() 方法,然後將 Driver 物件註冊到 DriverManager 中。下面繼續跟進 DriverManager 類,相關程式碼如下。
public class DriverManager {

    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
    private static volatile int loginTimeout = 0;
    ......
    private final static Object logSync = new Object();
    private DriverManager(){}

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

    ......
    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);
    }
......
}
在註冊之前,將傳過來的 Driver 物件封裝成一個 DriverInfo 物件。
class DriverInfo {

    final Driver driver;
    DriverInfo(Driver driver) {
        this.driver = driver;
    }

    public boolean equals(Object other) {
        return (other instanceof DriverInfo)
                && this.driver == ((DriverInfo) other).driver;
    }

    public int hashCode() {
        return driver.hashCode();
    }

    public String toString() {
        return ("driver[className="  + driver + "]");
    }
}
DriverInfo 本身其實就是 Driver。接下來繼續執行使用者端程式碼的第二步,呼叫 DriverManager 的 getConnection() 方法獲取連線物件,下面跟進原始碼。
public class DriverManager{
    ......
    public static Connection getConnection(String url,
        java.util.Properties info) throws SQLException {
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
     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 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 {
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized (DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
          
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    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");
    }
}
在 getConnection() 中,又會呼叫各自廠商實現的 Driver 的 connect() 方法獲得連線物件。這樣就巧妙地避開了使用繼承,為不同的資料庫提供了相同的介面。JDBC API 中的 DriverManager 就是橋,如下圖所示。

JDBC橋接模式類圖