自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

OSGi,Java模塊化框架的另類進(jìn)化

原創(chuàng)
開發(fā) 后端
2010年將是Java模塊化的一年,但為Java模塊化支持的OSGi正在受到過于復(fù)雜的抱怨。本文帶您走進(jìn)OSGi的進(jìn)化歷程,看看他為什么這么復(fù)雜。

【51CTO譯文】我們曾不只一次的聽到2010年將是Java模塊化的一年的言論;也知道目前為Java提供模塊化的OSGi正在受到IBM和Eclipse基金會的大力支持。但作為實(shí)現(xiàn)Java模塊化應(yīng)用的基礎(chǔ)框架,OSGi似乎并不完美;我們經(jīng)常能聽到關(guān)于OSGi過于復(fù)雜的抱怨。

從個(gè)人的角度,我以開放的心態(tài)去了解OSGi。令人失望的是,我發(fā)現(xiàn)它的規(guī)則非常復(fù)雜而且是低階的(low-level),對于大多數(shù)企業(yè) Java 環(huán)境,需要對其進(jìn)行許多改善/縮寫的工作,才能讓它更容易被人理解。對于大多數(shù)實(shí)際的企業(yè)需求,它又顯得功能過于強(qiáng)大。比較而言,Jigsaw 感覺更“干凈”,以 Java 為中心,緊湊而且易于理解。

說實(shí)話,這種抱怨讓我有點(diǎn)困惑。我來假設(shè)一下,如果OSGi 現(xiàn)在并不存在,有人給我一項(xiàng)任務(wù),為Java 平臺設(shè)計(jì)一個(gè)新的模塊系統(tǒng),那么對于這個(gè)模塊系統(tǒng)的最合理的需求集合將直接指向OSGi,因?yàn)镺SGi的設(shè)計(jì)目的可能是滿足我們需求的最簡單的解決方案。

是不是我的想象力不夠?或者這不過是愛因斯坦剃刀原理(事物應(yīng)盡可能簡單而不是更簡單)打敗奧卡姆剃刀原理(如無必要,勿增實(shí)體)的又一個(gè)實(shí)例?

另外,我認(rèn)可OSGi 初看并不是那么簡單這一說法,尤其是你不了解它為什么會是現(xiàn)在這樣的時(shí)候。

OSGi框架的各個(gè)組成部分 
OSGi框架的各個(gè)組成部分

在這篇文章中,我將按照上述的那個(gè)假設(shè),從零開始設(shè)計(jì)OSGi 系統(tǒng)。當(dāng)然許多細(xì)節(jié)問題在這里我不能一一講述。下面切入正題,為什么OSGi 成為現(xiàn)在這個(gè)樣子?我們一起看看OSGi不算漫長但足夠復(fù)雜的進(jìn)化(這種進(jìn)化是積極的,因?yàn)樗鼮榻鉀Q業(yè)界實(shí)際存在的問題而生)。

51CTO編者注:關(guān)于OSGi的更多內(nèi)容可以參考51CTO的專題OSGi入門與實(shí)踐全攻略或參考OSGi的入門文章初探Java企業(yè)級開源框架OSGi ;關(guān)于Java模塊化的內(nèi)容可以參考51CTO對淘寶網(wǎng)架構(gòu)師的專訪一步一步了解Java模塊化。

模塊分離

我們的第一個(gè)需求是清晰地劃分模塊,這樣一個(gè)模塊中的類就不會具有我們無法控制的功能:使用或覆蓋另一個(gè)模塊中的類。在傳統(tǒng)的 Java 中有一個(gè)“classpath”(類路徑),這是一個(gè)巨大的類列表,當(dāng)多個(gè)類碰巧使用相同的名稱時(shí),總是使用第一個(gè)類,而第二個(gè)和其他所有的同名類將被忽略??雌饋磉@種事情不會經(jīng)常發(fā)生,當(dāng)事實(shí)并非如此。當(dāng)存在許多庫而這些庫又依靠其他庫時(shí),這個(gè)問題就變得常見了。這個(gè)覆蓋問題絕對是致命的,因?yàn)樗鼤?dǎo)致一些奇怪的錯(cuò)誤,比如 LinkageError、IncompatibleClassChangeError 等。事實(shí)上能夠看到這些錯(cuò)誤,那還是比較幸運(yùn)的。倒霉的是這些錯(cuò)誤沒有提示,而系統(tǒng)一聲不響地錯(cuò)誤地運(yùn)行,哪怕在部署之前我們做了許多先行測試。

