使用JDBC鏈接數據庫的時候,須要先加載驅動。能夠經過Class.forName聲明要加載的驅動,加載這個詞在這裏其實不太明確,由於Class.forName不僅是把類加載到了內存中,還會初始化(static塊中的代碼會被執行)。註冊驅動其實就發生在 static 塊中。好比mysql的驅動com.mysql.cj.jdbc.Driver
java
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
因此這裏是沒法使用ClassLoader.loadClass()
來替換的。mysql
在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
看一下經過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
跟了代碼發現ServiceLoader
在Iterable
的實現中進行了初始化,代碼能夠參考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())
進行的初始化。
最近碰到了個問題,使用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
下,引入以後就沒有問題了。
看到了這裏必定是真愛了,關注微信公衆號【憨憨的春天】第一時間獲取更新