以JDBC為例談雙親委派模型的破壞

java本身有一套資源管理服務(wù)JNDI栈源,是放置在rt.jar中,由啟動(dòng)類加載器加載的。以對(duì)數(shù)據(jù)庫(kù)管理JDBC為例,
java給數(shù)據(jù)庫(kù)操作提供了一個(gè)Driver接口:

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();
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

然后提供了一個(gè)DriverManager來(lái)管理這些Driver的具體實(shí)現(xiàn):

public class DriverManager {


    // List of registered JDBC drivers 這里用來(lái)保存所有Driver的具體實(shí)現(xiàn)
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }

    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }
    

}

這里省略了大部分代碼,可以看到我們使用數(shù)據(jù)庫(kù)驅(qū)動(dòng)前必須先要在DriverManager中使用registerDriver()注冊(cè)肤无,然后我們才能正常使用眯搭。

不破壞雙親委派模型的情況(不使用JNDI服務(wù))

我們看下mysql的驅(qū)動(dòng)是如何被加載的:

         // 1.加載數(shù)據(jù)訪問(wèn)驅(qū)動(dòng)
        Class.forName("com.mysql.jdbc.Driver");
        //2.連接到數(shù)據(jù)"庫(kù)"上去
        Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

核心就是這句Class.forName()觸發(fā)了mysql驅(qū)動(dòng)的加載寇蚊,我們看下mysql對(duì)Driver接口的實(shí)現(xiàn):

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

可以看到梳玫,Class.forName()其實(shí)觸發(fā)了靜態(tài)代碼塊盼忌,然后向DriverManager中注冊(cè)了一個(gè)mysql的Driver實(shí)現(xiàn)。
這個(gè)時(shí)候跨嘉,我們通過(guò)DriverManager去獲取connection的時(shí)候只要遍歷當(dāng)前所有Driver實(shí)現(xiàn)川慌,然后選擇一個(gè)建立連接就可以了。

破壞雙親委派模型的情況

在JDBC4.0以后,開(kāi)始支持使用spi的方式來(lái)注冊(cè)這個(gè)Driver梦重,具體做法就是在mysql的jar包中的META-INF/services/java.sql.Driver 文件中指明當(dāng)前使用的Driver是哪個(gè)兑燥,然后使用的時(shí)候就直接這樣就可以了:

 Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?characterEncoding=GBK", "root", "");

可以看到這里直接獲取連接,省去了上面的Class.forName()注冊(cè)過(guò)程琴拧。
現(xiàn)在降瞳,我們分析下看使用了這種spi服務(wù)的模式原本的過(guò)程是怎樣的:

  • 第一,從META-INF/services/java.sql.Driver文件中獲取具體的實(shí)現(xiàn)類名“com.mysql.jdbc.Driver”
  • 第二蚓胸,加載這個(gè)類挣饥,這里肯定只能用class.forName("com.mysql.jdbc.Driver")來(lái)加載

好了,問(wèn)題來(lái)了沛膳,Class.forName()加載用的是調(diào)用者的Classloader扔枫,這個(gè)調(diào)用者DriverManager是在rt.jar中的,ClassLoader是啟動(dòng)類加載器于置,而com.mysql.jdbc.Driver肯定不在<JAVA_HOME>/lib下茧吊,所以肯定是無(wú)法加載mysql中的這個(gè)類的。這就是雙親委派模型的局限性了八毯,父級(jí)加載器無(wú)法加載子級(jí)類加載器路徑中的類搓侄。

那么,這個(gè)問(wèn)題如何解決呢话速?按照目前情況來(lái)分析讶踪,這個(gè)mysql的drvier只有應(yīng)用類加載器能加載,那么我們只要在啟動(dòng)類加載器中有方法獲取應(yīng)用程序類加載器泊交,然后通過(guò)它去加載就可以了乳讥。這就是所謂的線程上下文加載器。
線程上下文類加載器可以通過(guò)Thread.setContextClassLoaser()方法設(shè)置廓俭,如果不特殊設(shè)置會(huì)從父類繼承云石,一般默認(rèn)使用的是應(yīng)用程序類加載器

很明顯,線程上下文類加載器讓父級(jí)類加載器能通過(guò)調(diào)用子級(jí)類加載器來(lái)加載類研乒,這打破了雙親委派模型的原則

現(xiàn)在我們看下DriverManager是如何使用線程上下文類加載器去加載第三方j(luò)ar包中的Driver類的汹忠。