對于類的覆蓋和不可能空的可見性,預(yù)防方法是為每一個(gè)模塊創(chuàng)建一個(gè)類加載器(class loader)。類加載器能夠做到僅加載它能夠直接識別的類,在我們的這個(gè)系統(tǒng)中,就是某個(gè)模塊的內(nèi)容(不過,它也可以根據(jù)類對類的方式,請求其他類加載器提供類,這種方式稱為委派,即 delegation)。使用類加載器之后,每個(gè)模塊包括他需要處理的代碼和類,而且能夠保證獲得按照計(jì)劃應(yīng)該使用的類,即使系統(tǒng)中的其他模塊包含同名的類。

從整體上恢復(fù)可見性等功能

完成以上步驟之后,我們到達(dá)這樣一個(gè)點(diǎn):所有模塊完全隔離,無法互相通信。為了讓這個(gè)系統(tǒng)變得實(shí)用些,我們需要恢復(fù)一些功能,以便能夠看到其他模塊中的類,不過這樣做時(shí)必須非常謹(jǐn)慎,而且必須使用嚴(yán)格控制的方式。這里我們又多了一個(gè)需求:模塊需要能夠隱藏某些部署細(xì)節(jié)。

在 Java 中,protected/默認(rèn)和 public 類型之間缺少訪問修飾符。假設(shè)我寫了一個(gè)庫,希望這個(gè)庫中其他包能夠使用我的一個(gè)類,我必須讓這個(gè)類設(shè)置為 public。但這樣這個(gè)類將對所有人是可見的,包括這個(gè)庫外部的客戶,這些客戶將能夠直接使用我的內(nèi)部類。我們想要的是一個(gè)“模塊”級的訪問級別,但現(xiàn)在的問題是 javac 編譯器無法區(qū)分模塊邊界在哪里,因此對于這樣的訪問修飾符它無法執(zhí)行任何檢測。事實(shí)上,現(xiàn)有的“默認(rèn)”訪問修飾符也是有問題的,因?yàn)樗鼞?yīng)該只對同一個(gè)“運(yùn)行時(shí)包(runtime package,即由某個(gè)特定類加載器加載的包)”提供訪問權(quán)。但同樣 javac 無法確定運(yùn)行時(shí)存在哪些加載器。對于這種情況,javac 會采取冒險(xiǎn)的方式:即使之后會導(dǎo)致 IllegalAccessErrors 錯(cuò)誤,它也會提供訪問權(quán)。

#p#

在我們這個(gè)模塊系統(tǒng)中,我們選擇的解決方式是允許模塊僅“導(dǎo)出”其內(nèi)容的一部分。如果模塊中某些部分是非導(dǎo)出的,那么對于其他模塊就是不可見的。但默認(rèn)導(dǎo)出哪些內(nèi)容?除了某些明顯需要隱藏的部分,我們應(yīng)該導(dǎo)出所有內(nèi)容嗎?或者除了那些明顯需導(dǎo)出的部分,我們應(yīng)該隱藏所有其他內(nèi)容?選擇后者看起來能夠到來更好的透明度:我們可以很方便查看導(dǎo)出列表,確定那些可見的部分,即模塊的“表面部分”。

請注意,我目前還沒指定具體導(dǎo)出什么內(nèi)容,這是一個(gè)需要仔細(xì)考慮的問題。

導(dǎo)出的反面是什么?當(dāng)然是導(dǎo)入。一個(gè)模塊想要使用其他模塊中代碼可以從后者進(jìn)行導(dǎo)入。現(xiàn)在我們有了另一個(gè)選擇……我們應(yīng)該導(dǎo)入另一個(gè)模塊導(dǎo)出的所有內(nèi)容嗎?或者只導(dǎo)入我們所需的那部分?同樣我們還是選擇后者,因?yàn)樗鼤砀玫耐该鞫龋褐匾氖俏覀儗?dǎo)入了什么而不是從哪里導(dǎo)入。

與購物行為進(jìn)行類比

關(guān)于導(dǎo)入的話題非常重要,所以這里次岔開一下話題,讓我們看一個(gè)有點(diǎn)搞笑又有點(diǎn)夸張的購物行為。

