在OSGi,導入包是對導出包的補充。使用Import-Package聲明導入包,例如:
Import-Package: org.example.foo; version='[1,2)',
org.example.bar; version='[2.0,2.1)'
OSGi中bundle必須導入它所依賴的所有包,除了java.*開頭的包,如java.util。例如,如果你的bundle中的代碼依賴org.slf4j.Logger(并且你的bundle中實際上并不包含org.slf4j包),那么這個包必須被導入。同樣,如果你依賴于org.w3c.dom.Element,那么你必須導入org.w3c.dom。但是,如果你依賴java.math.BigInteger,你不需要導入java.math,因為Java.*包是由JVM的bootstrap類加載器加載的。
OSGi對于引用所有的bundle還有一個并行機制,稱為Require-Bundle,但在OSGi規(guī)范中它已經(jīng)過時了,現(xiàn)在它的存在只是為了支持很小的邊緣的案例。Import-Package最大的優(yōu)勢是它允許模塊在不影響下游模塊的前提下被重構(gòu)或被重命名。如圖1和2所示。
在圖1中,模塊A被重構(gòu)為兩個新的模塊,A和A',但模塊B不受該操作影響,因為它依賴于提供的軟件包。在圖2中,我們對模塊A執(zhí)行完全相同的重構(gòu),但現(xiàn)在B可能是壞的,因為它引用的包有可能不再存在于模塊A(在這里我們必須說“可能”,因為我們不知道模塊B使用模塊A哪些包——這正是問題所在!)。

圖1:通過 Imported Packages 重構(gòu)模塊

圖2:通過 Requires 重構(gòu)模塊
Import-Package語句手動寫是很繁瑣的,所以我們不這樣做。通過OSGi工具檢查依賴生成該語句,并將編譯類型內(nèi)置到bundle中。這是非??煽康?,比開發(fā)人員自己聲明運行時依賴更可靠。當然,開發(fā)人員仍然需要管理自己的編譯依賴,按照Maven正常的方式去做(或者你選擇的構(gòu)建工具)。如果編譯時把太多的依賴放在classpath下并不會有影響:可能發(fā)生的最壞情況是編譯失敗,這只會影響源頭的開發(fā)人員并且很容易修復。另一方面,太多的運行時依賴會降低模塊的可移植性,因為移植時所有這些依賴關系必須一起移植,而且可能與另一個模塊的依賴發(fā)生沖突。
這導致了OSGi和JPMS之間另一個關鍵的理論差異。在OSGi我們始終認為,編譯時依賴和運行時依賴可以并且經(jīng)常會不同。例如,它的標準做法是,有一套編譯時API和一套運行時API。此外,開發(fā)人員通常在我們所能兼容的最老的API版本上編譯,但會選擇可以找到的最新的版本來運行。甚至非OSGi開發(fā)人員也很熟悉這種方法:你通常會在準備支持的最低版本的JDK上編譯,卻鼓勵用戶在最高的版本(包含所有的安全補丁和增強性能)上運行。
另一方面JPMS采取了不同的策略。JPMS旨在實現(xiàn)“跨越所有階段的保真度”,這樣“模塊化系統(tǒng)應該…在編譯時、運行時以及在開發(fā)或部署的各個階段可以以完全相同的方式工作”(來自 JPMS需求 )。因此,依賴關系是在整個模塊運行時定義的,因為這就是它們在編譯時定義的方式。例如:
module B {
require A;
}
require語句和OSGi過時的Require-Bundle有相同的效果:模塊B可以訪問所有模塊A的導出包。因此,它也存在Require-Bundle同樣的問題:從模塊的聲明無法確定重構(gòu)模塊A的內(nèi)容是否是安全的,所以這樣做一般是不安全的。
我們發(fā)現(xiàn),依賴樹使用requirements而不是imports有更高程度的扇出:每個模塊攜帶比它真正需要的更多的依賴。這些問題是真實和重要的。尤其是Eclipse插件作者深受其害,因為歷史原因Eclipse bundle 傾向于使用requires而不是imports。非常不幸地,JPMS也遵循了這條路線。
有趣的是,雖然編譯/運行時的保真度是JPMS的根本目標,但最近的變化明顯減弱了保真度。目前的早期試用版本允許用static修飾符聲明requirement,這意味著在編譯時依賴是強制性的,但在運行時是可選的。相反,可以用dynamic修飾符聲明導出,這可以使導出包在編譯時無法訪問,但在運行時可以訪問(使用反射)。有了這些新特性可能會創(chuàng)建出成功編譯和鏈接,但在運行時拋出IllegalAccessError/Exception異常的模塊。
反射和服務
Java生態(tài)系統(tǒng)是巨大的,包含了用于各種目的的各種各樣的框架:從依賴注入到mocking框架、遠程調(diào)用、O/R映射等。從用戶提供的代碼來看,許多框架使用反射來實例化和管理對象。例如,Java持久化架構(gòu)(JPA),它是Java EE套件規(guī)范的一部分:作為對象關系映射,為了將domain類與從數(shù)據(jù)庫加載的記錄一一映射,必須從用戶代碼加載和實例化domain類。另一個例子是,Spring框架加載和實例化“bean”類實現(xiàn)接口。