聊聊JDBC是如何破壞雙親委派模型的

資源來源《深入理解Java虛擬機(jī)》

雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前。由于雙親委派模型是在JDK1.2之后才被引入的,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時(shí)候就已經(jīng)存在,面對已經(jīng)存在 的用戶自定義類加載器的實(shí)現(xiàn)代碼,Java設(shè)計(jì)者引入雙親委派模型時(shí)不得不做出一些妥協(xié)。為了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一個(gè)新的proceted方法findClass(),在此之前,用戶去繼承java.lang.ClassLoader的唯一目的就是重寫loadClass()方法,因?yàn)樘摂M在進(jìn)行類加載的時(shí)候會調(diào)用加載器的私有方法loadClassInternal(),而這個(gè)方法的唯一邏輯就是去調(diào)用自己的loadClass()。JDK1.2之后已不再提倡用戶再去覆蓋loadClass()方法,應(yīng)當(dāng)把自己的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯里,如果父類加載器加載失敗,則會調(diào)用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是符合雙親委派模型的。
雙親委派模型的第二次“被破壞”是這個(gè)模型自身的缺陷所導(dǎo)致的,雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以被稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API。但是,如果基礎(chǔ)類又要調(diào)用用戶的代碼,那該怎么辦呢。
這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù),它的代碼由啟動類加載器去加載(在JDK1.3時(shí)放進(jìn)rt.jar),但JNDI的目的就是對資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啟動類加載器不可能“認(rèn)識”之些代碼,該怎么辦?
為了解決這個(gè)困境,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文件類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會從父線程中繼承一個(gè);如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過,那么這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器。了有線程上下文類加載器,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,已經(jīng)違背了雙親委派模型,但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。

第一種比較簡單,這里就不說啦。

關(guān)于第二種,看看JDBC中是怎么實(shí)現(xiàn)的吧

首先,理解一下為什么JDBC需要破壞雙親委派模式,原因是原生的JDBC中Driver驅(qū)動本身只是一個(gè)接口,并沒有具體的實(shí)現(xiàn),具體的實(shí)現(xiàn)是由不同數(shù)據(jù)庫類型去實(shí)現(xiàn)的。例如,MySQL的mysql-connector-.jar中的Driver類具體實(shí)現(xiàn)的。 原生的JDBC中的類是放在rt.jar包的,是由啟動類加載器進(jìn)行類加載的,在JDBC中的Driver類中需要動態(tài)去加載不同數(shù)據(jù)庫類型的Driver類,而mysql-connector-.jar中的Driver類是用戶自己寫的代碼,那啟動類加載器肯定是不能進(jìn)行加載的,既然是自己編寫的代碼,那就需要由應(yīng)用程序啟動類去進(jìn)行類加載。于是乎,這個(gè)時(shí)候就引入線程上下文件類加載器(Thread Context ClassLoader)。有了這個(gè)東西之后,程序就可以把原本需要由啟動類加載器進(jìn)行加載的類,由應(yīng)用程序類加載器去進(jìn)行加載了。下面看看JDBC中是怎么去應(yīng)用的呢

//callerCL為空的時(shí)候,其實(shí)說明這個(gè)ClassLoader是啟動類加載器,但是這個(gè)啟動類加載并不能識別rt.jar之外的類,這個(gè)時(shí)候就把callerCL賦值為Thread.currentThread().getContextClassLoader();也就是應(yīng)用程序啟動類

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        //callerCL為空的時(shí)候,其實(shí)說明這個(gè)ClassLoader是啟動類加載器,但是這個(gè)啟動類加載并不能識別rt.jar之外的類,這個(gè)時(shí)候就把callerCL賦值為Thread.currentThread().getContextClassLoader();也就是應(yīng)用程序啟動類
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            //繼續(xù)看這里 
            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());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

    private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                //這一步會對類進(jìn)行初始化的動作,而初始化之前自然也要進(jìn)行的類的加載工作
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

希望我的理解能給你帶來幫助,如有錯誤的地方請指正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容