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,就不用我們在程序里自己去控制了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM