SPI 技術:JDBC加載不同數據庫廠商的驅動

SPI 技術

SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啓用框架擴展和替換組件。

Java SPI 實際上是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。

實現方式:

當服務的提供者提供了一種接口的實現之後,需要在 Classpath 下的 META-INF/services/ 目錄裏創建一個以服務接口命名的文件,此文件記錄了該 jar 包提供的服務接口的具體實現類。當某個應用引入了該 jar 包且需要使用該服務時,JDK SPI 機制就可以通過查找這個 jar 包的 META-INF/services/ 中的配置文件來獲得具體的實現類名,進行實現類的加載和實例化,最終使用該實現類完成業務功能。

樣例

抽象接口類:

public interface Log {

    public void log(String log);
}

兩個實現類:

public class Log4j implements Log {

    @Override
    public void log(String log) {
        System.out.println("log4j:" + log);
    }
}

public class Logback implements Log {

    @Override
    public void log(String log) {
        System.out.println("logback " + log);
    }
}

測試類:

public class SpiTestMain {

    public static void main(String[] args) {
        ServiceLoader<Log> serviceLoader =
                ServiceLoader.load(Log.class);
        Iterator<Log> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            Log log = iterator.next();
            log.log("JDK SPI");
        }
    }
}

在項目的 resources/META-INF/services 目錄下添加的文件以及內容

運行結果:

源碼分析

 

JDBC加載不同數據庫廠商的驅動

以我使用的mysql-connector-java-8.0.20.jar爲例,在jar 包中的 META-INF/services 目錄下,有一個 java.sql.Driver 文件中只有一行內容

com.mysql.cj.jdbc.Driver

如果我們手動加載JDBC通常會寫這樣的代碼:

String url = "jdbc:xxx://xxx:xxx/xxx";
Connection conn = DriverManager.getConnection(url, username, pwd);

DriverManager 是 JDK 提供的數據庫驅動管理器,它的靜態代碼塊:

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

在調用 getConnection() 方法的時候,DriverManager 類會被 Java 虛擬機加載、解析並觸發 static 代碼塊的執行

 

private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } 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()) {
                        //該地方雖然只是調用了next,但是卻是會獲取到Driver的實現類,觸發實現類的初始化
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        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);
            }
        }
    }

注意:driversIterator.next();該代碼雖然只是調用了next,但是卻是會獲取到Driver的實現類,觸發實現類的初始化,以配置文件配置的com.mysql.cj.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註冊了自己。

 

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