一开始我们在学习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里面,初始化要在静态代码块里面来做了