我妻子和我的購物方式是不同的。我認(rèn)為購物是一件麻煩的瑣事。每當(dāng)不得不去買東西時(shí),我就找到一家商店(或者一組商店),那里有我需要的東西,我只買我需要的商品,買到之后回家。只要能買到我需要的東西,我不關(guān)心是從哪家商店買到的。

而我妻子去了一家商店,那家商店買什么她就買什么。

很明顯我覺得我的購物方式更好,因?yàn)槲移拮訜o法控制她能買到什么東西。如果她常去的一家商店更換了貨柜上的商品,那她買回來的將是另外一些東西。當(dāng)然很多東西并不是她需要的,而且她真正需要的又沒有買到。

更糟糕的是,有時(shí)她買回來的東西并不能獨(dú)自使用,因?yàn)檫€需要其他東西,比如電池。所以她不得不再次去商店里買電池,同樣這次她會買下電池商店里出售的各種電池。再進(jìn)一步假設(shè),從電池商店里買到的某樣?xùn)|西還依靠其他東西才能使用,所以她又跑去另一家商店,僅僅是為了讓某些商品能夠正常工作,而這些商品從最初就不是我們所需要的。這個(gè)問題被稱為“扇出”(fan-out)。

通過這個(gè)購物類比,相信你對模塊系統(tǒng)將有一個(gè)更清晰的概念。這種非理智的購物行為等同于這樣一個(gè)系統(tǒng):我們申明了對某個(gè)模塊的依靠性,而這個(gè)系統(tǒng)強(qiáng)制我們從該模塊導(dǎo)入所有內(nèi)容。當(dāng)進(jìn)行導(dǎo)入時(shí),應(yīng)導(dǎo)入所有我們實(shí)際需要的內(nèi)容,而不管它來自哪里,同時(shí)忽略其他所有內(nèi)容,可能內(nèi)容只是碰巧位于它的包內(nèi)。使用 Maven 構(gòu)建工具時(shí)我們遇到這個(gè)尖銳的“扇出”問題,這個(gè)工具僅提供整體模塊的依賴性(即“買下整個(gè)商店”方式)。其結(jié)果是,在編譯 200 個(gè)字節(jié)的源文件之前,必須下載整個(gè)互聯(lián)網(wǎng)的內(nèi)容。

導(dǎo)入和導(dǎo)出的粒度(granularity)

從模塊導(dǎo)入和導(dǎo)出內(nèi)容的粒度應(yīng)該是怎樣的?由于存在各種嵌入等級,Java 中有多種等級的粒度。方法和域嵌入到類中,類又嵌入到包中,包嵌入到模塊或 JAR 文件中。

不難看出共享等級不應(yīng)是方法和域。導(dǎo)入一個(gè)類的某些方法而排除例外一些,這種方式很明顯是荒唐的。不僅僅這種方式是如此。我們可以為某個(gè)模塊中類寫一些方法/域,在另一個(gè)模塊中再寫一些方法/域,這種方式也同樣是不可行的。想象一下,為在模塊中的每一個(gè)共享方法寫一些導(dǎo)入和導(dǎo)出列表,運(yùn)行時(shí)對這些列表進(jìn)行檢查以及診斷為題的復(fù)雜度將是非??植赖?,會出現(xiàn)許多錯(cuò)誤,因?yàn)轭惒⒉皇窃O(shè)計(jì)用來在運(yùn)行時(shí)進(jìn)行分割的。

現(xiàn)在看看另一個(gè)極端,共享等級也不應(yīng)是整個(gè)模塊,因?yàn)檫@樣模塊就不能隱藏實(shí)施細(xì)節(jié)的部分,導(dǎo)入方將經(jīng)常性地遇到“買下整個(gè)商店”的問題。

所以唯一合理的選擇是類和包。老實(shí)說,選擇類也不是那么合理。雖然沒有方法/域那么糟糕,但類的數(shù)量非常多,由于它太過于依賴同一個(gè)包中的其他類,無論是將類列出作為我們的導(dǎo)入和導(dǎo)出,還是將包中的一些類劃分到某個(gè)模塊同時(shí)將同一個(gè)包中另一些類劃分到了另一個(gè)模塊中,都是不合理的。

