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