JDBC分析驱动加载全过程

本文深入解析JDBC框架的Driver接口和DriverManager类,探讨热插拔特性、驱动加载顺序及连接建立过程。通过实例展示了如何注册数据库驱动并建立连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  首先介绍一下,JDBC框架做到了热插拔。

一.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 标准

  这个就是java的Driver接口,非常简单,任何一个数据库驱动提供商都需要实现该接口,而DriverManager等类中使用Driver时都是直接使用Driver接口,这样就做到了实现与逻辑相分离。也就是OO中提到的依赖反转,实现与逻辑可以由不同的专业人员去实现。

JDBC Framework 中允许加载多个数据库的驱动!相应地,一般建议数据库提供商的驱动必须小一点,从而保证在加载多个驱动后不会占用太多的内存。

二.DriverManager类

  DriverManager可以建立数据库连接,我们把它按功能分为3部:1. 初始化; 2. 驱动的注册、查询、取消注册; 3. 建立连接; 4. 日志相关。

  先看其中代码,有一个类

// DriverInfo is a package-private support class.
class DriverInfo {
    Driver         driver;
    Class          driverClass;
    String         driverClassName;

    public String toString() {
    return ("driver[className=" + driverClassName + "," + driver + "]");
    }
}

  他包含了Driver的相关信息,是一个辅助类。

 

  再看DriverManager的构造

  /* Prevent the DriverManager class from being instantiated. */
    private DriverManager(){}

  是一个私有的构造,说明它不能被实例,只能用静态方法,是一个工具类,创建连接的工具类。

 

1.初始化

  private static boolean initialized = false;                                              // 是否初始化的标记,默认当然是否了

  static void initialize() {
        if (initialized) {
            return;                                                     //初始化过就直接return
        }
        initialized = true;                                         //没初始化就改成已初始化标记
        loadInitialDrivers();                                     //加载驱动集
        println("JDBC DriverManager initialized");
    }

 

private static boolean initialized = false;                                              // 是否初始化的标记,默认当然是否了

 

// 真正的初始化方法

    static void initialize() {

        if (initialized) {    return;     }                                   // 已经初始化就返回!(初始化了就算了)

        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 依次加载

}

2.驱动的注册,查询,取消注册

   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();

    }

 

    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.几个开源数据库的驱动类

 

以下第一个是 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 接口。其实其他数据库驱动也是一样,首先用一个static在类加载时注册驱动,然后实现Driver的connect方法,方法中实际返回一个实现Connection的子类对象,这个子类也是由我们重新实现的。

 

5.驱动加载顺序

 

       以上是 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 的其它一些特点

 

 

6.JDBC调用架构

 

 

红色的是抽象,蓝色的抽象方法获得实际的实现类,这样就只需要知道调用顺序,任何jdbc都可以用了,这种抽象和具体的连接是由 javaRTTI 支持的,不懂可以阅读《 Think in java 》。在接下来的结果集的处理 rs 也是抽象的吧!

       因此,在写 JDBC 程序时,即使我们使用不同数据库提供商的数据库我们只要改变驱动类的地址,和具体连接的 URL 及其用户名和密码,其它几乎不用任何修改,就可以完成同样的工作!

 

就总结这么多拉 ,下1篇我会把自己学习总结出来的JDBC类加载底层的原理写出来,就是CallerClassLoader那块。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值