使用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
下,引入以后就没有问题了。
看到了这里必定是真爱了,关注微信公众号【憨憨的春天】第一时间获取更新