最終的結(jié)果,OSGi 選擇了包。Java 包的內(nèi)容通常具有某種程度的一致性,但列出導(dǎo)入和導(dǎo)出的包并不是那么麻煩,而且在某個(gè)模塊加入一些包而在另一個(gè)模塊在加入另一些包,并不會對如何東西造成損壞。應(yīng)該屬于模塊內(nèi)部的代碼可以放到一個(gè)或多個(gè)非導(dǎo)出的包中。

我們的損失的無法干凈地處理那些所謂的“分裂包”(split-package)。在 OSGi 中,包是進(jìn)行共享的最基本單元:當(dāng)導(dǎo)入個(gè)包時(shí),你獲得一個(gè)模塊導(dǎo)出包的所有內(nèi)容而不包括其他內(nèi)容。一些傳統(tǒng)的包,一直堅(jiān)持在許多模塊中共享包內(nèi)容,對于這些包也存在一些方法進(jìn)行處理,但這好過對每個(gè)包進(jìn)行調(diào)整以便讓它作為整體只能由某個(gè)模塊導(dǎo)出。

包連線(wiring)

既然對于模塊如何自我分離然后再連接有了一個(gè)模型,我們現(xiàn)在可以想象創(chuàng)建一個(gè)框架,這個(gè)框架將為這些模塊構(gòu)造實(shí)際的運(yùn)行時(shí)實(shí)例。它將負(fù)責(zé)安裝模塊以及構(gòu)造類加載器(這些類加載器知道相應(yīng)模塊的內(nèi)容)。

然后它將查看新安裝的模塊的導(dǎo)入,并試圖找到匹配的導(dǎo)出。假設(shè)模塊 A 導(dǎo)出包 com.foo,模塊 B 要導(dǎo)入這個(gè)包。該框架將通知 B,它可以從模塊 A 獲得 com.foo 的類,這個(gè)稱為連線(wiring)。如果 B 的類加載器要加載類 com.foo.Bar,它將委派 A 的類加載器來做。對整個(gè)模塊的導(dǎo)入進(jìn)行連線的過程成為解析(resolution),當(dāng)所有導(dǎo)入都成功進(jìn)行連線后,那么這個(gè)組件(bundle)就被解析(resolved)了,這將令它完全可用。

一個(gè)預(yù)料之外的好處是我們可以動態(tài)地安裝、更新和卸載模塊。對于已經(jīng)解析的模塊,安裝新模塊對它們沒有影響,雖然這可能導(dǎo)致某些之前不可解析的模塊變得可解析。當(dāng)進(jìn)行卸載或更新時(shí),該框架非常清楚那些模塊受到影響,并且如果需要它將更改它們的狀態(tài)。為了能夠順利地進(jìn)行,還有一些額外的細(xì)節(jié)需要處理,比如,一個(gè)模塊在卸載或者取消解析之前正在做非常的事情,那么需要向它發(fā)送通知,以便讓它干凈利落地關(guān)閉。所以,OSGi 中的動態(tài)模塊并不是憑空出現(xiàn)的,這里并沒有什么神奇的功能,但 OSGi 至少讓它成為可能。

某些 OSGi 用戶更喜歡避免動態(tài)加載,這樣做沒有問題。這不是 OSGi 最重要的功能,但由于對于 OSGi 它是獨(dú)一無二的,英尺獲得了過多的關(guān)注。無論如何,沒有人強(qiáng)迫你使用它,即使從來不去利用動態(tài)性的優(yōu)勢,你仍然能夠從 OSGi 獲得許多好處。

版本控制

我們的模塊系統(tǒng)現(xiàn)在看起來非常不錯(cuò),但隨著時(shí)間的推移,模塊不可避免地會發(fā)生方便,對稱我們還不能處理。所以,我們還需要支持“版本控制”。

