Equinox動(dòng)態(tài)化深入分析
OSGi最吸引人的特性除了模塊化之外,就是動(dòng)態(tài)化了,在我之前寫的OSGi實(shí)戰(zhàn)以及進(jìn)階兩篇Opendoc中,都有相關(guān)的示例,但不知道大家有沒(méi)有注意,在兩篇Opendoc中都未提及到bundle本身的更新,而基本都是以新增服務(wù)實(shí)現(xiàn)的bundle以及停止服務(wù)時(shí)限的bundle為例,并且相對(duì)而言是個(gè)比較簡(jiǎn)單的例子,動(dòng)態(tài)化在java界更明確的詞也許是hotdeployment,而hotdeployment的實(shí)現(xiàn)并不容易,同樣,即使你采用OSGi,但也不代表你的應(yīng)用就具備了hotdeployment的能力,在hotdeployment上,完美的結(jié)果就是當(dāng)更新完成后,新的執(zhí)行請(qǐng)求就在新的代碼邏輯上正確的執(zhí)行,就像沒(méi)發(fā)生過(guò)更新這回事樣,但實(shí)際要做到這樣的效果,遠(yuǎn)沒(méi)這么容易,即使是基于OSGi也同樣如此,Nomagic&nosilverbullet,在本篇blog中我們就來(lái)具體的看看。
OSGi以Bundle為粒度來(lái)實(shí)現(xiàn)動(dòng)態(tài)化,也就是說(shuō),如果要更新一個(gè)類,需要做的是更新整個(gè)Bundle,雖然比直接部署一個(gè)類麻煩了點(diǎn),但也還算是不錯(cuò)的了,更新的方法有兩種,一種是直接update該bundle(在MANIFEST.MF中增加Bundle-UpdateLocation來(lái)指定Bundle更新時(shí)所使用的文件);另外一種是先uninstall舊的bundle,然后再安裝并啟動(dòng)新的bundle,無(wú)論是哪種方法,對(duì)于OSGi的應(yīng)用而言,問(wèn)題就在于package的類的改變以及bundle中OSGi服務(wù)實(shí)現(xiàn)的改變。
在Equinox中,當(dāng)update一個(gè)Bundle時(shí),如果這個(gè)Bundle中有對(duì)外暴露的package,如果這個(gè)Bundle是singleton模式,在update后仍然保留了同樣的BundleSymbolicName的話,其實(shí)是無(wú)法update成功的,會(huì)報(bào)出一個(gè)已經(jīng)有相同的Singleton的Bundle存在,因此update這種方法僅適用于沒(méi)有對(duì)外暴露package的bundle,如bundle沒(méi)有對(duì)外暴露的package,Equinox則可正常的完成update過(guò)程,通常來(lái)講,不對(duì)外暴露package的bundle都是一些對(duì)外暴露OSGi服務(wù)或者使用OSGi服務(wù)的類,對(duì)于對(duì)于暴露OSGi服務(wù)的類而言,在update過(guò)程中將會(huì)把依賴了此OSGi服務(wù)的OSGi組件的實(shí)例銷毀(遞歸),等當(dāng)前bundle更新并啟動(dòng)完畢后,會(huì)重新實(shí)例化該OSGi組件,同時(shí)將新的服務(wù)實(shí)現(xiàn)對(duì)象設(shè)置進(jìn)去,對(duì)于僅使用其他Bundle提供的OSGi服務(wù)的類而言則很簡(jiǎn)單了,在啟動(dòng)此bundle時(shí)自然會(huì)設(shè)置進(jìn)來(lái),同時(shí)也不會(huì)影響外部bundle。
從上可見(jiàn),通過(guò)update方式來(lái)完成Bundle的更新受到了很大的限制,畢竟大部分時(shí)候Bundle都是singleton的,并且在更新的時(shí)候也是不會(huì)去改變其BundleSymbolicName。
因此,在Equinox中要實(shí)現(xiàn)Bundle的更新,通常都使用另外一種方法,就是uninstall,然后再install并start更新后的bundle。
當(dāng)uninstall時(shí),如果此bundle有對(duì)外暴露的package,并且有使用這些package的bundle,那么Equinox會(huì)保留此Bundle的classloader,也就是說(shuō)原來(lái)使用了這些package的bundle仍將使用之前bundle的類,這也是為什么一個(gè)Bundleuninstall了之后,其他Bundle仍然可使用該Bundle中export的類,要想讓Bundle對(duì)外export的package的引用也失效并且切換到新的bundle中export的package,必須執(zhí)行refresh動(dòng)作,refresh時(shí)equinox將會(huì)找到之前uninstall沒(méi)完全成功的bundle,并遞歸找到使用了這個(gè)bundle中package的bundle,將這些bundle的狀態(tài)也置為unresolve,并解除對(duì)之前uninstall沒(méi)完全成功的bundle的classloader的引用,這樣被uninstall的bundle的class就能被GC卸載了,在此之后,Equinox會(huì)嘗試再次去resolve之前設(shè)置為unresolve的bundle,如果resolve不了則會(huì)調(diào)用這些bundle的stop方法,卸載其對(duì)外提供的OSGi服務(wù)以及引用的OSGi服務(wù),同時(shí)將其狀態(tài)置為INSTALLED;但在uninstall時(shí),對(duì)OSGi服務(wù)的處理方法則不太一樣,此Bundle中所引用的OSGi服務(wù)會(huì)被釋放,對(duì)外提供的OSGi服務(wù)也會(huì)注銷,這會(huì)造成引用了這些OSGi服務(wù)的Bundle的OSGi組件(遞歸)的實(shí)例會(huì)被銷毀。
完成了以上的動(dòng)作后,可以安裝新的bundle,安裝新bundle時(shí),其實(shí)就是做了些解析bundle的事情,直到startbundle時(shí),才開(kāi)始resolve過(guò)程,所謂resolve就是找到bundle對(duì)外提供的package、需要引用的package等,同時(shí)創(chuàng)建bundle的classloader,在這個(gè)過(guò)程,equinox也會(huì)對(duì)系統(tǒng)中所有unresolved的bundle進(jìn)行resolve,如能夠resolve則將其狀態(tài)轉(zhuǎn)化為resolved,最后調(diào)用BundleContext的start來(lái)完成bundle的啟動(dòng),這個(gè)過(guò)程僅僅是在配置了BundleActivator的情況下才有意義,DS則完成此bundle中引用的OSGi服務(wù)或?qū)ν馓峁㎡SGi服務(wù)的組件的條件的檢測(cè),以判斷這些組件是否可實(shí)例化,如有新的OSGi服務(wù)可對(duì)外提供,那么DS會(huì)檢測(cè)此時(shí)其他Bundle中的OSGi組件是否需要被激活,或者是否需要調(diào)用其他Bundle中OSGi組件的set方法。
根據(jù)以上這樣的描述,可以看出,在OSGi中如果要更新沒(méi)有對(duì)外提供package的Bundle是比較容易的,update以及uninstallàstart都是可選的方法,而對(duì)于對(duì)外提供了package的Bundle而言,則相對(duì)復(fù)雜很多,只能選擇uninstallàrefreshàstart來(lái)完成。
從兩個(gè)緯度來(lái)看OSGi的動(dòng)態(tài)化,對(duì)于有export-packageBundle的更新,OSGi將會(huì)重建更新的bundle以及引用了此bundle的package的ClassLoader,而對(duì)于OSGi服務(wù)組件的更新,OSGi則會(huì)重新創(chuàng)建引用了此OSGi服務(wù)的組件的實(shí)例,并通過(guò)unset這樣的方法通知原來(lái)的組件釋放對(duì)OSGi服務(wù)的引用,同時(shí)通過(guò)set方法來(lái)給新創(chuàng)建的實(shí)例注入更新后的OSGi服務(wù)組件實(shí)例,其實(shí)這也是hotdeployment中常見(jiàn)的對(duì)于引用變更的處理方法。
但從上面也可以看出,OSGi并沒(méi)有提供對(duì)象狀態(tài)保留的處理,這也就意味著,基本上在一次更新后,此次更新的Bundle以及相關(guān)的bundle因?yàn)閏lassloader的重建,其對(duì)象的狀態(tài)數(shù)據(jù)都丟失了,不過(guò)對(duì)于更新的僅為提供或引用OSGi服務(wù)的Bundle而言,則稍微好點(diǎn),畢竟只是影響到了遞歸的引用了OSGi服務(wù)的組件,組件由于重建實(shí)例,而導(dǎo)致?tīng)顟B(tài)數(shù)據(jù)丟失,這個(gè)倒是可以通過(guò)將服務(wù)的引用數(shù)量設(shè)置為cardinality=”0..1”或cardinality=”0..n”來(lái)解決,設(shè)置成這樣的條件后,即使引用了需要更新的Bundle中提供的OSGi服務(wù),其OSGi服務(wù)組件實(shí)例也不會(huì)被重建,這對(duì)于需要將OSGi服務(wù)引用提供給外部使用的系統(tǒng)而言,無(wú)疑非常有幫助。
根據(jù)以上所述,可以看到,即使是基于OSGi,要實(shí)現(xiàn)hotdeployment還是比較麻煩的,Nomagicandnosilverbullet,J,尤其是要注意classloader的重建以及OSGi服務(wù)組件實(shí)例的重建,否則很有可能會(huì)造成在更新后系統(tǒng)的異常,在基于OSGi實(shí)現(xiàn)hotdeployment時(shí),要合理的規(guī)劃系統(tǒng),常見(jiàn)的一些較好的實(shí)踐方法有:
◆接口和實(shí)現(xiàn)分離
避免因?yàn)閷?shí)現(xiàn)邏輯要更新,而造成其他引用了此Bundleexport出去接口所在的package而導(dǎo)致classloader的重建。
◆對(duì)于需要保留狀態(tài)數(shù)據(jù)的OSGi服務(wù)盡量避免引用其他bundleexport-package中的類
這也是為了避免這些類所在的bundle的classloader重建,畢竟OSGi服務(wù)組件類可以通過(guò)設(shè)置cardinality來(lái)保持組件實(shí)例的不變。
◆服務(wù)組件采用cardinality=”0..1”或cardinality=”0..n”來(lái)設(shè)置對(duì)OSGi服務(wù)的引用
避免服務(wù)組件實(shí)例的重建,畢竟這是個(gè)遞歸過(guò)程,影響還是很大的,而且誰(shuí)也不敢肯定這么多的服務(wù)組件實(shí)例的重建是不是會(huì)造成系統(tǒng)的異常現(xiàn)象。
在這種情況下,尤其要注意unset中的處理以及當(dāng)沒(méi)有可用服務(wù)情況下的處理,避免出現(xiàn)NPE。
◆盡量采用OSGi服務(wù)組件服務(wù)方式,而不是直接的類方式
由于類方式的更新成本實(shí)在是比較的高,畢竟那需要classloader的重建,但是有些類確實(shí)是沒(méi)辦法的,對(duì)于這些類要盡量的保證穩(wěn)態(tài)。
◆嚴(yán)格的版本控制
畢竟接口的更新影響是很大的,因?yàn)樗袑?shí)現(xiàn)接口的類都得改變,因此需要嚴(yán)格的制定版本規(guī)范,并在引用package時(shí)按照版本規(guī)范指定相應(yīng)的版本范圍。
【編輯推薦】