JDBC中驅動加載的過程分析(上)

 

       本篇從java.sql.Driver接口、java.sql.DriveManager類以及其它開源數據庫的驅動類討論JDBC中驅動加載的全過程以及JDBCFramework如何做到“可插拔”的細節。

       本篇包含了很多部分的內容。如類加載器、本地方法、對象鎖、類鎖、按功能或者狀態分離鎖、安全機制,對這些內容沒有深入討論!詳情可以繼續關注本博客!我在上篇主要關注驅動管理器的初始化、連接的建立、驅動的注冊、驅動的遍列、驅動的取消注冊以及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中經常談到的“依賴倒轉原則(DIPDependence 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中與日志相關的方法有好幾個,主要分為已被deprecatedStream相關的方法,和建議使用的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()方法。以及“可插拔”等概念!