JDBC中驅動加載的過程分析(上)
本篇從java.sql.Driver接口、java.sql.DriveManager類以及其它開源數據庫的驅動類討論JDBC中驅動加載的全過程以及JDBC的Framework如何做到“可插拔”的細節。
本篇包含了很多部分的內容。如類加載器、本地方法、對象鎖、類鎖、按功能或者狀態分離鎖、安全機制,對這些內容沒有深入討論!詳情可以繼續關注本博客!我在上篇主要關注驅動管理器的初始化、連接的建立、驅動的注冊、驅動的遍列、驅動的取消注冊以及DriverManager中的日志操作。
一、Driver接口
//Driver.java
package java.sql;
public interface Driver {
Connection connect(String url, java.util.Properties info) throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;
int getMajorVersion(); //返回驅動的主版本號
int getMinorVersion(); //返回驅動的次版本號
boolean jdbcCompliant(); //是否兼容于JDBC標準
}
以上就是JDBC中的Driver接口,它是任何數據庫提供商的驅動類必須實現的接口,驅動類必須實現該接口中的所有方法!簡單吧!
它之所以是一個接口,就是OO中經常談到的“依賴倒轉原則(DIP-Dependence Inverse Principle)”的具體應用了!在DriverManager類中可以看到:它使用的驅動都是Driver接口,從而依賴與高層,不依賴于實現。這樣可以使用JDBC Framework管理和維護不同JDBC提供商的數據庫驅動。
JDBC Framework中允許加載多個數據庫的驅動!相應地,一般建議數據庫提供商的驅動必須小一點,從而保證在加載多個驅動后不會占用太多的內存。
在Driver接口中共有以上六個方法。其中紅色的兩個相對很重要,它是供DriverManager調用的,其它四個很簡單的方法!下面簡單講述前兩個方法的意義!
Connection connect(String url, java.util.Properties info) throws SQLException方法是數據庫提供商的驅動必須實現的方法,它主要是使用指定的URL和與具體提供商相關的信息建立一個連接。
boolean acceptsURL(String url) throws SQLException方法也是數據庫提供商的驅動必須實現的方法,主要判定某個該驅動是否介紹該URL。(一般在建立連接之前調用。詳情將DriverManager類)
二、DriverManager類
DriverManager類是整個JDBC的起點!利用它可以創建連接,從而完成后續的操作。在JDBC 中DriverManager是一個相對比較復雜的類,因此我們按其只能分為幾類介紹。本篇將DriverManager中的方法分為3類:1.初始化;2.驅動的注冊、查詢、取消注冊;3.建立連接;4.日志相關。
下面就看看它的源代碼吧!
//DriverManager.java 1.45 05/11/17
package java.sql;
import sun.misc.Service;
import java.util.Iterator;
class DriverInfo {
Driver driver;
Class driverClass;
String driverClassName;
public String toString() {
return ("driver[className=" + driverClassName + "," + driver + "]");
}
}
public class DriverManager {
// Prevent the DriverManager class from being instantiated.
private DriverManager(){}
以上是其代碼的前面部分。主要是包的定義、相關文件的導入、類的定義以及一個私有化的構造器――即該類不可以實例化,只可以調用其靜態方法,相當于一個工具類――一個管理驅動的工具類!還有一個就是一個輔助類DriverInfo,它包裝了驅動類Driver,包含驅動類的類和驅動類的名稱。
下面就開始DriverManager類重要方法的介紹吧!
1.初始化
private static java.util.Vector drivers = new java.util.Vector(); //保存多個驅動的聚集
private static boolean initialized = false; //是否初始化的標記,默認當然是否了
// 真正的初始化方法
static void initialize() {
if (initialized) { return; } //已經初始化就返回?。?span style="color: red">初始化了就算了)
initialized = true; //設置此標識符,表示已經完成初始化工作
loadInitialDrivers(); //初始化工作主要是完成所有驅動的加載
println("JDBC DriverManager initialized");
}
//初始化方法中完成加載所有系統提供的驅動的方法
private static void loadInitialDrivers() {
String drivers;
try {
drivers = (String) java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("jdbc.drivers"));
//得到系統屬性"jdbc.drivers"對應的驅動的驅動名(這可是需要許可的哦!)。
} catch (Exception ex) {
drivers = null;
}
Iterator ps = Service.providers(java.sql.Driver.class); //從系統服務中加載驅動
while (ps.hasNext()) { //加載這些驅動,從而實例化它們
ps.next();
}
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);
}
}//end of while
//系統屬性"jdbc.drivers"可能有多個數據庫驅動,這些驅動的名字是以“:”分隔開的,
//上面的過程就是將此以“:”分隔的驅動,依次遍列,然后調用Class.forName依次加載
}
private static Object logSync = new Object(); //對象鎖
//下面是一個輔助方法,用于向日志中寫入信息!
public static void println(String message) {
synchronized (logSync) { //很重要的一致性編程的方法,見下面
if (logWriter != null) { //設置日志才可以進行下面的寫入信息
logWriter.println(message); //向logger中寫入信息
logWriter.flush();
}
}
}
//以上藍色的屬性和方法,是一致性編程(Concurent Programming)中的重要方法。
//首先明確我們在向日志寫入信息的時候,是可以調用其它非寫入日志的方法的,
//只是不同的客戶不能同時調用該寫入方法――一個客戶正在寫入,其它必須等待寫完
//假如我們機械地使用synchronized(this)或synchronized該寫入方法時,必然會導致效率低
//一般地,當類的中多個方法可以分為多個不同組,這些組的方法互相之間不干擾時,
//可以為每個組指定一個自己的鎖,限制同一個方法被多個客戶使用,從而保證該方法的
//一致性,保證無必要的synchronized方法!
//關于一致性編程,請多關注博客中的文章
2.驅動的注冊、查詢、取消注冊
//向DriverManager 注冊指定的驅動。驅動這么注冊請閱讀下篇!
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(); //以上填入注冊驅動的信息
drivers.addElement(di); //將此驅動信息假如驅動聚集中
println("registerDriver: " + di);
}
//
public static synchronized Driver getDriver(String url) throws SQLException {
println("DriverManager.getDriver( "" + url + " ")");
if (!initialized) { initialize(); } //同樣必須先初始化
//本地方法,得到調用此方法的類加載器
ClassLoader callerCL = DriverManager.getCallerClassLoader();
// 遍列所有的驅動信息,返回能理解此URL的驅動
for (int i = 0; i < drivers.size(); i++) { //遍列驅動信息的聚集
DriverInfo di = (DriverInfo)drivers.elementAt(i);
// 調用者在沒有權限加載此驅動時會忽略此驅動
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println(" skipping: " + di);
continue;
}
try {
println(" trying " + di);
if (di.driver.acceptsURL(url)) { //驅動能理解此URL時,返回此驅動
println("getDriver returning " + di);
return (di.driver);
}
} catch (SQLException ex) {
// Drop through and try the next driver.
}
}
println("getDriver: no suitable driver");
throw new SQLException("No suitable driver", "08001");
}
//從DriverManager 中取消注冊某個驅動。Applet僅僅能夠取消注冊從它的類加載器加載的驅動
public static synchronized void deregisterDriver(Driver driver) throws SQLException {
ClassLoader callerCL = DriverManager.getCallerClassLoader();
println("DriverManager.deregisterDriver: " + driver);
int i;
DriverInfo di = null;
for (i = 0; i < drivers.size(); i++) {
di = (DriverInfo)drivers.elementAt(i);
if (di.driver == driver) { break; } //找到了某個驅動則返回,同時返回i值
}
if (i >= drivers.size()) { //全部遍列完,度沒有找到驅動則返回
println(" couldn't find driver to unload");
return;
}
//找到此驅動,但調用者不能加載此驅動,則拋出異常
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
throw new SecurityException();
}
// 在以上所有操作后,可以刪除此驅動了
drivers.removeElementAt(i);
}
//得到當前所有加載的JDBC驅動的枚舉**
public static synchronized java.util.Enumeration getDrivers() {
java.util.Vector result = new java.util.Vector();
if (!initialized) { initialize(); } //該類沒有初始化時,必須完成初始化工作
//詳情請閱讀初始化部分
ClassLoader callerCL = DriverManager.getCallerClassLoader(); //得到當前類的類加載器
for (int i = 0; i < drivers.size(); i++) { //遍列所有的驅動
DriverInfo di = (DriverInfo)drivers.elementAt(i); //得到某個具體的驅動
// 假如調用者沒有許可加載此驅動時,忽略該驅動
if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println(" skipping: " + di);
continue;
}
result.addElement(di.driver); //將可以加載的驅動加入要返回的結果集
}
return (result.elements()); //返回結果集
}
private static native ClassLoader getCallerClassLoader();
//獲得當前調用者的類裝載器的本地方法(關于本地方法JNI請關注本博客后續文章)
// 返回類對象。我們使用DriverManager的本地方法getCallerClassLoader()得到調用者的類加載器
private static Class getCallerClass(ClassLoader callerClassLoader, String driverClassName) {
// callerClassLoader為類加載器,driverClassName為驅動的名稱
Class callerC = null;
try {
callerC = Class.forName(driverClassName, true, callerClassLoader);
//使用指定的類裝載器定位、加載指定的驅動類,
//true代表該驅動類在沒有被初始化時會被初始化,返回此類
}catch (Exception ex) {
callerC = null; // being very careful
}
return callerC;
}
3.建立連接
在JDBC程序中一般使用DriverManager.getConnection方法返回一個連接。該方法有多個變體,它們都使用了具體驅動類的connect方法實現連接。下面是連接的核心方法。
private static Connection getConnection(String url, java.util.Properties info, ClassLoader callerCL)
throws SQLException {
//當類加載器為null時,必須檢查應用程序的類加載器
//其它在rt.jar之外的JDBC驅動類可以從此加載驅動 /*
synchronized(DriverManager.class) { //同步當前DriverManger的類
if(callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); }
//得到當前線程的類加載器(此句的真正含義請關注后續線程相關的文章)
}
if(url == null) { throw new SQLException("The url cannot be null", "08001"); }
println("DriverManager.getConnection( "" + url + " ")");
if (!initialized) { initialize(); } //必須初始化,將默認的驅動加入
// 遍列當前的所有驅動,并試圖建立連接
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
DriverInfo di = (DriverInfo)drivers.elementAt(i);
// 假如調用者沒有許可加載該類,忽略它
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) { //在建立連接后打印連接信息且返回連接
println("getConnection returning " + di);
return (result);
}
} catch (SQLException ex) {
if (reason == null) { reason = ex; } //第一個錯誤哦
}
}
//以上過程要么返回連接,要么拋出異常,當拋出異常會給出異常原因,即給reason賦值
//在所有驅動都不能建立連接后,若有錯誤則打印錯誤且拋出該異常
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
//若根本沒有返回連接也沒有異常,否則打印沒有適當連接,且拋出異常
println("getConnection: no suitable driver");
throw new SQLException("No suitable driver", "08001");
}
//以下三個方法是上面的連接方法的變體,都調用了上面的連接方法
public static Connection getConnection(String url, java.util.Properties info) throws SQLException {
ClassLoader callerCL = DriverManager.getCallerClassLoader();
//沒有類加載器時就是該調用者的類加載器
return (getConnection(url, info, callerCL));
}
public static Connection getConnection(String url, String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
ClassLoader callerCL = DriverManager.getCallerClassLoader();
if (user != null) { info.put("user", user); }
if (password != null) { info.put("password", password); }
return (getConnection(url, info, callerCL));
}
public static Connection getConnection(String url) throws SQLException {
java.util.Properties info = new java.util.Properties();
ClassLoader callerCL = DriverManager.getCallerClassLoader();
return (getConnection(url, info, callerCL));
}
4.日志相關
DriverManager中與日志相關的方法有好幾個,主要分為已被deprecated的Stream相關的方法,和建議使用的Reader、Writer相關的方法。(對應于java IO的字符流和字節流哦!因為寫入日志的信息一般為字符流,所以廢棄了與字節流相關的方法)
//常數。允許設置logging stream
final static SQLPermission SET_LOG_PERMISSION = new SQLPermission("setLog");
private static int loginTimeout = 0;
private static java.io.PrintWriter logWriter = null; //寫入的字符流
private static java.io.PrintStream logStream = null; //寫入的字節流
//設置驅動在試圖連接(log)時等待的最大時間
public static void setLoginTimeout(int seconds) { loginTimeout = seconds; }
public static int getLoginTimeout() { return (loginTimeout); }
public static java.io.PrintWriter getLogWriter() { //得到LogWriter
return logWriter;
}
public static void setLogWriter(java.io.PrintWriter out) { //設置字符流
SecurityManager sec = System.getSecurityManager(); //取得安全管理器
if (sec != null) { sec.checkPermission(SET_LOG_PERMISSION); }
//檢查是否具有日志寫入的權限,有權限則繼續,否則拋出異常!
logStream = null;
logWriter = out;
}
public static void setLogStream(java.io.PrintStream out) { //設置字節流
SecurityManager sec = System.getSecurityManager();
if (sec != null) { sec.checkPermission(SET_LOG_PERMISSION); }
logStream = out;
if ( out != null )
logWriter = new java.io.PrintWriter(out); //將字節流包裝為字符流
else
logWriter = null;
}
public static java.io.PrintStream getLogStream() { //得到字節流
return logStream;
}
}
以上對應于《教你建立簡單JDBC程序的》DriverManager.getConnection()方法。
下篇我們將關注:數據庫提供商如何注冊自己的驅動,即關注Class.forName()方法。以及“可插拔”等概念!