JDBC中驱动加载的过程分析(下)
本篇主要用几个开源数据库的驱动讲述驱动是如何加载的,以及“可插拔”机制等。
由于文章篇幅有限,本文章并不是专门研究开源源代码!因此我们仅研究那些与数据库驱动加载直接相关的方法。在下面的几个开源软件的驱动中,我们主要关注驱动类的getConnection()方法。
一、几个开源数据库的驱动类
以下第一个是smallsql中驱动类SSDriver的源代码:
package smallsql.database;
import java.sql.*;
import java.util.Properties;
public class SSDriver implements Driver {
static SSDriver drv;
static {
try{
drv = new SSDriver();
java.sql.DriverManager.registerDriver(drv);
}catch(Throwable e){}
}
public Connection connect(String url, Properties info) throws SQLException {
if(!acceptsURL(url)) return null;
……
return new SSConnection( (idx > 0) ? url.substring(idx+1) : null);
}
……
}
从上面红色的部分可以看到:这是一个静态语句块(static block),这意味着该语句是在类构造完成前完成的(关于语句块的加载请阅读《Think in java》)。即调用class.forName(“smallsql.database.SSDriver”)语句时,会首先创建一个SSDriver的实例,并且将其向驱动管理器(DriverManager)注册。这样就完成驱动的注册了。
从上面的蓝色的代码可以看出:驱动的连接方法返回的是一个具体的SSConnection对象。而在前面研究的Driver接口中返回的是Connection接口,这是不茅盾的,SSConnection对象实现了Connection接口。
再下面一个是HSqlD中的驱动类的源代码:
package org.hsqldb;
import java.sql.*;
import java.util.Properties;
import org.hsqldb.jdbc.jdbcConnection;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.persist.HsqlProperties;
public class jdbcDriver implements Driver {
public Connection connect(String url, Properties info) throws SQLException {
return getConnection(url, info);
}
public static Connection getConnection(String url, Properties info) throws SQLException {
HsqlProperties props = DatabaseURL.parseURL(url, true);
if (props == null) {
throw new SQLException(Trace.getMessage(Trace.INVALID_JDBC_ARGUMENT));
} else if (props.isEmpty()) {
return null;
}
props.addProperties(info);
return new jdbcConnection(props);
}
static {
try {
DriverManager.registerDriver(new jdbcDriver());
} catch (Exception e) {}
}
}
蓝色的依然是建立连接的部分,依然返回某个具体的实现java.sql.Connection接口的对象。是设计模式中的哪个工厂啊(一般工厂、抽象工厂、工厂方法还是什么都不是啊)!
红色的同样是一个静态语句块,同样完成该驱动的注册。
两个开源数据库的驱动竟然如此相似,是不是其它的就不一样呢!看下面是mckoi中的驱动类:
package com.mckoi;
public class JDBCDriver extends com.mckoi.database.jdbc.MDriver {
static {
com.mckoi.database.jdbc.MDriver.register();
}
public JDBCDriver() {
super();
// Or we could move driver registering here...
}
}
红色的部分又是完成相同的工作――在类装载完成前向驱动管理器注册驱动,只不过这次是调用MDriver的register方法罢了。那么该驱动建立连接是否也一样呢,当然一样啦!不信你看下面的代码:
package com.mckoi.database.jdbc;
……
public class MDriver implements Driver {
……
private static boolean registered = false;
public synchronized static void register() {
if (registered == false) {
try {
java.sql.DriverManager.registerDriver(new MDriver());
registered = true;
}catch (SQLException e) {
e.printStackTrace();
}
}
}
public Connection connect(String url, Properties info) throws SQLException {
if (!acceptsURL(url)) { return null; }
DatabaseInterface db_interface;
int row_cache_size;
int max_row_cache_size;
……
MConnection connection = new MConnection(url, db_interface, row_cache_size, max_row_cache_size);
……
return connection;
}
}
从以上三个开源数据库驱动的源代码可以看出:在调用Class.forName(“XXXDriver”)时,完成了将具体的驱动程序向JDBC API中驱动管理器的注册,该注册方法在类构造完成前完成,一般使用静态语句块。在调用DriverManager的getConnection方法时,一般先在已注册的驱动中查找可以了解此URL的驱动,然后调用该驱动的connect方法,从而建立连接,返回的连接都是一个实现java.sql.Connection接口的具体类。下面是该过程的时序图。
二、JDBC中驱动加载的时序图
以上是JDBC中驱动加载的时序图。时序图主要有以下7个动作:
1. 客户调用Class.forName(“XXXDriver”)加载驱动。
2. 此时此驱动类首先在其静态语句块中初始化此驱动的实例,
3. 再向驱动管理器注册此驱动。
4. 客户向驱动管理器DriverManager调用getConnection方法,
5. DriverManager调用注册到它上面的能够理解此URL的驱动建立一个连接,
6. 在该驱动中建立一个连接,一般会创建一个对应于数据库提供商的XXXConnection连接对象,
7. 驱动向客户返回此连接对象,不过在客户调用的getConnection方法中返回的为一个java.sql.Connection接口,而具体的驱动返回一个实现java.sql.Connection接口的具体类。
以上就是驱动加载的全过程。由此过程我们可以看出JDBC的其它一些特点。
三、JDBC的架构
在《教你建立简单JDBC程序》一篇中,讲述了一般JDBC的几个步骤。通过本篇的介绍,我将此程序分为以下几部分:
上图中,蓝色的即为本章前面介绍的JDBC驱动加载的细节部分。看看下面的部分:左面的很明显吧!是java.sql包中的接口吧!它是抽象的!右边呢?通过驱动管理器DriverManager得到的是一个实现java.sql.Connection接口的具体类吧!(不知道啊!前面不是讲过了吗!)因此我们可以可以注意到左右分别是抽象的和具体的。(这种抽象和具体的连接是由java的RTTI支持的,不懂可以阅读《Think in java》)。在接下来的结果集的处理rs也是抽象的吧!
因此,在写JDBC程序时,即使我们使用不同数据库提供商的数据库我们只要改变驱动类的地址,和具体连接的URL及其用户名和密码,其它几乎不用任何修改,就可以完成同样的工作!方便吧!
这意味着什么呢?我们其实是在针对抽象接口编程,只要知道接口的调用顺序,以及其中的主要方法,我们就可以迅速学会JDBC编程了!
同时,我们只要对不同数据库提供商的驱动类使用Class.forName(“XXXDriver”)就可以加载驱动,其细节你根本不用关注――JDBC Framework已经全为你搞定了!应用程序对于不同提供商的数据库是无需太多改动的,因而其是“可插拔”的!整个J2EE就是一个高层的API――抽象接口,用户可以使用不同的产品提供商提供的产品,使用统一的API,从而便于程序员学习!
谢谢大家!到此结束!