jdbc连接数据库一定要用Class.forName吗?

一来源

上周,在调试使用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包。