在使用JDBC时,都需要提前加载对应数据库的Driver类,MySQL为例:
Class.forName("com.mysql.jdbc.Driver")
但是不写这句代码也可以让 com.mysql.jdbc.Driver 类成功加载,这就涉及到 java.sql.DriverManager 类了,先看一下代码:
public class DriverManager { // 注册驱动的集合 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>(); // 初始化驱动 static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
它位于启动类路径之下,由 BootStrap ClassLoader 加载,在加载的初始化过程中,运行 static 代码块里的 loadInitialDrivers() 方法,在该方法里加载了 Driver 类,具体如下:
private static void loadInitialDrivers() { String drivers; try { drivers = AccessController.doPrivileged(new PrivilegedAction<String> () { public String run() { return System.getProperty("jdbc.drivers"); } }); } catch (Exception ex) { drivers = null; } // 1)使用 ServiceLoader 机制加载驱动,即 SPI AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); println("DriverManager.initialize: jdbc.drivers = " + drivers); // 2)使用 jdbc.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); // 这里的 ClassLoader.getSystemClassLoader() 就是 Application ClassLoader 类加载器 Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); } catch (Exception ex) { println("DriverManager.Initialize: load failed: " + ex); } } }
可以看到,一开始定义了 drivers 属性,通过 System.getPorperty 方法试图获取系统变量中定义的 jdbc.drivers 变量值并赋给 drivers,如果结果不为空,再看标号 2)部分的代码,这段代码是使用 Application ClassLoader 加载 drivers 属性中指定的类。而系统变量中如果没有定义 jdbc.drivers,也不用担心,看标号 1)的代码部分,它使用 SPI 机制加载驱动类,如图:
mysql-connector-java-5.1.47.jar 中,含有 META-INF→services→java.sql.Driver 文件。在 SPI 机制中,只要对应文件夹下的文件名和类接口名对应,就可以通过 ServiceLoader.load() 方法加载对应文件中记录的实现了该接口的类(当然,具体过程比较复杂,有耐心的读者可以自行查看load函数的调用链)。到此,就实现了不显示写出 Class.forName("com.mysql.jdbc.Driver"),也可以加载Driver类的功能。