對于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):
測試項目中除了包含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)原理如下圖:
下面以測試項目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里。