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

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

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

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

test project dir.png

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

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

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

下面以測試項目test為例闡述class隔離實(shí)現(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包和實(shí)現(xiàn)包暴露出去,bundle-first-api-1.0.jar包中的類為BundleFirstApi接口,bundle-first-1.0.jar實(shí)現(xiàn)包中的類是BundleFirstImpl(BundleFirstApi接口實(shí)現(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實(shí)例的時候該接口類又會被應(yīng)用類加載器加載一次,這樣接口類實(shí)現(xiàn)類BundleFirstImpl與應(yīng)用類加載器加載的BundleFirstApi類不屬于父子關(guān)系,因?yàn)锽undleFirstImpl的父類是由bundle-first的classloader加載的BundleFirstApi,不是應(yīng)用類加載器加載的BundleFirstApi,從而無法強(qiáng)轉(zhuǎn)而出現(xiàn)ClassCastException,如果把BundleFirstApi作為import配置在配置文件中,當(dāng)加載BundleFirstImpl類時會先加載BundleFirstApi,當(dāng)發(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機(jī)制獲取組件的實(shí)例,且通過ContainerClassLoader類加載器加載。

對該jar包隔離組件的用法是:首先要對要依賴的組件的api進(jìn)行抽離,然后把實(shí)現(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不需要顯示依賴。這樣實(shí)際上該組件依賴的第三方依賴如netty都會在bundle-first-1.0.jar依賴?yán)?,而沒有在bundle-first-api-1.0.jar的依賴?yán)?,?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àn)閼?yīng)用的classpath中也有bundle-first-api-1.0.jar,因此也可以加載到BundleFirstApi類。
  • step2:應(yīng)用通過ContainerClassLoader類加載器加載BundleFirstImpl組件實(shí)現(xiàn)類。
  • step3:ContainerClassLoader會判斷BundleFirstImpl類是不是export class中的類,是的話便交給BundleExportClassManager處理。
  • step4:BundleExportClassManager維護(hù)著所有export class的緩存,在容器啟動過程中就會把每個bundle的classloader創(chuàng)建好并根據(jù)配置文件把export class全都用相應(yīng)的BundleClassLoader加載出來放在緩存中(注意:當(dāng)加載暴露的類中的api接口實(shí)現(xiàn)類時,以BundleFirstImpl為例,要先加載其父類BundleFirstApi,因?yàn)锽undleFirstApi是import類,會交給應(yīng)用類加載器加載,也就是父類和子類不是同一個classloader加載的),因此可以根據(jù)類的全限定名從BundleExportClassManager獲取每個bundle暴露出來的類,ContainerClassLoader把要加載的bundle-first的實(shí)現(xiàn)類的全限定名交給BundleExportClassManager,BundleExportClassManager找到對應(yīng)的組件實(shí)現(xiàn)類并實(shí)例化。
  • step5:得到組件實(shí)現(xiàn)類BundleFirstImpl實(shí)例后,便可以調(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,但因?yàn)榕渲昧藄pring的類為import,因此會從AppClassLoader加載,驗(yàn)證了結(jié)果的正確性。

bundle1的BundleFirstImpl使用他自己的classloader加載的,結(jié)果正確;bundle1依賴的netty類是由自己的classloader加載的,且netty版本為4.1.8,結(jié)果正確。bundle1的BundleFirstApi類是由AppClassLoader加載的,因?yàn)樵擃惖陌渲迷诹薸mport里。

bundle2的BundleSecondImpl使用他自己的classloader加載的,結(jié)果正確;bundle2依賴的netty類是由自己的classloader加載的,且netty版本為4.1.7,結(jié)果正確。bundle2的BundleSecondApi類是由AppClassLoader加載的,因?yàn)樵擃惖陌渲迷诹薸mport里。

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

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

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

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