一来源
上周,在调试使用jdbc连接infobright的时候,遇到了一个奇怪的问题,当我使用Class.forName加载myslq jdbc的驱动的进行连接数据库的时候,连接缺报异常了。奇怪的是异常信息并不是mysql驱动抛出来的,而是另一个数据库驱动,达梦的驱动异常。
二好奇心
为什么会这样呢,我明明没有加载达梦的数据集驱动呀?连不上也就算了,还抛了个其他的异常,百思不得其解。于是,我debug到了DriverManager中的源码看了看,发现已经加载了各种不同的jdbc驱动有6,7种之多。就这样,顺带的,我凑了眼DriverManager的源码。
三源码解读
我用的版本是1.7.0_17,所以我只在这个版本上讲讲。
首先我们看看它的static方法,挺简单,就掉了一个方法。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
接下来,我们看看调用的这个方法loadInitialDrivers。
private static void loadInitialDrivers() {
String drivers;
try {
// 不通过权限检查,直接查找系统的jdbc.drivers
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
//英文写的挺详细的,如果驱动被打包作为一个服务提供者,加载它。
//通过这个类加载器加载所有的驱动。
//使用ServiceLoader.load() 代替 sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator 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{
//ServiceLoader是一个可迭代的懒加载器,调用next实现类的加载。
while(driversIterator.hasNext()) {
println(" Loading done by the java.util.ServiceLoader : "+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);
}
}
}
四对比
我们看了1.7的代码,接下来看下1.6的实现。
开始都差不多,初始化的时候并没有在类初始的时候执行,移到了第一次调用此方法的时候,里面有个判断,如果没有加载的话会加载一遍。这是一种懒加载的形式,但是后面每次加载驱动的时候都要判断,效率上要比1.7的代码低一点。
// Class initialization.
static void initialize() {
if (initialized) {
return;
}
initialized = true;
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
然后我们再看看loadInitialDrivers的实现。
private static void loadInitialDrivers() {
String drivers;
try {
drivers = (String) java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("jdbc.drivers"));
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider,
// load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
//使用sun的Service.providers方法来加载驱动,至于1.7为什么要改写,还需要执行琢磨下。
//安装1.7的说法是由于如果一个驱动加载失败就会导致所有的驱动加载失败,1.7进行了异常捕获。
DriverService ds = new DriverService();
// Have all the privileges to get all the
// implementation of java.sql.Driver
java.security.AccessController.doPrivileged(ds);
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null) {
return;
}
while (drivers.length() != 0) {
int x = drivers.indexOf(':');
String driver;
if (x < 0) {
driver = drivers;
drivers = "";
} else {
driver = drivers.substring(0, x);
drivers = drivers.substring(x+1);
}
if (driver.length() == 0) {
continue;
}
try {
println("DriverManager.Initialize: loading " + driver);
Class.forName(driver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
五其他
我们可以看看DriverManager从1.7之后改了很多。
例如:在1.7中使用CopyOnWriteArrayList来存储注册的驱动信息的。
在这里解释下这个类。CopyOnWriteArrayList表示在写的时候先copy一份出来,然后再进行写操作,写完后再将原来的引用指向到当前这个数据对象,这样保证了写的一致性。然后读的时候就是在当前对象上读,不存在加锁的问题。CopyOnWriteArrayList的写因为需要大量的copy,所以性能会比较差,读的话不用加锁,性能高。所以适合在写少读多的场景下使用。
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
在1.6中使用的是:一个writeDrivers和一个readDrivers,造成空间浪费和并发效率的问题。
/* write copy of the drivers vector */
private static java.util.Vector writeDrivers = new java.util.Vector();
/* write copy of the drivers vector */
private static java.util.Vector readDrivers = new java.util.Vector();
六SPI
顺便提一下,DriverManager用到了SPI,即Service Provider Interfaces。这种方式为程序动态的扩展提供了便利,当你添加一个服务的时候(例如驱动类),不需要重新修改代码来利用这个服务的功能。相比叫spring 依赖反转而言,也是一种不错的实现方式,可以不依赖与任何第三方jar包。