zoukankan      html  css  js  c++  java
  • jdbc驱动的类加载过程

    这段时间跟类加载机制是干上了。

    这一篇来分析一下jdbc工作过程中涉及到的类加载流程,重点是想看看在双亲委派模型不适用的时候,如何解决。

    第一步,加载数据库的驱动

    Class.forName("oracle.jdbc.driver.OracleDriver")
    Class.forName("com.mysql.jdbc.Driver")

    Class.forName 方法会根据类的全路径名称去加载对应的class文件,生成类型,并初始化类型。也就是说static语句块会执行。

    下面来看看 com.mysql.jdbc.Driver 类

     1 public class Driver extends NonRegisteringDriver implements java.sql.Driver {
     2     //
     3     // Register ourselves with the DriverManager
     4     //
     5     static {
     6         try {
     7             java.sql.DriverManager.registerDriver(new Driver());
     8         } catch (SQLException E) {
     9             throw new RuntimeException("Can't register driver!");
    10         }
    11     }
    12 
    13     /**
    14      * Construct a new driver and register it with DriverManager
    15      * 
    16      * @throws SQLException
    17      *             if a database error occurs.
    18      */
    19     public Driver() throws SQLException {
    20         // Required for Class.forName().newInstance()
    21     }
    22 }

    里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。

    第二步,取得数据库连接connection

    Connection conn= DriverManager.getConnection(url, user, password);

    这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!

     1 public static Connection getConnection(String url,
     2         String user, String password) throws SQLException {
     3         java.util.Properties info = new java.util.Properties();
     4 
     5         if (user != null) {
     6             info.put("user", user);
     7         }
     8         if (password != null) {
     9             info.put("password", password);
    10         }
    11 
    12         return (getConnection(url, info, Reflection.getCallerClass()));
    13     }
    Reflection.getCallerClass() 是取得调用类,这个方法是native的。我这里是jdk1.8,以前的版本不是调用的这个方法,如果感兴趣也可以看看。
     1 private static Connection getConnection(
     2         String url, java.util.Properties info, Class<?> caller) throws SQLException {
     3         /*
     4          * When callerCl is null, we should check the application's
     5          * (which is invoking this class indirectly)
     6          * classloader, so that the JDBC driver class outside rt.jar
     7          * can be loaded from here.
     8          */
     9         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    10         synchronized(DriverManager.class) {
    11             // synchronize loading of the correct classloader.
    12             if (callerCL == null) {
    13                 callerCL = Thread.currentThread().getContextClassLoader();
    14             }
    15         }
    16 
    17         if(url == null) {
    18             throw new SQLException("The url cannot be null", "08001");
    19         }
    20 
    21         println("DriverManager.getConnection("" + url + "")");
    22 
    23         // Walk through the loaded registeredDrivers attempting to make a connection.
    24         // Remember the first exception that gets raised so we can reraise it.
    25         SQLException reason = null;
    26 
    27         for(DriverInfo aDriver : registeredDrivers) {
    28             // If the caller does not have permission to load the driver then
    29             // skip it.
    30             if(isDriverAllowed(aDriver.driver, callerCL)) {
    31                 try {
    32                     println("    trying " + aDriver.driver.getClass().getName());
    33                     Connection con = aDriver.driver.connect(url, info);
    34                     if (con != null) {
    35                         // Success!
    36                         println("getConnection returning " + aDriver.driver.getClass().getName());
    37                         return (con);
    38                     }
    39                 } catch (SQLException ex) {
    40                     if (reason == null) {
    41                         reason = ex;
    42                     }
    43                 }
    44 
    45             } else {
    46                 println("    skipping: " + aDriver.getClass().getName());
    47             }
    48 
    49         }
    50 
    51         // if we got here nobody could connect.
    52         if (reason != null)    {
    53             println("getConnection failed: " + reason);
    54             throw reason;
    55         }
    56 
    57         println("getConnection: no suitable driver found for "+ url);
    58         throw new SQLException("No suitable driver found for "+ url, "08001");
    59     }

    这个方法一开始就要得到调用类caller的类加载器callerCL,为的是后面再去加载数据库的driver,做一下验证,

    具体在 isDriverAllowed(aDriver.driver, callerCL) 里面的代码里

    那问题来了,为什么就不能用加载DriverManager的类加载器呢???

    因为DriverManager在rt.jar里面,它的类加载器上启动类加载器。而数据库的driver(com.mysql.jdbc.Driver)是放在classpath里面的,启动类加载器是不能加载的。所以,如果严格按照双亲委派模型,是没办法解决的。而这里的解决办法是:通过调用类的类加载器去加载。而如果调用类的加载器是null,就设置为线程的上下文类加载器:

    Thread.currentThread().getContextClassLoader()

    好的,下面通过Thread类的源码,分析线程的上下文类加载器。

        /* The context ClassLoader for this thread */
        private ClassLoader contextClassLoader;
    
        // 这段if---else代码出自init方法
        if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
        else
                this.contextClassLoader = parent.contextClassLoader;
    
    
        public ClassLoader getContextClassLoader() {
            if (contextClassLoader == null)
                return null;
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                       Reflection.getCallerClass());
            }
            return contextClassLoader;
        }
    
    
        public void setContextClassLoader(ClassLoader cl) {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(new RuntimePermission("setContextClassLoader"));
            }
            contextClassLoader = cl;
        }

    init方法里面代码的逻辑是:把父线程的上下文类加载器给继承过来。这里的父子关系是指谁启动谁的关系,比如在线程A里面启动了线程B,那B线程的父线程就是A。

    既然都是一路继承,那第一个启动的线程(包含main方法的那个线程)里面的contextClassLoader是谁设置的呢???

    这就要看 sun.misc.Launcher 这个类的源码。Launcher是JRE中用于启动程序入口main()的类。

    loader = AppClassLoader.getAppClassLoader(extcl);
    
    Thread.currentThread().setContextClassLoader(loader);

    这里截取的两行代码出自 Launcher 的构造方法。第一行用一个扩展类加载器extcl构造了一个系统类加载器loader,第二行把loader设置为当前线程(包含main方法)的类加载器。所以,我们启动一个线程的时候,如果之前都没有调用 setContextClassLoader 方法明确指定的话,默认的就是系统类加载器。

    到这里,整个加载流程基本上一目了然了。

    现在,再回到之前 DriverManager的getConnection 方法,好像还有一个疑问没有解决。

    for(DriverInfo aDriver : registeredDrivers) {
                // If the caller does not have permission to load the driver then
                // skip it.
                if(isDriverAllowed(aDriver.driver, callerCL)) {
                    try {
                        println("    trying " + aDriver.driver.getClass().getName());
                        Connection con = aDriver.driver.connect(url, info);
                        if (con != null) {
                            // Success!
                            println("getConnection returning " + aDriver.driver.getClass().getName());
                            return (con);
                        }
                    } catch (SQLException ex) {
                        if (reason == null) {
                            reason = ex;
                        }
                    }
    
                } else {
                    println("    skipping: " + aDriver.getClass().getName());
                }
    
            }

    最后返回一个connection,但是在一个循环里面。也就是说,当我们注册了多个数据库驱动,mysql,oracle等;DriverManager都帮我们管理了,它会取出一个符合条件的driver,就不用我们在程序里自己去控制了。

  • 相关阅读:
    nyoj 199 无线网络覆盖
    hdoj 2682 Tree
    nyoj 845 无主之地1
    hdoj 1874 畅通工程续【dijkstra算法or spfa算法】
    hdoj 2544 最短路【dijkstra or spfa】
    hdoj 4548 美素数
    打表法
    hdoj 2098 分拆素数和
    hdoj 1262 寻找素数对
    bzoj1180,2843
  • 原文地址:https://www.cnblogs.com/cz123/p/6867345.html
Copyright © 2011-2022 走看看