如何進(jìn)行版本控制?手洗,導(dǎo)出方可可進(jìn)行聲明,為其導(dǎo)出的包提供一些有用的信息:“這個(gè)是 API 版本 1.0.0”。導(dǎo)入方現(xiàn)在能夠只導(dǎo)入與其預(yù)期匹配并且經(jīng)過編譯/測試的版本,并且解決接受某個(gè)版本,比如版本 3.0.0。但是如果導(dǎo)入方想要版本 1.0.0 而只有版本 1.0.1 可用時(shí),應(yīng)該如何處理呢?一個(gè)稍高一點(diǎn)的版本看起來不會保護(hù)巨大的更改,所以導(dǎo)入方應(yīng)該可以接受版本 1.0.1。事實(shí)上,導(dǎo)入方應(yīng)為其可接受版本指定一個(gè)范圍,比如類似這樣的一個(gè)范圍:“版本 1.0.0 到 2.0.0 但不含 2.0.0”。對包進(jìn)行連線的流程可以支持這種范圍,如果導(dǎo)出的導(dǎo)出版本位于導(dǎo)入指定的范圍內(nèi),就將導(dǎo)入與該導(dǎo)出進(jìn)行連線。為了讓這個(gè)機(jī)制能夠正常使用,版本編號應(yīng)該是有順序的并且能夠進(jìn)行比較。

我們?nèi)绾未_定版本 1.0.1 相對于 1.0.0 沒有包含巨大的更改呢?很遺憾,我們無法確認(rèn)這種事情。對于版本編號,OSGi 強(qiáng)烈建議而不是強(qiáng)制使用以下語法規(guī)則:

1. 對于非向后兼容的更改,對主要(第一)部分進(jìn)行遞增。

2. 對于向后兼容的功能改善,對次要(中間)部分進(jìn)行遞增。

3. 對于未造成可見的功能更改的故障修復(fù),對最后部分進(jìn)行遞增。

如果所有人都遵守這些語法規(guī)則,那么指定導(dǎo)入范圍將是一件輕松簡單的事情。但現(xiàn)實(shí)世界并不是這么簡單,因此在試用如何外部庫時(shí),我們必須小心地處理兼容問題。

#p#

對模塊和元數(shù)據(jù)進(jìn)行打包

我們這個(gè)模塊系統(tǒng)需要一種方法來對模塊的內(nèi)容以及描述導(dǎo)入和導(dǎo)出的元數(shù)據(jù)進(jìn)行打包,將其包括到一個(gè)可部署的單元中。

Java 已經(jīng)有了標(biāo)準(zhǔn)的部署單元:JAR 文件。JAR 文件可能并不算一種非常成熟的模塊,但對于移動大塊的編譯代碼還是不錯(cuò)的,所以我們并不需要創(chuàng)建新的東西。那么現(xiàn)在的唯一問題是,將元數(shù)據(jù)(即導(dǎo)入和導(dǎo)出列表、版本等等)放在哪里?

看起來配置格式強(qiáng)烈地受到一時(shí)潮流的影響;如果我們是在 2000 年到 2006 年期間設(shè)計(jì)這個(gè)模塊系統(tǒng),我們很可能會選擇將元數(shù)據(jù)放到 JAR 文件下的某個(gè) XML 文件中這種方式能夠工作,但會遇到許多問題:對于流程,XML 文件并不是特別有效率,尤其是我們必須在 JAR 文件的某個(gè)地方才能找到它,而且在進(jìn)行語法分析之前還要對其進(jìn)行解壓。JAR 文件是一個(gè) ZIP 壓縮包,所以要找到某個(gè)特定文件,意味著必須讀取末端,找到用于跟蹤記錄的中央目錄,然后再跳轉(zhuǎn)到該目錄指定的分支上。換句話說,通常不得不讀取整個(gè) JAR 文件,對于需掃描大型目錄的工具,如果這個(gè)目錄下有很多模塊,這個(gè)過程將變得非常痛苦。比如,搜索某個(gè)可用的模塊,以滿足某個(gè)依賴關(guān)系。

另外 XML 幾乎不能人工編輯。為了正確的編輯這種文件,我們需要使用特定的編輯根據(jù)。

另一方面,如果是在 2006年之后設(shè)計(jì)這個(gè)模塊系統(tǒng),我們的第一個(gè)想法會是使用 Java 注釋(annotation)。如果使用適當(dāng),我非常喜歡注釋,將類似 @Export(version="1.0.0") 的東西放到 Java 源文件中的包聲明上,很明顯比在單獨(dú)文件中對其進(jìn)行維護(hù)要更有吸引力。不過,等一下……在包的每個(gè)源文件中,包聲明都會重復(fù)一次;難道我們也必須在所有源文件中加入注釋?

