Classloader、插件化開發(fā)(結(jié)合Presto)

Classloader

JVM加載class文件到內(nèi)存有兩種方式:

  • 隱式加載:不通過(guò)在代碼里調(diào)用ClassLoader來(lái)加載需要的類橡淆,而是通過(guò)JVM來(lái)自動(dòng)加載需要的類到內(nèi)存辣吃,例如:當(dāng)類中繼承或者引用某個(gè)類時(shí),JVM在解析當(dāng)前這個(gè)類不在內(nèi)存中時(shí)仰猖,就會(huì)自動(dòng)將這些類加載到內(nèi)存中捏肢。
  • 顯式加載:在代碼中通過(guò)ClassLoader類來(lái)加載一個(gè)類奈籽,例如調(diào)用this.getClass.getClassLoader().loadClass()或者Class.forName()

ClassLoader工作機(jī)制

注意:

程序在啟動(dòng)的時(shí)候,并不會(huì)一次性加載程序所要用的所有class文件鸵赫,而是根據(jù)程序的需要衣屏,通過(guò)Java的類加載機(jī)制來(lái)動(dòng)態(tài)加載某個(gè)class文件到內(nèi)存中。
ClassLoader工作機(jī)制

雙親委派模型

雙親委派模式是在Java 1.2后引入的辩棒,其工作原理的是:

如果一個(gè)類加載器收到了類加載請(qǐng)求狼忱,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給父類的加載器去執(zhí)行一睁,如果父類加載器還存在其父類加載器钻弄,則進(jìn)一步向上委托,依次遞歸者吁,請(qǐng)求最終將到達(dá)頂層的啟動(dòng)類加載器窘俺,如果父類加載器可以完成類加載任務(wù),就成功返回复凳,倘若父類加載器無(wú)法完成此加載任務(wù)瘤泪,子加載器才會(huì)嘗試自己去加載,這就是雙親委派模式育八,即每個(gè)兒子都很懶对途,每次有活就丟給父親去干,直到父親說(shuō)這件事我也干不了時(shí)髓棋,兒子自己想辦法去完成

雙親委派模式優(yōu)勢(shì)

  • 避免類的重復(fù)加載
  • 安全因素实檀,java核心api中定義類型不會(huì)被隨意替換

深入理解Java類加載器

用類加載器顯式加載類例如java.lang.Integer,類加載器只會(huì)返回給已加載過(guò)的按声;如果自定義java.lang.Integer并加載之膳犹,會(huì)報(bào)錯(cuò)。
https://blog.csdn.net/Mint6/article/details/80864788?from=singlemessage

具體解析

一般來(lái)說(shuō)儒喊,例如程序hello.jar執(zhí)行到:

Demo demo = new Demo();

會(huì)按照雙親委派模型進(jìn)行加載類Demo镣奋。如果Demohello.jar內(nèi),AppClassLoader就將其加載完成怀愧;但是如果例如SPI這種侨颈,既不在應(yīng)用hello.jar內(nèi)又不在系統(tǒng)類路徑內(nèi),那么就要拋棄雙親委派模型芯义,獲取線程上下文類加載器加載(線程上下文類加載器默認(rèn)是AppClassLoader哈垢,此時(shí)的線程上下文類加載器肯定是自定義的類加載器)。

在DriverManager類初始化時(shí)執(zhí)行了loadInitialDrivers()方法,在該方法中通過(guò)ServiceLoader.load(Driver.class);去加載外部實(shí)現(xiàn)的驅(qū)動(dòng)類扛拨,ServiceLoader類會(huì)去讀取mysql的jdbc.jar下META-INF文件的內(nèi)容

這樣ServiceLoader會(huì)幫助我們處理一切耘分,并最終通過(guò)load()方法加載,看看load()方法實(shí)現(xiàn)就知道最終是通過(guò)線程上下文類加載器加載

public static <S> ServiceLoader<S> load(Class<S> service) {
     //通過(guò)線程上下文類加載器加載
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return ServiceLoader.load(service, cl);
}

自定義一個(gè)破壞雙親委派模型的類加載器的方法:

  • 父加載器parent設(shè)置為null
  • 重寫loadClass()方法直接調(diào)用findClass
    (可以參考ClassLoader代碼)

深入理解Java類加載器

加載指定路徑的class或jar

這里介紹2種加載方式:

  • URLClassLoader直接加載
  • ServiceLoader加載

URLClassLoader直接加載

例如要加載類:

package com;

public class Demo {
    public Demo() {
        System.out.println("\n" + this.getClass().getClassLoader().toString());
    }

}

將其編譯為class文件,存放在路徑/Users/root/Projects/idea/my/com求泰。

注意央渣!

  • 根目錄是/Users/root/Projects/idea/my/com是表示包路徑渴频。
  • 該類里面如果引用根目錄以外的類芽丹,必須在runtime中能夠獲取到

這時(shí)要加載它:

@Test
public void test() throws Exception {
    // 使用根路徑
    URL url = new URL("file:/Users/root/Projects/idea/my/");
    ClassLoader newCL = new URLClassLoader(new URL[]{url}, Thread.currentThread().getContextClassLoader());
    
    // 加載。注意要使用全限定名
    Class clazz = Class.forName("com.Demo", false, newCL);
    // 或者
    clazz = newCL.loadClass("com.Demo");
}
    

ServiceLoader加載

對(duì)于SPI這種卜朗,就需要用到ServiceLoader加載拔第。可以參考地址:https://github.com/byamao1/try-plugin

