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,從而便于程序員學習!
謝謝大家!到此結束!