一種解決jar包沖突的組件實現(xiàn)

對于java開發(fā)程序員來說悬嗓,jar包沖突是個讓人很頭痛的問題,而osgi可以解決這個問題砂沛,但是使用成本比較高烫扼,必須要按照osgi那一套結(jié)構(gòu)來才能使用,在現(xiàn)有項目代碼基礎(chǔ)上重構(gòu)的成本比較高碍庵,因此我通過學習osgi那一套實現(xiàn)映企,設(shè)計了一款使用不依賴osgi且使用門檻比較低的解決jar包沖突的組件,但沒有在生產(chǎn)環(huán)境驗證過静浴,只是用來學習而已堰氓,代碼見https://github.com/zjuphoenix/container-parent

其基本設(shè)計思想是把系統(tǒng)中依賴的組件全都單獨打成一個bundle苹享,比如項目依賴了幾個單獨的模塊bundle1双絮、bundle2等等,而bundle1和bundle2都依賴了netty得问,project本身也依賴了netty囤攀,但我們想要bundle1、bundle2和應(yīng)用他們分別使用自己獨立的netty版本的依賴宫纬,應(yīng)用業(yè)務(wù)也用自己獨立的netty版本焚挠。這時需要為bundle1和bundle2設(shè)計單獨的自定義classloader,用于加載該bundle自己的類和依賴的第三方庫漓骚。

首先看下測試項目打成的test.tar.gz壓縮包解壓后的目錄結(jié)構(gòu):

test project dir.png

測試項目中除了包含lib(依賴的第三方庫)和bin(運行腳本文件存放位置)這兩個目錄蝌衔,還包含一個container目錄,里面包含bundle-first和bundle-second兩個依賴的模塊和一個lib(conatiner自己的類及依賴)蝌蹂,每個模塊的目錄結(jié)構(gòu)包含一個lib目錄(用于存放該模塊自身類的jar包和依賴的第三方j(luò)ar包)和一個config.json配置文件(用于定義該模塊要暴露給業(yè)務(wù)方使用的類和需要從業(yè)務(wù)方類庫中導入的類)

假設(shè)應(yīng)用依賴了兩個組件bundle1和bundle2噩斟,且希望這兩個組件的類與應(yīng)用自己的類隔離開來,其classloader隔離實現(xiàn)原理如下圖:

classloader隔離實現(xiàn)架構(gòu).png

下面以測試項目test為例闡述class隔離實現(xiàn)孤个,首先test項目依賴了bundle-first和bundle-second這兩個組件剃允,bundle-first依賴了netty的4.1.8版本,bundle-second依賴了netty的4.1.7版本齐鲤,并且他們都依賴了spring的4.3.7版本硅急,test自己也依賴了netty的4.1.6版本,假設(shè)希望這兩個組件分別使用自己的netty版本佳遂,但希望使用應(yīng)用依賴的spring版本,因此需要在配置文件中設(shè)置如下撒顿,以bundle-first為例:

{
  "exportJars":["bundle-first-api-1.0.jar", "bundle-first-1.0.jar"],
  "importPackages":["org.springframework", "com.study.bundle.first.api"]
}

以bundle-first為例丑罪,需要把自己的api包和實現(xiàn)包暴露出去,bundle-first-api-1.0.jar包中的類為BundleFirstApi接口,bundle-first-1.0.jar實現(xiàn)包中的類是BundleFirstImpl(BundleFirstApi接口實現(xiàn))吩屹,需要設(shè)置spring的依賴為import跪另,表示spring的類不用bundle-first自己的classloader加載,交給應(yīng)用類加載器去加載煤搜,需要注意的是必須要將bundle-first-api-1.0.jar中的接口類所在的包名作為import設(shè)置免绿,讓應(yīng)用類加載器來加載這個api接口類,否則該接口類會由bundle-first的classloader加載擦盾,而在應(yīng)用中獲取該接口類BundleFirstApi實例的時候該接口類又會被應(yīng)用類加載器加載一次嘲驾,這樣接口類實現(xiàn)類BundleFirstImpl與應(yīng)用類加載器加載的BundleFirstApi類不屬于父子關(guān)系,因為BundleFirstImpl的父類是由bundle-first的classloader加載的BundleFirstApi迹卢,不是應(yīng)用類加載器加載的BundleFirstApi辽故,從而無法強轉(zhuǎn)而出現(xiàn)ClassCastException,如果把BundleFirstApi作為import配置在配置文件中腐碱,當加載BundleFirstImpl類時會先加載BundleFirstApi誊垢,當發(fā)現(xiàn)該類是import配置時,會交由應(yīng)用類加載器該類症见,這樣bundle-first的classloader加載的BundleFirstImpl類與應(yīng)用類加載器加載的BundleFirstApi類便構(gòu)成了父子關(guān)系喂走。

