注意,jdbc4.0不再需要显示调用class.forName()注册驱动,而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册,DriverManager是一个单例类。
参见如下简单的程序
String url ="jdbc:mysql://localhost:3306/jdbcana";
String username = "root";
String password = "root";
Connection con = DriverManager.getConnection(url, username, password);
DriverManager.getConnection(url, username, password)会返回Connection,看源码:
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
// Gets the classloader of the code that called this method, may
// be null.
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, callerCL));
}
接下来调用getConnection(url, info, callerCL)方法,这里面会调用initialize()->loadInitialDrivers();方 法(如果使用了class.forName注册驱动,则initialize()在注册时已被调用,这里不会再执行)。
loadInitialDrivers()方法中会首先尝试从System.getproperty中获取设置的jdbc.drivers属性,没设置就不管了,代码如下:
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.
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);
}
}
}
上面代码中DriverService是一个内部类,实现了java.security.PrivilegedAction接口,代码如下:
class DriverService implements java.security.PrivilegedAction {
Iterator ps = null;
public DriverService() {};
public Object run() {
// uncomment the followin line before mustang integration
// Service s = Service.lookup(java.sql.Driver.class);
// ps = s.iterator();
ps = Service.providers(java.sql.Driver.class);
try {
while (ps.hasNext()) {
ps.next();
} // end while
} catch(Throwable t) {
// Do nothing
}
return null;
} //end run
}
上面代码会调用Service.providers(Driver.class);方法,他其实是从String str = "META-INF/services/" + this.service.getName();中加载类,具体看代码:
private static class LazyIterator implements Iterator {
Class service;
ClassLoader loader;
Enumeration configs = null;
Iterator pending = null;
Set returned = new TreeSet();
String nextName = null;
private LazyIterator(Class service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
public boolean hasNext() throws ServiceConfigurationError {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = prefix + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, ": " + x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, (URL)configs.nextElement(), returned);
}
nextName = (String)pending.next();
return true;
}
public Object next() throws ServiceConfigurationError {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
try {
return Class.forName(cn, true, loader).newInstance();
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
} catch (Exception x) {
fail(service,
"Provider " + cn + " could not be instantiated: " + x,
x);
}
return null; /* This cannot happen */
}
public void remove() {
throw new UnsupportedOperationException();
}
}
上面代码调用了return Class.forName(str, true, this.loader).newInstance();装载连接初始化并生产驱动类实例,即自动将驱动注册到DriverManager中的一个列表中。
下面做一个简单测试读取META-INF/services的驱动,代码如下:
Enumeration<URL> list = ClassLoader.getSystemResources("META-INF/services/" + Driver.class.getName());
while (list.hasMoreElements()) {
System.out.println(list.nextElement());
}
控制台输出:
jar:file:/usr/local/glassfish3/jdk7/jre/lib/resources.jar!/META-INF/services/java.sql.Driver
jar:file:/home/alexis/mysql-connector/mysql-connector-java-5.1.22-bin.jar!/META-INF/services/java.sql.Driver
这两个jar文件一个是jdk自带的,另一个是我们自己加到环境变量里的mysql驱动,然后我们再看看这两个java.sql.Driver里的东西,就是驱动类名称了。
下面来看看驱动被自动加载,是如何自动注册的,查看驱动类文件,会发现都有这样一段static代码块,如下:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
它调用了单例类DriverManager.registerDriver(Driver);向驱动管理器注册了驱动,代码如下:
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
if (!initialized) {
initialize();
}
DriverInfo di = new DriverInfo();
di.driver = driver;
di.driverClass = driver.getClass();
di.driverClassName = di.driverClass.getName();
// Not Required -- drivers.addElement(di);
writeDrivers.addElement(di);
println("registerDriver: " + di);
/* update the read copy of drivers vector */
readDrivers = (java.util.Vector) writeDrivers.clone();
}
从上面代码知道,驱动类被赋给值对象,值对象被存入一个同步列表中,再看initialize()后面的代码如下:
synchronized (DriverManager.class){
// use the readcopy of drivers
drivers = readDrivers;
}
// Walk through the loaded drivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
DriverInfo di = (DriverInfo)drivers.elementAt(i);
// If the caller does not have permission to load the driver then
// skip it.
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println(" skipping: " + di);
continue;
}
try {
println(" trying " + di);
Connection result = di.driver.connect(url, info);
if (result != null) {
// Success!
println("getConnection returning " + di);
return (result);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
初始化完成之后,从vector中拿出DriverInfo,然后取出driver,调用connect方法, 这个方法会解析url,如果符合当前驱动的规则,那么就用当前驱动,比如如果传入的是jdbc:mysql://localhost:3306 /jdbcana就可以解析出来是用mysql jdbc 驱动,其中getCallerClass(callerCL, di.driverClassName)代码如下:
private static Class getCallerClass(ClassLoader callerClassLoader,
String driverClassName) {
Class callerC = null;
try {
callerC = Class.forName(driverClassName, true, callerClassLoader);
}
catch (Exception ex) {
callerC = null; // being very careful
}
return callerC;
}
实际上是返回与给定字符串名的类或接口的Class对象,使用给定的类加载器,检查是不是被当前加载器加载的类。
曾经有一个面试题:同时注册多个驱动,问得到的connection是哪一个数据库的,为什么?
看到这里答案已经解开,它是循环取出每个注册的驱动与URL匹配,只返回成功连接的那个。
最后总结一下流程:
1. 调用 getConnection()。
2. DriverManager 通过 SystemProerty jdbc.driver 获取数据库驱动类名
或者
通过ClassLoader.getSystemResources 去CLASSPATH里的类信息里查找 META-INF/services/java.sql.Driver 这个文件里获取数据库驱动名。
3. 通过找的的driver名对他们进行类加载。
4. Driver类在被加载的时候执行static语句块,将自己注册到DriverManager里去。
5. 注册完毕后 DriverManager 调用这些驱动的connect方法,将合适的Connection 返回给客户端。