為了解決這個(gè)問題,Java 語言規(guī)范(JLS)建議使用一個(gè)名為“package-info.java” 特定源文件。但對于不屬于任何特定包的元數(shù)據(jù)怎么處理呢?比如導(dǎo)入包的列表或模塊本身的名稱和版本。Java 語言規(guī)范建議我們需要使用另一個(gè)特定源文件,使用類似“module-info.java”名稱。

到目前一切順利,現(xiàn)在讓我們看看如何對模塊進(jìn)行處理。

這些特定的源文件將在 package-info.class 和 module-info.class 中被編譯為字節(jié)碼,這樣就不需要打開 ZIP 壓縮的 JAR 文件來查看元數(shù)據(jù)了。所有模塊掃描工具都必須對整個(gè)模塊系統(tǒng)進(jìn)行讀取,而且也必須能夠處理字節(jié)碼。運(yùn)行時(shí)模塊系統(tǒng)自身也必須立即為模塊常見一個(gè)類加載器,用于讀取它的元數(shù)據(jù);結(jié)果是,如果我們能夠?qū)㈩惣虞d器的創(chuàng)建推遲到真正從模塊中加載某個(gè)類那個(gè)時(shí)刻,就可以消除大量的優(yōu)化工作。

已經(jīng)發(fā)生的事實(shí)是,OSGi 的設(shè)計(jì)的確是在 2000 年之前,所以它的確選擇了這些方案中的其中之一?;仡^看看 JAR 文件規(guī)范,答案自動浮現(xiàn):META-INF/MANIFEST.MF 是應(yīng)用程序?qū)S迷獢?shù)據(jù)的標(biāo)準(zhǔn)位置。在規(guī)范中這樣寫道:“忽略不可理解的屬性。這類屬性可能包含應(yīng)用程序所用的特定部署新型。”

MANIFEST.MF 專為提高流程的效率而設(shè)計(jì),而且它至少比 XML 更快。某種長度上,它是可讀的;至少與 XML 一樣可讀,很明顯比編譯的 Java 字節(jié)碼更具有可讀性。此外,標(biāo)準(zhǔn)的 jar 命令行工具通常將 MANIFEST.MF 放到 JAR 文件的第一項(xiàng)中,所以為了獲取元數(shù)據(jù),工具只需掃描文件中的前幾百個(gè)字節(jié)。

令人遺憾的是 MANIFEST.MF 并不完美。其一,由于規(guī)則要求每行不超過 72 個(gè)字節(jié),手工編寫相對困難,考慮到單個(gè) UTF-8 字符為 1-6 個(gè)字節(jié),這種規(guī)則會導(dǎo)致一些問題。一個(gè)更好的方式是利用另一格式的模板來生成 MANIFEST.MF。Bnd 工具是這樣的,Maven 的 Bundle Pulin 和 SpringSource 的 Bundlor 也是如此。

事實(shí)上,Bnd 甚至包括對于處理注釋的實(shí)驗(yàn)式的支持,比如 @Exporton 源代碼注釋。這樣我們將能夠獲得來自2個(gè)方面的好處:注釋的便利性,以及 MANIFEST.MF 的效率和運(yùn)行時(shí)可讀性/工具性。

后期綁定

模塊拼圖的最后一塊是部署到接口的后期綁定。我認(rèn)為這是模塊化一個(gè)至關(guān)重要的功能,雖然某些模塊系統(tǒng)對此完全忽略,或者認(rèn)為它不屬于模塊化這個(gè)范圍。

人們都知道,Java 中的接口會破壞功能提供者和使用方之間的耦合性。定義一個(gè)接口,其作用相對于使用方和提供方的合同,如何一方都不需直接獲得對方的信息,這樣我們就可以將它們放到不同的模塊中,而這些模塊之間不存在互相的依賴關(guān)系。而是每一個(gè)模塊對于接口存在依靠性,我們可以選擇囧這個(gè)接口放在第三個(gè)模塊中。唯一的問題是如何為使用方類提供接口實(shí)例,而最常見的答案是使用依賴注入(Dependency Injection,縮寫為DI),比如 Spring 或 Guice。

