前言
《Mysql Connector/J 源码分析(综述)》提到普通的Connection是最基本的连接。本文试图揭开它的内幕,看看它是如何与Mysql数据库管理软件(下文简称Mysql)交互的。
本次分析的版本为5.1.46。若通过maven下载,可添加以下依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
一、准备工作
撇开所有框架,我们最原始的获取Mysql连接的方式如下:
Connection conn = null;
URL =“jdbc:mysql://ip:port/dbname”;
try{
// 注册 JDBC 驱动
Class.forName("com.mysql.jdbc.Driver");
// 打开链接
conn = DriverManager.getConnection(DB_URL,USER,PASS);
....
这几行代码表面上看,只是加载了"com.mysql.jdbc.Driver"这个类,然后把url和用户名与密码通过DriverManager#getConnection方法就能够得到连接了。貌似连Class#forName的方法调用都可以省略。但如果我们真把这句删掉,程序就跑不下去了。
以前有个牛人说过“简单是复杂的最终状态”,简单的两行代码,背后藏着不简单处理,目的就是让我们用得轻松,我们做软件设计时不正是抱着这宗旨吗?
我们看看com.mysql.jdbc.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()
}
}
从结构上看,它继承了NonRegisteringDriver,实现了java.sql.Driver接口。代码简洁,只有看上去不复杂的静态块和什么事情都没做的构造函数。
这里有一个常识性在里面,就是加载一个类的时候,静态块就会被执行。所以当程序执行Class.forName("com.mysql.jdbc.Driver")命令的时候,com.mysql.jdbc.Driver的静态块会被执行,但它并不是第一段被执行的静态块,原因是Driver继承了NonRegisteringDriver。所以程序会先执行NonRegisteringDriver的静态块,然后再执行Driver的静态块代码。NonRegisteringDriver的静态块所做的事情并不是本文要探讨的范围,看官可自行阅读。
在Driver的静态块里,只是构建一个Driver,然后就注册到 DriverManager。然而,DriverManger自己也有静态块,当程序执行java.sql.DriverManager.registerDriver(new Driver());命令的时候,DriverManger的静态块会先被执行,然后再执行registerDriver命令。它的静态块代码如下:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
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);
}
}
}
它的静态块主要是调用了loadInitialDrivers静态方法。该方法主要做的事情就是加载所有的驱动类到JDK里。这些驱动类来源于两部分,一部分是jdk的环境变量jdbc.drivers,另一部分使用了java spi机制,加载实现了“java.sql.Driver”接口的驱动类。当前jar包/META-INF/services/java.sql.Driver文件内容是:
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
所以,这些类都将会被加载。根据程序的执行顺序,com.mysql.jdbc.Driver的静态块正在执行中,后续将补充执行FabricMySQLDriver的静态块。
现在,我们简单的归纳下Class.forName("com.mysql.jdbc.Driver")命令,引发的一连串操作:
- NonRegisteringDriver的静态块被执行,创建了一条守护线程,定时清空丢弃的连接。
- com.mysql.jdbc.Driver的静态块被执行,但没执行完毕
- DriverManager的静态块被执行,加载所有的驱动类进入JDK,从而轮循地触发每个驱动的静态块的执行。
- 继续执行com.mysql.jdbc.Driver的静态块,构造com.mysql.jdbc.Driver对象并注册到DriverManager里。
- 执行com.mysql.fabric.jdbc.FabricMySQLDriver的静态块,构造com.mysql.fabric.jdbc.FabricMySQLDriver对象并注册到DriverManager里。
所以,设计者就是利用静态块在类被加载进JDK时自动执行的特性,暗地里做了一些事情,包括将驱动注册到DriverManager。注册驱动的时候,驱动会被保存在DriverManager的registeredDrivers静态属性里。
二、构造连接
2.1 连接的实现类
当执行conn = DriverManager.getConnection(DB_URL,USER,PASS);命令的时候,我们跟踪调用链,会来到DriverManager#getConnection( String , java.util.Properties , Class<?> )方法:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
....
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
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());
}
}
....
}
该方法轮循已注册到DriverManager的驱动,如果哪个驱动能根据url等信息创建连接,就返回此连接,不再继续轮循其他驱动。不过,一个驱动无法根据url等信息成功地创建连接的原因会很多,所以这种轮循的机制存在进一步优化的空间。在当前,控制着注册到DriverManager的驱动数量也是一种折衷的方法。
从上面的代码片断看出,接下来的创建连接的工作交给驱动去做。因为只有com.mysql.jdbc.Driver才能解析到我们的url,所以由它的实例对象建立连接。但它并没有重载connect方法,所以这操作由父类NonRegisteringDriver#connect方法进行。
public java.sql.Connection conn