需要注意:

  • 放入U(xiǎn)RLClassLoader的URL场钉,目標(biāo)是jar必須是到j(luò)ar文件路徑蚊俺,目標(biāo)是class可以是class的根文件夾路徑
  • 自定義類加載器或自己new的URLClassLoader,要在重寫的方法loadClass中先判斷要加載的類是否為非本加載器加載的類(如spi中的類)逛万,如果是則用其他類加載器(例如spi加載器)加載泳猬,否則才由自己加載
  • 在idea中resources文件夾下不要直接新建META-INFO.services文件夾,而是要新建文件夾META-INFO后再在其下新建文件夾services(雖然這樣建idea的顯示就是META-INFO.services泣港,但絕不能按照前面的做暂殖,那樣只是1個(gè)名字叫META-INFO.services的文件夾)价匠。
  • 插件類例如Demo必須有一個(gè)無(wú)參構(gòu)造方法当纱,否則ServiceLoader無(wú)法實(shí)例化插件類

知識(shí)點(diǎn)

  • 放入U(xiǎn)RLClassLoader的URL的用途就是讓該類加載器能加載其應(yīng)該擁有的jar或class
  • URLClassLoader符合雙親委派模型
  • 從日志中可以看出:Demo中的IDemo是由AppClassLoader加載的;Demo踩窖、OtherClass坡氯、Internal是由插件類加載器加載。

插件化

插件化的一個(gè)重要目標(biāo)就是利用類加載器實(shí)現(xiàn)類隔離(比如不同廠商版本的依賴包)洋腮,其原理在于在類中(例如Demo)隱式類加載器就是Demo的類加載器(一般為插件類加載器)箫柳,對(duì)于插件中出現(xiàn)的插件外的類(例如SPI接口類)則不加載。

這里分析Presto的connector插件架構(gòu)啥供。

Presto的自定義類加載器PluginClassLoader繼承URLClassLoader類并重寫了loadClass悯恍,其類加載邏輯為:

  • 如果類已加載了,就返回它

  • 如果是個(gè)SPI接口類伙狐,則委托給spiClassLoader(就是PluginManager的類加載器)加載

  • 否則交給父方法super.loadClass加載涮毫。這里是真正加載插件類的地方,會(huì)到該加載器的成員URLClassPath中找該類贷屎。要注意的是罢防,PluginManager.parent為空,實(shí)際上就是不會(huì)委托父加載器加載唉侄,而是只由自己加載(實(shí)際上打破了雙親委派模型)咒吐。插件類的加載過(guò)程是:

    PluginClassLoader載入插件類過(guò)程


注意:

更改當(dāng)前線程的ContextClassLoader,只是為了應(yīng)對(duì)擴(kuò)展程序中可能出現(xiàn)的如下代碼:

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
classLoader.loadClass(...);

Java 自定義 ClassLoader 實(shí)現(xiàn)隔離運(yùn)行不同版本jar包的方式


從上面我們得知,如果采取ServiceLoader的SPI方案恬叹,應(yīng)該在resources/META-INF/services中存放實(shí)現(xiàn)類的全限定名候生。有意思的是Presto的插件基本都沒(méi)有這個(gè)聲明文件,但是編譯打包后插件模塊的target/classes中卻能找到绽昼。如果觀察插件的pom.xml文件陶舞,就會(huì)發(fā)現(xiàn)<packaging>presto-plugin</packaging>。其實(shí)在根pom.xml中使用了presto自己的打包插件presto-maven-plugin绪励,將該maven插件打開看就能發(fā)現(xiàn)ServiceDescriptorGenerator中會(huì)在打包時(shí)自動(dòng)生成了聲明文件肿孵。

SOFA-Ark

SOFA-Ark是螞蟻金服開源的一款基于Java實(shí)現(xiàn)的輕量級(jí)類隔離加載容器。
具體可以參考博客:sofa-ark類隔離技術(shù)分析調(diào)研

站在插件的角度看待疏魏,我覺得:

SOFA-Ark = SPI接口聲明 + 插件間可依賴

Ref

你應(yīng)該知道的Java Classloader - 知乎

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末停做,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子大莫,更是在濱河造成了極大的恐慌蛉腌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件只厘,死亡現(xiàn)場(chǎng)離奇詭異烙丛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)羔味,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門河咽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赋元,你說(shuō)我怎么就攤上這事忘蟹。” “怎么了搁凸?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵媚值,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我护糖,道長(zhǎng)褥芒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任嫡良,我火速辦了婚禮锰扶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘皆刺。我一直安慰自己少辣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布羡蛾。 她就那樣靜靜地躺著漓帅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忙干,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天器予,我揣著相機(jī)與錄音,去河邊找鬼捐迫。 笑死乾翔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的施戴。 我是一名探鬼主播反浓,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼赞哗!你這毒婦竟也來(lái)了雷则?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肪笋,失蹤者是張志新(化名)和其女友劉穎月劈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體藤乙,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猜揪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坛梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片而姐。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖罚勾,靈堂內(nèi)的尸體忽然破棺而出毅人,到底是詐尸還是另有隱情,我是刑警寧澤尖殃,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站划煮,受9級(jí)特大地震影響送丰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜弛秋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一器躏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蟹略,春花似錦登失、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春馅巷,著一層夾襖步出監(jiān)牢的瞬間膛虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工钓猬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稍刀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓敞曹,卻偏偏與公主長(zhǎng)得像账月,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子澳迫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

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