因此,為了完成我們的模塊系統(tǒng),只需使用現(xiàn)有的 DI 框架即可。畢竟我們追求的簡潔性,聲明一個(gè)問題不屬于我們處理的范圍,讓別人來解決,沒有什么比這個(gè)還簡單。但是,這種方式并不是非常令人滿意,因?yàn)?DI 框架事實(shí)上也需要知道模塊的邊界。傳統(tǒng)的 DI 使用方式的問題在于它會創(chuàng)建巨大的中心化配置,這個(gè)配置會對所有模塊產(chǎn)生影響。Peter Kriens 將這一問題稱為“全能類”(God Class)問題,在這個(gè)問題中,一個(gè)組件了解每個(gè)模塊的所有內(nèi)容,并要求所有模塊對其進(jìn)行綁定(作為一個(gè)無神論者,我認(rèn)為這個(gè)不可能做到,但即便你是有神論者,我肯定你也同意除了當(dāng)前已存在的上帝之外,我們不應(yīng)再去制造更多神)。這些全能類(或 XML 配置)非常脆弱,難于維護(hù),否定了將代碼劃分到模塊中所帶來的大多數(shù)好處。

我們應(yīng)該尋找一種去中心化的方法。不是讓全能類告訴我們?nèi)プ鍪裁矗覀兛梢约僭O(shè),每個(gè)模塊可能常見對象并將它們發(fā)布到某些地方,而其他模塊可以找到它們。我們將這些發(fā)布的對象成為“服務(wù)”,而它們發(fā)布的地方稱為“服務(wù)寄存器”。有關(guān)服務(wù),最重要的信息是它進(jìn)行部署的接口,所以我們可以將它作為最初的注冊碼?,F(xiàn)在,一個(gè)模塊,如果需要找到特定接口的實(shí)例,只需查詢寄存器,看看當(dāng)時(shí)提供哪些服務(wù)。寄存器本身仍然是位于任何模塊之外的中性化組件,但它不是全能的,而是更像一個(gè)共享黑板。

我們不需要放棄 DI,事實(shí)上它還非常有用:現(xiàn)有的 DI 框架可用來向其他服務(wù)中注入服務(wù),以及將某些對象發(fā)布為服務(wù)。DI 框架不在指揮整個(gè)系統(tǒng),相反它只是在單個(gè)模塊中的部署的應(yīng)用。我們甚至可以使用多個(gè) DI 框架,比如在同一個(gè)應(yīng)用程序中同時(shí)使用 Spring 和 Guice,當(dāng)想要集成第三方組件而這個(gè)組件使用的框架不是我們所選擇的那個(gè)時(shí),這是非常有用的。最后,服務(wù)寄存器為發(fā)布和查詢提供可編程的 API 接口,但只能用于低階工作,如部署一個(gè)新的 DI 框架。

總結(jié)

希望以上的泛泛而論能夠解釋為什么OSGi 會是現(xiàn)在這個(gè)樣子;從某種意義上說,這是一種技術(shù)的進(jìn)化。人們將會繼續(xù)抱怨OSGi 太復(fù)雜,但我認(rèn)為任何存在的復(fù)雜性都是必要的,用于解決我以上描述的難題。

當(dāng)然它并不是完美的。比如,版本控制還可以進(jìn)行改善,尤其是對于那些版本方案非常奇怪的第三方庫。為版本編號賦予一定的意義,仍然是正確的做法,但為了對版本和 API 兼容性進(jìn)行管理,還需要更多的協(xié)助工具。還有傳統(tǒng)的庫,仍然在危險(xiǎn)的假設(shè)一個(gè)扁平化系統(tǒng)類路徑的存在。按照我的觀點(diǎn),任何在類名稱中使用字符串或調(diào)用 Class.forName() 來獲得對象的庫都是錯(cuò)誤的,因?yàn)樗僭O(shè)所有類對于模塊都是可見的,而在任何類型的模塊化系統(tǒng)中,這都是不正確的。很遺憾,這些問題還不能在一夜之間完全解決,所以處理這些破損的庫,我們需要一些策略。不過處理這些問題需要一種不同的方式,從而對于其他人來說,不至于破壞模塊化的規(guī)則。

 

【編輯推薦】

  1. OSGi 4.2規(guī)范已發(fā)布 新特性介紹
  2. Java動態(tài)模塊化運(yùn)行原理與實(shí)踐
  3. OSGi依賴性管理:Bundle訪問域
  4. OSGi Bundle之Hello World
責(zé)任編輯:佚名 來源: 51CTO.com
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號