從應(yīng)用的lib包中可以看到,應(yīng)用類依賴了bundle-first-api-1.0.jar和bundle-second-api-1.0.jar這兩個組件的api包谋作,應(yīng)用通過ServiceLoader機制獲取組件的實例芋肠,且通過ContainerClassLoader類加載器加載瓷们。

對該jar包隔離組件的用法是:首先要對要依賴的組件的api進行抽離业栅,然后把實現(xiàn)類是api的包分為兩個不同的包谬晕,如bundle-first-api-1.0.jar和bundle-first-1.0.jar碘裕。bundle-first-api-1.0.jar是要讓應(yīng)用在maven pom里去顯示依賴的包攒钳,bundle-first-1.0.jar不需要顯示依賴。這樣實際上該組件依賴的第三方依賴如netty都會在bundle-first-1.0.jar依賴里不撑,而沒有在bundle-first-api-1.0.jar的依賴里文兢,應(yīng)用只依賴了bundle-first-api-1.0.jar,所以bundle-first的第三方依賴包不會引入到應(yīng)用的依賴中姆坚,應(yīng)用只是用到了bundle-first-api-1.0.jar這個包而已。

下面根據(jù)上圖逐步分析:

  • step1:應(yīng)用要加載BundleFirstApi類实愚,因為應(yīng)用的classpath中也有bundle-first-api-1.0.jar兼呵,因此也可以加載到BundleFirstApi類。
  • step2:應(yīng)用通過ContainerClassLoader類加載器加載BundleFirstImpl組件實現(xiàn)類击喂。
  • step3:ContainerClassLoader會判斷BundleFirstImpl類是不是export class中的類维苔,是的話便交給BundleExportClassManager處理。
  • step4:BundleExportClassManager維護著所有export class的緩存介时,在容器啟動過程中就會把每個bundle的classloader創(chuàng)建好并根據(jù)配置文件把export class全都用相應(yīng)的BundleClassLoader加載出來放在緩存中(注意:當加載暴露的類中的api接口實現(xiàn)類時,以BundleFirstImpl為例凌彬,要先加載其父類BundleFirstApi沸柔,因為BundleFirstApi是import類,會交給應(yīng)用類加載器加載饿序,也就是父類和子類不是同一個classloader加載的)勉失,因此可以根據(jù)類的全限定名從BundleExportClassManager獲取每個bundle暴露出來的類,ContainerClassLoader把要加載的bundle-first的實現(xiàn)類的全限定名交給BundleExportClassManager原探,BundleExportClassManager找到對應(yīng)的組件實現(xiàn)類并實例化乱凿。
  • step5:得到組件實現(xiàn)類BundleFirstImpl實例后,便可以調(diào)用該組件的一些方法了咽弦。

測試結(jié)果如下:

