而與此同時,OSGi用了16年時間不斷發(fā)展和完善。OSGi是應(yīng)用程序模塊化的標(biāo)準(zhǔn):由于它不是Java平臺的一部分,它不能影響平臺本身的模塊化。但是,許多應(yīng)用已經(jīng)受益于它提供的高于JVM的模塊化模型。
高層比較
JPMS和OSGi之間有很多小的差異,但是有一個很大的不同,就是隔離的實(shí)現(xiàn)。
隔離是模塊化系統(tǒng)最基本的特征。每個模塊必須有一些保護(hù)措施防止運(yùn)行在同一應(yīng)用程序中其他模塊的干擾。隔離是一個連續(xù)的而不是二進(jìn)制的概念:無論OSGi還是JPMS都需要做一些事情來避免那些表現(xiàn)不好的模塊的影響,這些模塊占用了JVM中所有可用的內(nèi)存,運(yùn)行了數(shù)千個線程或者讓CPU處于繁忙的循環(huán)。如果一個模塊可以作為操作系統(tǒng)上獨(dú)立的進(jìn)程運(yùn)行,是可以提供這類保護(hù)的,但即使是這樣,它也是不完美的;有人仍然可以使操作系統(tǒng)崩潰或者擦除磁盤。
OSGi和JPMS都提供了代碼級隔離,這意味著一個模塊不能訪問另一個模塊的內(nèi)部類型,除非該模塊有明確的許可。
OSGi通過類加載器實(shí)現(xiàn)隔離。每個模塊(或者在OSGi術(shù)語中稱為“bundle”)有一個類加載器,它知道如何在bundle中加載類型。它也可以將類加載請求委托給它所依賴的其他bundle的加載器。該系統(tǒng)是高度優(yōu)化的,例如,OSGi不會為一個bundle創(chuàng)建一個類加載器直到最后一刻,而且事實(shí)上每個加載器會處理一個更小的類型,這樣每個類型可以加載得更快。
這個系統(tǒng)最大的優(yōu)勢是,bundle可以包含重疊的包和類型,而且不會相互干擾。實(shí)際的結(jié)果是,可能某些包和庫有多個版本同時運(yùn)行在相同的JVM。在處理像Maven這樣的構(gòu)建工具帶來的復(fù)雜的傳遞依賴圖時,這是個福音。在許多企業(yè),Java應(yīng)用程序幾乎不可能有這樣的一套依賴,該依賴中每個庫只包含一個版本。
例如,我們來看看 JitWatch 庫 1 。JitWatch依賴于 slf4j-api 1.7.7 和 logback-classic 1.1.2,但是logback-classic 1.1.2依賴于slf4j-api 1.7.6,與JitWatch直接的依賴有沖突。JitWatch也傳遞地依賴于 jansi 1.6和1.9版本,如果包含測試范圍的依賴,我們會有另一個slf4j-api的版本1.6。這種混亂是很常見的,傳統(tǒng)的Java中沒有真正的解決方案,只能逐步在依賴樹中添加“excludes”直到奇跡般地得到一套可以運(yùn)行的依賴庫。不幸的是對于這個問題JPMS也沒有答案,我們很快就會看到。
使用類加載器進(jìn)行隔離確實(shí)有一個缺點(diǎn):它打破了每一個類型最多可以在一個位置找到的假設(shè)。這是模塊化的一個自然結(jié)果。如果一個模塊可以不受其他模塊的干擾使用自己的類型,那么不可避免地一個單一類型的名稱可能會在多個模塊中發(fā)現(xiàn)。遺憾的是,這造成了一個問題,因?yàn)楹芏啾A舻腏ava代碼不是用模塊化的思想編寫的。特別是,調(diào)用Class.forName(String)通過名字查詢類型時,在真正模塊化的環(huán)境中不是總能得到正確的結(jié)果,因?yàn)橛卸鄠€可能的返回類型。
正是由于這個缺點(diǎn),不能使用OSGi模塊化JDK本身。JDK的許多地方都有一個隱含的假設(shè),任何JDK類型可以從JDK的任何其他部分加載,所以很多事情在OSGi下會被打破,比如模型。為了解決這個問題,也為了減少使用Class.forName代碼的遷移,JPMS選擇在隔離時不使用類加載器。當(dāng)你在“modulepath”使用一組模塊來啟動應(yīng)用程序時,所有這些模塊將由相同的類加載器加載。相反,JPMS引入新的訪問規(guī)則實(shí)現(xiàn)隔離。
OSGi的隔離屏障是 可見的 。在OSGi,我們不能加載一個模塊的內(nèi)部類,因?yàn)樗鼈兪遣豢梢姷摹R簿褪钦f,自己模塊的類加載器只能看到自己模塊內(nèi)部的類型以及從其他模塊明確導(dǎo)入的類型。如果我試圖從其他的模塊中加載一個內(nèi)部類,我的類加載器是看不到該類型的。就好像是根本不存在的類型。如果試圖繼續(xù)加載該類,就會得到NoClassDefFoundError或者ClassNotFoundException的異常。
在JPMS,每一個類型對于任何其他類型都是可見的,因?yàn)樗麄兇嬖谟谕粋€類加載器。但是,JPMS增加了輔助檢查以確定加載類有權(quán)訪問它試圖加載的類型。其他模塊的內(nèi)部類型實(shí)際上是private的,即使它們被聲明為public。如果我們試圖繼續(xù)加載它,那么我們會得到IllegalAccessError或者IllegalAccessException的異常。如果我們試圖加載private的或者另一個包的默認(rèn)訪問類型也會得到相同的錯誤,而且在這個類型上調(diào)用setAccessible也是無用的。這改變了Java中public修飾符的語義,以前它是普遍可訪問的,現(xiàn)在只可在一個模塊和它的require對象中訪問。