Java虛擬機--線程上下文類加載器

線程上下文類加載器

通過名字可知,線程上下文類加載,就是當前線程所擁有的類加載器财边,可通過Thread.currentThread()獲取當前線程。

線程上下文類加載器(Thread Context ClassLoader)可以通過java.lang.Thread類的setContextClassLoader()方法設置点骑,創(chuàng)建線程時候未指定的話酣难,則默認從父線程中繼承。

那父線程中也沒指定呢黑滴?那么會默認為應用程序的類加載器憨募。例如:main方法的線程上下文類加載器就是sun.misc.Launcher$AppClassLoader。

前兩篇文章中袁辈,我們講解了類加載器的雙親委派模型菜谣,該模型的實現(xiàn)是通過類加載器中的parent屬性(父加載器)來完成的,默認統(tǒng)一交給最上層類加載器去嘗試加載。

那尾膊,這個線程上下文類加載器又是干啥的甘磨?

在介紹線程上下文類加載前,我們先了解下Java的SPI機制眯停。

Java--SPI機制

線程上下文類加載實現(xiàn)

public class JVMTest6 {

   public static void main(String[] agrs) throws ClassNotFoundException {
       ClassLoader loader = JVMTest6.class.getClassLoader();
       System.out.println(loader); //默認是應用類加載器

       //此時獲得上下文類加載器:
       ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
       System.out.println(loader2);//默認也是應用類加載器

       //設置為自定義類加載器:
       Thread.currentThread().setContextClassLoader(
               new ClassLoaderTest("d:/"));
       System.out.println(Thread.currentThread().getContextClassLoader());

       //使用自定義類加載器加載:
       Class c = Thread.currentThread().getContextClassLoader().loadClass("HelloWorld");
       System.out.println(c.getClassLoader());//線程上下文類加載器

       ClassLoader loader3 = String.class.getClassLoader();
       System.out.println(loader3);//啟動類加載器 = null
   }
}

測試結果:

sun.misc.Launcher$AppClassLoader@41dee0d7
sun.misc.Launcher$AppClassLoader@41dee0d7
ClassLoaderTest@516a4aef
ClassLoaderTest@516a4aef
null

JDBC加載案例分析

介紹完了spi济舆,下來我們來舉幾個例子進一步說明逆向類加載。

舉個簡單的例子莺债,mysql是如何獲取數(shù)據(jù)庫連接:

public class JVMTest7 {

   public static void main(String[] agrs) {
       try {
           // 注冊驅動類
           Class.forName("com.mysql.jdbc.Driver");
           String url = "jdbc:mysql://localhost:3306/testdb";
           //通過java庫獲取數(shù)據(jù)庫連接
           Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (SQLException e) {
           e.printStackTrace();
       }
   }
}

以上就是mysql注冊驅動及獲取connection的過程滋觉。在該流程中,java通過線程上線文類加載器實現(xiàn)了逆向類加載齐邦。

(1)通過系統(tǒng)類加載器椎侠,加載Driver類---Class.forName("com.mysql.jdbc.Driver");底層具體實現(xiàn):registerDriver()將driver實例注冊到java.sql.DriverManager類中措拇,其實就是將com.mysql.jdbc.Driver添加到java.sql.DriverManager類的靜態(tài)變量CopyOnWriteArrayList集合中我纪。

com.mysql.jdbc.Driver包中:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

(2)通過java.sql.DriverManager注冊數(shù)據(jù)庫驅動。首先丐吓,來看下DriverManager的靜態(tài)方法浅悉。需要明確的是java.sql.DriverManager位于rt.jar包目錄下,該目錄下的所有類均由Bootstrap啟動類加載器進行加載券犁。

java.sql.DriverManager包中:

static {
    //初始化Driver驅動實現(xiàn)類:
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    //通過spi來加載jdbc驅動實現(xiàn)類:
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            //通過SPI方式术健,讀取META-INF/services下文件中的類:
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {}
            return null;
        }
    });
    println("DriverManager.initialize: jdbc.drivers = " + drivers);
    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

(3)spi具體實現(xiàn):

在下面代碼中,通過SPI方式來完成java.sql.Driver接口實現(xiàn)類的類加載操作粘衬。

java.sql.DriverManager包中:

AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Void run() {
        //通過SPI方式荞估,讀取META-INF/services下文件中的類名: 
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator driversIterator = loadedDrivers.iterator();
        try{
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {}
        return null;
    }
});

獲取到ServiceLoader對象后,進行遍歷操作稚新,遍歷出所有META-INF/services文件夾下的實現(xiàn)類名稱勘伺,之后再進行Class.forName("")類加載操作。類加載操作在driversIterator.next()中完成褂删。

java.util.ServiceLoader包中:

public static <S> ServiceLoader<S> load(Class<S> service) {
    //獲取線程上下文類加載器:
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //生成ServiceLoader對象:
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}
    
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = svc;
    loader = cl;
    reload();
}

在獲取ServiceLoader對象時飞醉,獲取了此時線程上下文中的類加載器,將此類加載賦值給ServiceLoader類中的loader成員變量笤妙。在后續(xù)類加載過程中冒掌,都是使用的此類加載來完成。這一步的操作蹲盘,直接打破了雙親委派模型股毫,實現(xiàn)了逆向類加載。

try{
    while(driversIterator.hasNext()) {
        driversIterator.next();
    }
} catch(Throwable t) {}

通過debug發(fā)現(xiàn)召衔,driversIterator.next()方法內部會調用Class c = Class.forName(cn, false, loader)方法進行類加載操作铃诬。而此時傳遞的loader就是之前獲取的線程上下文類加載器,傳遞的cn就是META-INF/services文件中的具體實現(xiàn)類。

由于筆者是通過本地的test進行測試趣席,所以上文中涉及到的類加載器都是AppClassLoader系統(tǒng)類加載器兵志。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市宣肚,隨后出現(xiàn)的幾起案子想罕,更是在濱河造成了極大的恐慌,老刑警劉巖霉涨,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件按价,死亡現(xiàn)場離奇詭異,居然都是意外死亡笙瑟,警方通過查閱死者的電腦和手機楼镐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往枷,“玉大人框产,你說我怎么就攤上這事〈斫啵” “怎么了秉宿?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長墓臭。 經常有香客問我蘸鲸,道長,這世上最難降的妖魔是什么窿锉? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮膝舅,結果婚禮上嗡载,老公的妹妹穿的比我還像新娘。我一直安慰自己仍稀,他們只是感情好洼滚,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著技潘,像睡著了一般遥巴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上享幽,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天铲掐,我揣著相機與錄音,去河邊找鬼值桩。 笑死摆霉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播携栋,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼搭盾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了婉支?” 一聲冷哼從身側響起鸯隅,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎向挖,沒想到半個月后蝌以,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡户誓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年饼灿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帝美。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡碍彭,死狀恐怖,靈堂內的尸體忽然破棺而出悼潭,到底是詐尸還是另有隱情庇忌,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布舰褪,位于F島的核電站皆疹,受9級特大地震影響,放射性物質發(fā)生泄漏占拍。R本人自食惡果不足惜略就,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晃酒。 院中可真熱鬧表牢,春花似錦、人聲如沸贝次。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛔翅。三九已至敲茄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間山析,已是汗流浹背堰燎。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留盖腿,地道東北人爽待。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓损同,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鸟款。 傳聞我的和親對象是個殘疾皇子膏燃,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容

  • 這一冬 寒風凜冽 我還沒等到第一場雪 我睜大雙眼 也只是滿目蕭索 耳朵里灌進滿滿的冰碴 刺破耳膜 我還能聽到什么 ...
    月暗小白閱讀 195評論 0 3
  • Alan大學畢業(yè)后组哩,并沒有參加工作,也沒有考研的打算处渣,她安靜的呆在出租屋里伶贰,默默地看著申論這些枯燥而乏味的資料。 ...
    魯小魚閱讀 437評論 0 4
  • 我在上高中時寫過一篇作文罐栈。這讓我被叫去辦公室被旁敲側擊的試探戀情黍衙。 他不帥不丑,長的白不高荠诬,跟普通的男生沒有區(qū)別琅翻,...
    Lain嵐音閱讀 250評論 0 1
  • 引子 家常話,小心說 家常話柑贞,小心說方椎! 你有沒有見過人臉紅? 如果臉紅的是個孩子钧嘶,你一定覺得“天真可愛”棠众。 如果臉...
    mean_370b閱讀 389評論 0 0