public class DriverManager {
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    private static void loadInitialDrivers() {
        //省略代碼
        //這里就是查找各個(gè)sql廠商在自己的jar包中通過(guò)spi注冊(cè)的驅(qū)動(dòng)
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        try{
             while(driversIterator.hasNext()) {
                driversIterator.next();
             }
        } catch(Throwable t) {
                // Do nothing
        }

        //省略代碼
    }
}

使用時(shí),我們直接調(diào)用DriverManager.getConn()方法自然會(huì)觸發(fā)靜態(tài)代碼塊的執(zhí)行雹熬,開(kāi)始加載驅(qū)動(dòng)
然后我們看下ServiceLoader.load()的具體實(shí)現(xiàn):

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }

可以看到核心就是拿到線程上下文類加載器宽菜,然后構(gòu)造了一個(gè)ServiceLoader,后續(xù)的具體查找過(guò)程,我們不再深入分析竿报,這里只要知道這個(gè)ServiceLoader已經(jīng)拿到了線程上下文類加載器即可铅乡。
接下來(lái),DriverManager的loadInitialDrivers()方法中有一句driversIterator.next();,它的具體實(shí)現(xiàn)如下:

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                //此處的cn就是產(chǎn)商在META-INF/services/java.sql.Driver文件中注冊(cè)的Driver具體實(shí)現(xiàn)類的名稱
               //此處的loader就是之前構(gòu)造ServiceLoader時(shí)傳進(jìn)去的線程上下文類加載器
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
         //省略部分代碼
        }

現(xiàn)在烈菌,我們成功的做到了通過(guò)線程上下文類加載器拿到了應(yīng)用程序類加載器(或者自定義的然后塞到線程上下文中的)阵幸,同時(shí)我們也查找到了廠商在子級(jí)的jar包中注冊(cè)的驅(qū)動(dòng)具體實(shí)現(xiàn)類名花履,這樣我們就可以成功的在rt.jar包中的DriverManager中成功的加載了放在第三方應(yīng)用程序包中的類了。

總結(jié)

這個(gè)時(shí)候我們?cè)倏聪抡麄€(gè)mysql的驅(qū)動(dòng)加載過(guò)程:

  • 第一挚赊,獲取線程上下文類加載器臭挽,從而也就獲得了應(yīng)用程序類加載器(也可能是自定義的類加載器)
  • 第二,從META-INF/services/java.sql.Driver文件中獲取具體的實(shí)現(xiàn)類名“com.mysql.jdbc.Driver”
  • 第三咬腕,通過(guò)線程上下文類加載器去加載這個(gè)Driver類,從而避開(kāi)了雙親委派模型的弊端

很明顯葬荷,mysql驅(qū)動(dòng)采用的這種spi服務(wù)確確實(shí)實(shí)是破壞了雙親委派模型的涨共,畢竟做到了父級(jí)類加載器加載了子級(jí)路徑中的類。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宠漩,一起剝皮案震驚了整個(gè)濱河市举反,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扒吁,老刑警劉巖火鼻,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雕崩,居然都是意外死亡魁索,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門盼铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)粗蔚,“玉大人,你說(shuō)我怎么就攤上這事饶火∨艨兀” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵肤寝,是天一觀的道長(zhǎng)当辐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鲤看,這世上最難降的妖魔是什么缘揪? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮刨摩,結(jié)果婚禮上寺晌,老公的妹妹穿的比我還像新娘。我一直安慰自己澡刹,他們只是感情好呻征,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著罢浇,像睡著了一般陆赋。 火紅的嫁衣襯著肌膚如雪沐祷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天攒岛,我揣著相機(jī)與錄音赖临,去河邊找鬼。 笑死灾锯,一個(gè)胖子當(dāng)著我的面吹牛兢榨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播顺饮,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吵聪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兼雄?” 一聲冷哼從身側(cè)響起吟逝,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赦肋,沒(méi)想到半個(gè)月后块攒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佃乘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年囱井,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恕稠。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琅绅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹅巍,到底是詐尸還是另有隱情千扶,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布骆捧,位于F島的核電站澎羞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏敛苇。R本人自食惡果不足惜妆绞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枫攀。 院中可真熱鬧括饶,春花似錦、人聲如沸来涨。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹦掐。三九已至技羔,卻和暖如春僵闯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藤滥。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工鳖粟, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拙绊。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓向图,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親标沪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子张漂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容