一开始我们在学习JDBC的时候,老师就教我们了以下几步来建立JDBC连接.
public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/springboot", "root", "redhat"); PreparedStatement statement = connection.prepareStatement("select * from tag"); ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { //do something } }
首先Class.forName来加载注册我们的mysql驱动,执行完这个Class.forName, 到底我们的类里,哪些方法会被执行呢.我们写个类来测试一下,测试类如下
package org.linuxsogood.boot.jdbc; public class TestInit { static { System.out.println("load me"); } { System.out.println("nomal load me"); } public TestInit() { System.out.println("construct method is execute"); } }
最后我们使用Class.forName(“org.linuxsogood.boot.jdbc.TestInit”)测试的结果是,只有静态代码块里的代码被执行了.
我们再看一下myql的Driver类里是怎么写的
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { // Required for Class.forName().newInstance() } }
mysql的Driver类,其实还是调用了DriverManager的注册驱动方法,new了一个自己的类.仅此. 所以我们在使用JDBC连接MySQL的时候,可以完全不用Class.forName, 我们可以直接使用DriverManager的registerDriver方法手动来注册,测试下来,结果也确实是一样的.
然后我们再重点关注一下DriverManager类
2. 为什么我不写Class.forName, 直接使用DriverManager.getConnection也可以获取MySQL连接?
我测试着把Class.forName给注释掉,发现程序依然能正常运行,这和我们初学的时候,老师教我们的,好像有点违背,不是说一定要按这个步骤吗? 太奇怪了.那下面我们就重点关注一下DriverManager这个类,到底是在搞什么飞机.
首先看一下源码上的注释
* <P>As part of its initialization, the <code>DriverManager</code> class will * attempt to load the driver classes referenced in the "jdbc.drivers" * system property. This allows a user to customize the JDBC Drivers * used by their applications. For example in your * ~/.hotjava/properties file you might specify: * <pre> * <CODE>jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver</CODE> * </pre> *<P> The <code>DriverManager</code> methods <code>getConnection</code> and * <code>getDrivers</code> have been enhanced to support the Java Standard Edition * <a href="../../../technotes/guides/jar/jar.html#Service%20Provider">Service Provider</a> mechanism. JDBC 4.0 Drivers must * include the file <code>META-INF/services/java.sql.Driver</code>. This file contains the name of the JDBC drivers * implementation of <code>java.sql.Driver</code>. For example, to load the <code>my.sql.Driver</code> class, * the <code>META-INF/services/java.sql.Driver</code> file would contain the entry: * <pre> * <code>my.sql.Driver</code> * </pre> * <P>Applications no longer need to explicitly load JDBC drivers using <code>Class.forName()</code>. Existing programs * which currently load JDBC drivers using <code>Class.forName()</code> will continue to work without * modification. * * <P>When the method <code>getConnection</code> is called, * the <code>DriverManager</code> will attempt to * locate a suitable driver from amongst those loaded at * initialization and those loaded explicitly using the same classloader * as the current applet or application. *
重点读一下以上说明,大概意思是说:
DriverManager会先读取系统环境变量jdbc.drivers属性, 我们可以通过设置这个属性的值,来达到让DriverManager来帮我们注册数据库驱动的目的,如果有多个数据库驱动,多个之间使用:号分隔
DriverManager的getConnection方法做了增强, 自从JDBC 4.0开始,就不需要再使用Class.forName的方式来注册数据库驱动了, 使用的是一java里面的Service Loader的机制, Service Loader机制要求在classpath下面有一个META-INF/services的目录,并且在这个目录下,建立一个接口全路径名的文件,比如java.sql.Driver的文件,内容写上
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
这样在使用ServiceLoader.load(Driver.class)的时候,文件里面这两个类,会被系统加载,并且延迟执行.
和Class.forName不同的就是这个是延迟执行的,只有当你把这个类取出来的时候,里面的静态方法,才会被执行.
我们继续看getConnection方法,到底是怎么搞的
private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection(/"" + url + "/")"); SQLException reason = null; for(DriverInfo aDriver : registeredDrivers) { if(isDriverAllowed(aDriver.driver, callerCL)) { try { println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); }
我们发现他是从registeredDrivers这个集合中去找对应的驱动.是根据你数据库连接的URL来找的.那这个集合是啥时候被初始化的呢.我们代码只写了一个getConnection方法,别的没写,应该不会跑出DriverManager这个类,当我们调用这个类的方法的时候,我们知道,静态代码块应该会被调用,看一下这个类的静态代码块.
/** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {@code ServiceLoader} mechanism */ static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
跟踪loadInitialDrivers方法
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; } 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); 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); } } }
感觉回到了这个类上的注释上面, 首先读取系统变量jdbc.drivers, 其次我们看到了一个ServiceLoader.load方法,load了Driver.class类. 这个就是前面注释上讲过的ServiceLoader, 它会将实现Driver接口的classPath中找META-INF/service中的java.sql.Driver文件中的驱动,并且加载他们.为什么是META-INF/service这个路径,这是因为ServiceLoader的实现机制就是这样的,去在这样一个路径下寻找你的接口的全路径名的文件,并加载其中的每一行. 因为java对JDBC的驱动都必须实现java.sql.Driver接口,所以就是找这个文件.然后依次循环. 如果没有后面的循环,我们的类只是被加载进内存,并不会触发里面的静态代码块的方法.这是因为ServiceLoader的懒加载机制.
3.模拟java.sql.Driver自己写一个使用ServiceLoader的例子
首先定义一个接口
package org.linuxsogood.boot.serviceloader; public interface IService { String sayHello(); String getSchema(); }
然后定义实现类
package org.linuxsogood.boot.serviceloader; public class HDFSServiceImpl implements IService { static { System.out.println("load HDFSServiceImpl"); } @Override public String sayHello() { return "hello hdfs"; } @Override public String getSchema() { return "hdfs"; } }
再定义一个
package org.linuxsogood.boot.serviceloader; public class NTFSServiceImpl implements IService { @Override public String sayHello() { return "hello ntfs"; } @Override public String getSchema() { return "ntfs"; } }
在classpath目录下,我这里是maven项目,就在resources目录下,创建META-INF/services目录
然后建立一个org.linuxsogood.boot.serviceloader.IService名字的文件,里面写入以下两行
org.linuxsogood.boot.serviceloader.HDFSServiceImpl org.linuxsogood.boot.serviceloader.NTFSServiceImpl
写一个测试类来测试
package org.linuxsogood.boot.serviceloader; import java.util.Iterator; import java.util.ServiceLoader; public class TestServiceLoader { public static void main(String[] args) { ServiceLoader<IService> load = ServiceLoader.load(IService.class); Iterator<IService> iterator = load.iterator(); while (iterator.hasNext()) { iterator.next(); } } }
我们发现,HDFSServiceImpl里面的静态代码块被执行了. 如果云掉下面的遍历操作,加载能成功,但是静态代码块不会被调用.这也就是为什么,MySQL的Driver里面,初始化要在静态代码块里面来做了