JDBC驅動爲何沒加載

使用Class.forName加載驅動

使用JDBC鏈接數據庫的時候,須要先加載驅動。能夠經過Class.forName聲明要加載的驅動,加載這個詞在這裏其實不太明確,由於Class.forName不僅是把類加載到了內存中,還會初始化(static塊中的代碼會被執行)。註冊驅動其實就發生在 static 塊中。好比mysql的驅動com.mysql.cj.jdbc.Driverjava

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

因此這裏是沒法使用ClassLoader.loadClass()來替換的。mysql

不使用Class.forName

在JDBC 4.0以後,能夠經過SPI的方式加載驅動。sql

在驅動相應的jar包裏,META-INF/services目錄下,會有名爲java.sql.Driver的文件,裏面的內容是驅動的全路徑名。數據庫

好比在mysql-connector-java-8.0.16.jar中,META-INF/services目錄下的java.sql.Driver內容爲:apache

com.mysql.cj.jdbc.Driver

DriverManager初始化的時候會經過SPI加載全部Driver接口的實現類微信

在DriverManager中有以下代碼app

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

loadInitialDrivers方法中包含了兩部分ide

  • 經過系統屬性jdbc.drivers加載驅動
  • 經過SPI的方式加載

看一下經過SPI方式加載的部分ui

AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Void run() {

        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();

        /* Load these drivers, so that they can be instantiated.
            * It may be the case that the driver class may not be there
            * i.e. there may be a packaged driver with the service class
            * as implementation of java.sql.Driver but the actual class
            * may be missing. In that case a java.util.ServiceConfigurationError
            * will be thrown at runtime by the VM trying to locate
            * and load the service.
            *
            * Adding a try catch block to catch those runtime errors
            * if driver not available in classpath but it's
            * packaged as service and that service is there in classpath.
            */
        try{
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {
        // Do nothing
        }
        return null;
    }
});

第一次看的時候很疑惑,爲何只經過迭代器遍歷了一遍就實現加載了。spa

跟了代碼發現ServiceLoaderIterable的實現中進行了初始化,代碼能夠參考ServiceLoader類的nextService方法

private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

注意第一次調用Class.forName(cn, false, loader)並無初始化,而是在後面service.cast(c.newInstance())進行的初始化。

爲何JDBC驅動沒有加載

最近碰到了個問題,使用phoenix進行jdbc鏈接的時候報錯

java.sql.SQLException: No suitable driver found for jdbc:phoenix:127.0.0.1:2182

而若是代碼中經過Class.forName聲明,卻不會報錯,能夠確定是經過SPI註冊的時候有問題。

phoenix-core.jar包中的java.sql.Driver內容爲

org.apache.phoenix.jdbc.PhoenixDriver

和我使用Class.forName聲明時是同樣的

後來在跟代碼的時候發現經過SPI加載驅動時,獲取到了一個驅動org.apache.calcite.avatica.remote.Driver,而在加載這個類的時候報錯了,classpath中並無這個類。參考代碼,能夠看到遍歷的時候只要有一次報錯後續就不會執行了。

/* Load these drivers, so that they can be instantiated.
    * It may be the case that the driver class may not be there
    * i.e. there may be a packaged driver with the service class
    * as implementation of java.sql.Driver but the actual class
    * may be missing. In that case a java.util.ServiceConfigurationError
    * will be thrown at runtime by the VM trying to locate
    * and load the service.
    *
    * Adding a try catch block to catch those runtime errors
    * if driver not available in classpath but it's
    * packaged as service and that service is there in classpath.
    */
try{
    while(driversIterator.hasNext()) {
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}

註釋中也寫到可能會有驅動類不存在的狀況,因此加了一個異常處理。

看到org.apache.calcite.avatica.remote.Driver類,想到了項目中使用的kylin,翻看kylin-jdbc相應的java.sql.Driver內容爲

org.apache.calcite.avatica.remote.Driver

org.apache.calcite.avatica.remote.Driver這個類實際上是在org.apache.calcite.avatica:avatica下,引入以後就沒有問題了。

總結

  1. 使用Class.forName加載驅動時,把類加載到內存同時進行了初始化,註冊驅動的過程發生在初始化中。
  2. JDBC4.0後能夠經過SPI方式註冊驅動。
  3. 經過SPI方式註冊驅動時若是有一個驅動加載出問題,會影響後續的驅動加載。

看到了這裏必定是真愛了,關注微信公衆號【憨憨的春天】第一時間獲取更新
qrcode_for_gh_7fff61e23381_344.jpg

相關文章
相關標籤/搜索