App:
test spring classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
spring version: 4.3.6.RELEASE
test app class: io.netty.util.Version classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
app netty version:{netty-buffer=netty-buffer-4.1.6.Final.35fb0ba, netty-codec=netty-codec-4.1.6.Final.35fb0ba, netty-codec-dns=netty-codec-dns-4.1.6.Final.35fb0ba, netty-codec-haproxy=netty-codec-haproxy-4.1.6.Final.35fb0ba, netty-codec-http=netty-codec-http-4.1.6.Final.35fb0ba, netty-codec-http2=netty-codec-http2-4.1.6.Final.35fb0ba, netty-codec-memcache=netty-codec-memcache-4.1.6.Final.35fb0ba, netty-codec-mqtt=netty-codec-mqtt-4.1.6.Final.35fb0ba, netty-codec-redis=netty-codec-redis-4.1.6.Final.35fb0ba, netty-codec-socks=netty-codec-socks-4.1.6.Final.35fb0ba, netty-codec-stomp=netty-codec-stomp-4.1.6.Final.35fb0ba, netty-common=netty-common-4.1.6.Final.35fb0ba, netty-handler=netty-handler-4.1.6.Final.35fb0ba, netty-handler-proxy=netty-handler-proxy-4.1.6.Final.35fb0ba, netty-resolver=netty-resolver-4.1.6.Final.35fb0ba, netty-resolver-dns=netty-resolver-dns-4.1.6.Final.35fb0ba, netty-tcnative=netty-tcnative-1.1.33.Fork23.f89906a, netty-transport=netty-transport-4.1.6.Final.35fb0ba, netty-transport-native-epoll=netty-transport-native-epoll-4.1.6.Final.35fb0ba, netty-transport-rxtx=netty-transport-rxtx-4.1.6.Final.35fb0ba, netty-transport-sctp=netty-transport-sctp-4.1.6.Final.35fb0ba, netty-transport-udt=netty-transport-udt-4.1.6.Final.35fb0ba}
BundleFirst:
test import: spring classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
spring version: 4.3.6.RELEASE
test import: BundleFirstApi classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
test export: BundleFirstImpl classloader:bundle-first's BundleClassLoader
test bundle class: io.netty.util.Version classloader:bundle-first's BundleClassLoader
bundle-first netty version:{netty-buffer=netty-buffer-4.1.8.Final.76e22e6, netty-codec=netty-codec-4.1.8.Final.76e22e6, netty-codec-dns=netty-codec-dns-4.1.8.Final.76e22e6, netty-codec-haproxy=netty-codec-haproxy-4.1.8.Final.76e22e6, netty-codec-http=netty-codec-http-4.1.8.Final.76e22e6, netty-codec-http2=netty-codec-http2-4.1.8.Final.76e22e6, netty-codec-memcache=netty-codec-memcache-4.1.8.Final.76e22e6, netty-codec-mqtt=netty-codec-mqtt-4.1.8.Final.76e22e6, netty-codec-redis=netty-codec-redis-4.1.8.Final.76e22e6, netty-codec-smtp=netty-codec-smtp-4.1.8.Final.76e22e6, netty-codec-socks=netty-codec-socks-4.1.8.Final.76e22e6, netty-codec-stomp=netty-codec-stomp-4.1.8.Final.76e22e6, netty-common=netty-common-4.1.8.Final.76e22e6, netty-handler=netty-handler-4.1.8.Final.76e22e6, netty-handler-proxy=netty-handler-proxy-4.1.8.Final.76e22e6, netty-resolver=netty-resolver-4.1.8.Final.76e22e6, netty-resolver-dns=netty-resolver-dns-4.1.8.Final.76e22e6, netty-tcnative=netty-tcnative-1.1.33.Fork26.142ecbb, netty-transport=netty-transport-4.1.8.Final.76e22e6, netty-transport-native-epoll=netty-transport-native-epoll-4.1.8.Final.76e22e6, netty-transport-rxtx=netty-transport-rxtx-4.1.8.Final.76e22e6, netty-transport-sctp=netty-transport-sctp-4.1.8.Final.76e22e6, netty-transport-udt=netty-transport-udt-4.1.8.Final.76e22e6}
1
BundleSecond:
test import: spring classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
spring version: 4.3.6.RELEASE
test import: BundleSecondApi classloader:sun.misc.Launcher$AppClassLoader@14dad5dc
test export: BundleSecondImpl classloader:bundle-second's BundleClassLoader
test bundle class: io.netty.util.Version classloader:bundle-second's BundleClassLoader
bundle-second netty version:{netty-buffer=netty-buffer-4.1.7.Final.7a21eb1, netty-codec=netty-codec-4.1.7.Final.7a21eb1, netty-codec-dns=netty-codec-dns-4.1.7.Final.7a21eb1, netty-codec-haproxy=netty-codec-haproxy-4.1.7.Final.7a21eb1, netty-codec-http=netty-codec-http-4.1.7.Final.7a21eb1, netty-codec-http2=netty-codec-http2-4.1.7.Final.7a21eb1, netty-codec-memcache=netty-codec-memcache-4.1.7.Final.7a21eb1, netty-codec-mqtt=netty-codec-mqtt-4.1.7.Final.7a21eb1, netty-codec-redis=netty-codec-redis-4.1.7.Final.7a21eb1, netty-codec-smtp=netty-codec-smtp-4.1.7.Final.7a21eb1, netty-codec-socks=netty-codec-socks-4.1.7.Final.7a21eb1, netty-codec-stomp=netty-codec-stomp-4.1.7.Final.7a21eb1, netty-common=netty-common-4.1.7.Final.7a21eb1, netty-handler=netty-handler-4.1.7.Final.7a21eb1, netty-handler-proxy=netty-handler-proxy-4.1.7.Final.7a21eb1, netty-resolver=netty-resolver-4.1.7.Final.7a21eb1, netty-resolver-dns=netty-resolver-dns-4.1.7.Final.7a21eb1, netty-tcnative=netty-tcnative-1.1.33.Fork25.87555d6, netty-transport=netty-transport-4.1.7.Final.7a21eb1, netty-transport-native-epoll=netty-transport-native-epoll-4.1.7.Final.7a21eb1, netty-transport-rxtx=netty-transport-rxtx-4.1.7.Final.7a21eb1, netty-transport-sctp=netty-transport-sctp-4.1.7.Final.7a21eb1, netty-transport-udt=netty-transport-udt-4.1.7.Final.7a21eb1}
2

