JPMS方法的缺點(diǎn)是,它不可能有重疊內(nèi)容的模塊。也就是說,如果兩個(gè)模塊都包含一個(gè)私有(非導(dǎo)出)的包org.example.util,這些模塊不能同時(shí)在模塊路徑上被加載——它會導(dǎo)致layerinstantiationexception異常。通過應(yīng)用程序?qū)嵗惣虞d器可能會解決此限制——但這正是OSGi已經(jīng)為我們做的!
再次強(qiáng)調(diào),完全是通過設(shè)計(jì)允許JPMS模塊化JDK的內(nèi)部。但結(jié)果是,你會有不能完全一起工作的模塊,因?yàn)樗鼈儍?nèi)部的實(shí)現(xiàn)細(xì)節(jié)有沖突。
復(fù)雜性
對于OSGi最常見的抱怨之一是,它給開發(fā)人員增加了復(fù)雜性。這有一定的道理,但是有這些抱怨的人都搞錯(cuò)了復(fù)雜性的原因。
模塊化并不是一個(gè)在應(yīng)用程序發(fā)布前灑在上面的神奇的塵埃。它是在設(shè)計(jì)和開發(fā)各個(gè)階段必須遵循的準(zhǔn)則。一些開發(fā)人員已經(jīng)意識到了OSGi帶來的巨大收益,他們在早期就開始使用OSGi并且在編寫一行代碼之前會運(yùn)用模塊化思想,他們發(fā)現(xiàn)OSGi實(shí)際上是非常簡單的,尤其是在使用現(xiàn)代化OSGi工具鏈時(shí),它自動生成元數(shù)據(jù)并且在運(yùn)行前做了大量的一致性檢查捕獲異常。
而另一方面,開發(fā)人員試圖把OSGi引入現(xiàn)有的大型代碼庫時(shí)遭遇了困難,因?yàn)檫@些代碼很少能夠模塊化以便遷移。沒有執(zhí)行模塊化的準(zhǔn)則,很容易走捷徑,打破封裝性。BEA WebLogic的一個(gè)開發(fā)人員告訴我,在Oracle收購BEA之前:“我們以為我們是模塊化的,直到我們開始使用OSGi。”
除了非模塊化的應(yīng)用程序,OSGi的采用也受到非模塊化庫的阻礙。一些流行的Java庫中類加載和全局可見性的假設(shè)在模塊化結(jié)構(gòu)中被打破了。OSGi做了大量工作,讓它可以使用這些庫,這是OSGi規(guī)范明顯復(fù)雜性的來源。我們需要有一定的復(fù)雜性來處理混亂的、復(fù)雜的現(xiàn)實(shí)世界。
我們很快就會看到,JPMS也會有同樣的問題——可能更是如此。如果你的組織曾試圖采用OSGi,卻因?yàn)檫w移工作量過大而放棄了,那么當(dāng)你要遷移到JPMS時(shí),至少應(yīng)該預(yù)期會有同樣多的工作量。只需要看看Oracle在模塊化JDK時(shí)的經(jīng)驗(yàn):有很多的工作要做,導(dǎo)致Jigsaw從Java 7延遲到Java 8,再到Java 9,甚至Java 9已經(jīng)延遲了一年(到目前為止)。
Jigsaw項(xiàng)目開始于一個(gè)目標(biāo)就是越來越簡單,但JPMS規(guī)范大大增加了復(fù)雜性:與類裝載器模塊的相互作用;分層結(jié)構(gòu)和配置;re-exporting要求;弱模塊;靜態(tài)要求;qualified導(dǎo)出;dynamic導(dǎo)出;跨層繼承的可讀性;多模塊JAR文件;自動模塊;未命名的模塊等等,已經(jīng)非常清晰所有的這些功能都會作為需求添加進(jìn)來。類似的過程也發(fā)生在OSGi,只是它有16年領(lǐng)先的優(yōu)勢。
依賴:包vs全部模塊
隔離只是模塊化的一個(gè)難題:模塊仍然需要協(xié)同工作和通信。模塊之間建立“墻”后,它們需要以一個(gè)可控的方式重新連接。一個(gè)模塊化系統(tǒng)必須定義模塊訪問其他模塊功能的方式。可以通過在類型級別上靜態(tài)地或者動態(tài)地使用對象來實(shí)現(xiàn)。
靜態(tài)依賴在編譯時(shí)就是已知的和可控的。如果一個(gè)類型在一個(gè)模塊的邊界引用另一個(gè)類型,那么模塊系統(tǒng)需要提供一個(gè)方法讓該類型可見并且可訪問。有兩種方式:模塊需要有選擇性地暴露一些內(nèi)部類型,模塊需要指定自己使用了其他模塊的哪些類型。
導(dǎo)出(Exports)
在OSGi和JPMS中,類型暴露在Java包級別就完成了。在OSGi使用Export-Package語句聲明指定名稱的包對其他bundle是可見的。它看起來像這樣:
Export-Package: org.example.foo; version=1.0.1,
org.example.bar; version=2.1.0
該聲明在META-INF/ MANIFEST.MF文件中。OSGi初期大多數(shù)開發(fā)人員會手工指定這樣的聲明;但我們越來越傾向于使用構(gòu)建工具生成?,F(xiàn)在最流行的方式是在Java源代碼中添加注解,Java 5中引入了package-info.java文件允許包級別的注解和文檔,所以O(shè)SGi中可以如下編寫:
@org.osgi.annotation.versioning.Version("1.0.1")
package org.example.foo;
這是一個(gè)有用的模式,因?yàn)橄胍獙?dǎo)出一個(gè)包時(shí)可以直接在該包中表示。版本也可以在這里顯示,包的內(nèi)容變化時(shí)在附近就可以更新 2 。
JPMS中包的導(dǎo)出在module-info.java文件中,如下:
module A {
exports org.example.foo;
exports org.example.bar;
}
請注意,如果缺少version,JPMS中模塊和包都不能被版本化;稍后我們會討論這一點(diǎn)。
Imports/Requires
雖然在導(dǎo)出時(shí)OSGi和JPMS是類似的,但是導(dǎo)入或?qū)ζ渌K的依賴卻有顯著的差異。