通過測試結(jié)果可知:
首先看App的class測試結(jié)果徒蟆,其依賴的spring的類的classloader是AppClassLoader,其依賴的netty類的classloader是AppClassLoader型型,版本為4.1.6段审,與每個bundle的不同。

然后看每個bundle的class測試結(jié)果闹蒜,每個bundle的spring的類是由AppClassLoader加載的寺枉,且版本為4.3.6.RELEASE,雖然每個bundle依賴的spring版本都是4.3.7.RELEASE绷落,但因為配置了spring的類為import姥闪,因此會從AppClassLoader加載,驗證了結(jié)果的正確性砌烁。

bundle1的BundleFirstImpl使用他自己的classloader加載的筐喳,結(jié)果正確;bundle1依賴的netty類是由自己的classloader加載的函喉,且netty版本為4.1.8避归,結(jié)果正確。bundle1的BundleFirstApi類是由AppClassLoader加載的管呵,因為該類的包名配置在了import里梳毙。

bundle2的BundleSecondImpl使用他自己的classloader加載的,結(jié)果正確捐下;bundle2依賴的netty類是由自己的classloader加載的顿天,且netty版本為4.1.7堂氯,結(jié)果正確。bundle2的BundleSecondApi類是由AppClassLoader加載的牌废,因為該類的包名配置在了import里。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啤握,一起剝皮案震驚了整個濱河市鸟缕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌排抬,老刑警劉巖懂从,帶你破解...
    沈念sama閱讀 223,126評論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹲蒲,居然都是意外死亡番甩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評論 3 400
  • 文/潘曉璐 我一進店門届搁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缘薛,“玉大人,你說我怎么就攤上這事卡睦⊙珉剩” “怎么了?”我有些...
    開封第一講書人閱讀 169,941評論 0 366
  • 文/不壞的土叔 我叫張陵表锻,是天一觀的道長恕齐。 經(jīng)常有香客問我,道長瞬逊,這世上最難降的妖魔是什么显歧? 我笑而不...
    開封第一講書人閱讀 60,294評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮确镊,結(jié)果婚禮上士骤,老公的妹妹穿的比我還像新娘。我一直安慰自己骚腥,他們只是感情好敦间,可當我...
    茶點故事閱讀 69,295評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著束铭,像睡著了一般廓块。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上契沫,一...
    開封第一講書人閱讀 52,874評論 1 314
  • 那天带猴,我揣著相機與錄音,去河邊找鬼懈万。 笑死拴清,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的口予。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼沪停,長吁一口氣:“原來是場噩夢啊……” “哼煤辨!你這毒婦竟也來了木张?” 一聲冷哼從身側(cè)響起众辨,我...
    開封第一講書人閱讀 40,249評論 0 277
  • 序言:老撾萬榮一對情侶失蹤鹃彻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妻献,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,760評論 1 321
  • 正文 獨居荒郊野嶺守林人離奇死亡旋奢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,840評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了至朗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,973評論 1 354
  • 序言:一個原本活蹦亂跳的男人離奇死亡锹引,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嫌变,到底是詐尸還是另有隱情吨艇,我是刑警寧澤腾啥,帶...
    沈念sama閱讀 36,631評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站倘待,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏凸舵。R本人自食惡果不足惜祖娘,卻給世界環(huán)境...
    茶點故事閱讀 42,315評論 3 336
  • 文/蒙蒙 一渐苏、第九天 我趴在偏房一處隱蔽的房頂上張望掀潮。 院中可真熱鬧,春花似錦仪吧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凡蚜。三九已至吭从,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涩金,已是汗流浹背谱醇。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評論 1 275
  • 我被黑心中介騙來泰國打工步做, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人全度。 一個月前我還...
    沈念sama閱讀 49,431評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像将鸵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子顶掉,可洞房花燭夜當晚...
    茶點故事閱讀 45,982評論 2 361

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)痒筒,斷路器宰闰,智...
    卡卡羅2017閱讀 134,722評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,349評論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,871評論 6 342
  • 作為一個20出頭的美少女移袍,我太對不起我的年紀了。繼脖子硬了一周之后眼睛神奇的長出了個大包萎战,麥粒腫,哈哈哈蚂维,其...
    蕾哦蕾哦蕾閱讀 582評論 0 0
  • 你有選擇權(quán)路狮?看似有又好似沒有。 年少不懂事蔚约,等到懂的時候又盤根錯節(jié)分不清是不能選 還是不敢選。 到頭來空余恨苹祟。 周...
    吧一閱讀 369評論 0 0