Java 9 中一個重要的新特性就是模塊化灿意。它的實現(xiàn)機(jī)制是什么那估灿?它和已有的模塊框架OSGi有什么差異那壤短?為了回答這些問題祷膳,本人在網(wǎng)上找到了一篇比較好的介紹文章,為了加深理解以政,對文章進(jìn)行了翻譯鞭执。由于原文分為2個部分司顿,所以翻譯對應(yīng)也分為2篇:
1)《Java 9,OSGi和模塊化的未來(1)》是對《Java 9, OSGi and the Future of Modularity (Part 1)》的翻譯兄纺,文章日期為2016年9月22日大溜。介紹的內(nèi)容包括:背景、高層次比較估脆、復(fù)雜性钦奋、依賴粒度對比、模塊導(dǎo)出對比疙赠、模塊導(dǎo)入對比付材、反射和服務(wù)。
2)《Java 9圃阳,OSGi和模塊化的未來(2)》是對《Java 9, OSGi and the Future of Modularity (Part 2)》的翻譯厌衔,文章日期為2016年10月4日。介紹的內(nèi)容包括:動態(tài)性捍岳、二者協(xié)同工作富寿、未來發(fā)展、結(jié)論锣夹。
本文是對原文第二部分的翻譯页徐。
引言
關(guān)鍵點
- Java 9 在2017年發(fā)布,其中一個重要的特性就是新的模塊化系統(tǒng)银萍,被稱作Java平臺模塊系統(tǒng)(Java Platform Module System变勇,JPMS)。本文將介紹它與Java已有的模塊標(biāo)準(zhǔn)化機(jī)制OSGi的關(guān)系贴唇,以及對OSGi的影響搀绣。
- 自1.0版本以來Java平臺已經(jīng)增長了20倍飞袋,整個平臺存在著模塊化的需求。為了解決這個問題豌熄,進(jìn)行了很多不成功的嘗試授嘀。與之相對的是,OSGi已經(jīng)提供了16年的應(yīng)用模塊化服務(wù)锣险。
- OSGi和JPMS在實現(xiàn)細(xì)節(jié)上差別很大蹄皱。如果將JPMS作為模塊化的一般解決方案,會存在一些重大的缺陷芯肤,并且缺少OSGi的一些特性巷折。
- JMPS的目標(biāo)是比OSGi更簡單和更容易,但是對一個非模塊化的產(chǎn)品進(jìn)行模塊化設(shè)計本身就是一件很復(fù)雜的事情崖咨,JMPS看起來好像還沒有實現(xiàn)這個目標(biāo)锻拘。
- JMPS在對Java平臺本身的模塊化方面做得非常好,這意味著我們可以在運行時只加載和任務(wù)相關(guān)的Java平臺組件击蹲。對于應(yīng)用模塊化署拟,OSGi有很多的優(yōu)點。我們已經(jīng)證明這兩者可以很好地結(jié)合在一起歌豺。
這篇文章是“Java 9, OSGi and the Future of Modularity”文章系列的第二部分推穷。第一部分請查看《Java 9, OSGi and the Future of Modularity (Part 1)》。
本文將繼續(xù)深入了解OSGi和JPMS(Java Platform Module System)类咧,其中JPMS會作為Java 9的一個組成部分馒铃。在上篇文章中,我們在一個較高的層次比較了這兩個模塊化系統(tǒng)痕惋,討論了它們是如何解決模塊間的隔離性的区宇。我們研究了依賴關(guān)系是如何建立的,并且我們探討了一些關(guān)于反射的問題值戳。本文作為第二個部分议谷,將探討版本管理、模塊動態(tài)加載以及二者潛在的協(xié)同工作可能性述寡。
版本管理
版本管理是所有軟件交付的一個關(guān)鍵點柿隙。API和實現(xiàn)都會改變,所以無論何時我們依賴它們鲫凶,我們都隱含地依賴于它們在某個時間點的存在。任何模塊系統(tǒng)必須能夠處理這個現(xiàn)實衩辟,通常用顯式的版本來指明依賴關(guān)系螟炫。
然而并非所有的改變都具有同樣的破壞性。如果我們使用了版本為1.0.0的模塊的來構(gòu)建和測試我們的軟件艺晴,那么當(dāng)我們部署版本1.0.1或1.0.5時昼钻,我們有可能可以繼續(xù)工作掸屡,但是如果部署的是版本2.0.0或版本5.2.10,那么不能工作的可能性較大然评。這表明模塊系統(tǒng)需要了解并支持兼容性范圍仅财。
OSGi一直支持這些概念。Bundle在導(dǎo)出包時標(biāo)注了版本號碗淌。在導(dǎo)入包的時候指明了版本范圍盏求,通常使用閉開區(qū)間來表示這個范圍,例如“[1.0.0, 2.0.0)” 表示版本介于1.0.0和2.0.0之間亿眠,不包括2.0.0碎罚。OSGi使用語義版本控制,與流行的語義版本規(guī)范完全一致(盡管OSGi早于那些規(guī)范)纳像。大致來說荆烈,版本號的第一段是主版本號,表示功能和API的重大變化竟趾,第二段是次版本號憔购,表示功能的增強(qiáng),第三段表示增加了一些補(bǔ)丁岔帽。
OSGi的開發(fā)者不需要推理或顯式地聲明這些版本范圍玫鸟。和 import 一樣,版本范圍可以在構(gòu)建時期根據(jù)依賴情況自動構(gòu)建山卦。例如鞋邑,如果我們僅需要使用一個API包,那么我們可以提供一個比較寬的區(qū)間账蓉,如“[1.0.0, 2.0.0)”枚碗,這個區(qū)間包含了最小和最大版本號之間的所有版本。但是如果我們是提供一個服務(wù)接口的實現(xiàn)铸本,那么我們需要使用較窄的區(qū)間來導(dǎo)入依賴肮雨,例如“[1.0.0, 1.1.0)”,意味著包含1.0.0和這個區(qū)間內(nèi)的箱玷,但是不包含1.1.0怨规。這里的不同點在于,一個支持1.0.0的服務(wù)提供者無法支持1.1.0锡足,因為增加的版本號表明有新的功能被添加了進(jìn)來波丰,而提供者無法自動提供新的功能。另一方面舶得,一個服務(wù)消費者掰烟,可以方便地使用1.1.0和1.2.0等版本號,因為它可以忽略新增加的功能。
除了生成導(dǎo)入范圍外纫骑,OSGi構(gòu)建工具(bnd)還可以幫助獲得導(dǎo)出包的正確版本蝎亚。版本號是包的一個屬性,它可以直接寫在包中(通過在 package-info.java
文件中添加 @Version
注解)先馆。當(dāng)包中的內(nèi)容改變的時候发框,一件很重要的事就是修改這個版本號,例如:當(dāng)我們在服務(wù)接口中增加了一個方法時煤墙,我們需要將版本號從1.0.0增加到1.1.0梅惯。構(gòu)建工具檢查版本號是否準(zhǔn)確地反映了所做更改的性質(zhì)。例如番捂,當(dāng)我們添加了方法而忘記修改版本號的時候个唧,構(gòu)建將會失敗,或者我們只是將版本號增加為1.0.1的時候也會如此设预。
最終徙歼,OSGi具有這樣的靈活性:可以在單個應(yīng)用程序中同時部署模塊的多個版本。這種情況會在這樣的場景出現(xiàn):通過傳遞依賴我們引用了一些通用庫的不同版本鳖枕,如 slf4j 或 Guava魄梯。還有一些限制是:我們不能直接在單個模塊中導(dǎo)入包的多個版本,但是在真正需要的時候宾符,具備這樣的能力還是很有用的酿秸。
與之相對的是,JPMS對版本控制基本沒有任何支持魏烫。
在 module-info.java 中沒有辦法為一個模塊指明版本辣苏。在 module-info.class 文件中存在一個版本屬性,但是它并不是來自于 Java 代碼哄褒,目前還不清楚它在實際中該如何使用稀蟋。依賴聲明同樣沒有版本:JPMS模塊只能通過名字來 require 其它模塊,無法指明版本呐赡,當(dāng)然更無法指明版本的范圍退客。這些特性需要由外部工具添加,但這種方法是受限的链嘀,因為 module-info.java 源文件是不可擴(kuò)展的萌狂,并且在該文件內(nèi)也無法使用 Java 注解。
JPMS的需求規(guī)定:在運行時選擇兼容的版本不在支持范圍內(nèi)怀泊。這意味著其它工具將不得不做這項工作茫藏,但沒有合適的元數(shù)據(jù)它們無法完成這項工作。將版本元數(shù)據(jù)存儲在與基本模塊元數(shù)據(jù)相同的描述符中是很自然的霹琼,但這是不可能的刷允。
正如我們提到的一樣冤留,JPMS中禁止在運行時同時包含一個模塊的多個版本碧囊。此外树灶,它禁止多個模塊導(dǎo)出相同的包,甚至禁止重復(fù)的私有包糯而。因此天通,無論其它工具使用什么方法來構(gòu)建一個有效的模塊集合,都需要找到一個解決轉(zhuǎn)遞依賴沖突的方法熄驼。在很多案例中像寒,一個簡單的“方法”是:在多個模塊共同存在的場景下,禁止一些模塊的使用瓜贾。
動態(tài)性
OSGi基于類加載的隔離實現(xiàn)方案有一個不錯的用處:可以支持模塊在運行時被動態(tài)加載诺祸、更新和卸載。這在企業(yè)環(huán)境中似乎并不重要祭芦,事實上大多數(shù)OSGi企業(yè)在部署中都不會使用動態(tài)更新筷笨。的確,OSGi沒有要求你一定要使用動態(tài)更新龟劲!
但是動態(tài)更新在其它環(huán)境中是非常有用的胃夏,比如物聯(lián)網(wǎng)。在數(shù)千臺甚至數(shù)百萬臺設(shè)備上昌跌,通過緩慢或斷斷續(xù)續(xù)的網(wǎng)絡(luò)更新軟件是一件讓人頭痛的事情仰禀。OSGi是少數(shù)技術(shù)之一,可以在任何平臺上直接支持使用最少數(shù)據(jù)量進(jìn)行即時更新:我們只需要發(fā)送實際更改的模塊蚕愤。
最初在2000年答恶,電信運營商在家庭網(wǎng)關(guān)和路由器上使用OSGi構(gòu)建智能家居解決方案的主要原因之一是:能夠在不進(jìn)行固件更新的情況下管理軟件。固件更新不吸引人的原因有很多:下載固件更新通常需要下載兆字節(jié)的軟件到潛在的數(shù)百萬設(shè)備上萍诱;固件是針對設(shè)備設(shè)計的悬嗓,因此你可能會有許多不同的更新來創(chuàng)建和管理部署;測試固件更新需要大量的砂沛、耗時的烫扼、昂貴的壓力測試,每次都要對每個設(shè)備執(zhí)行這個測試碍庵。OSGi顯著簡化了這個過程映企;更新可以應(yīng)用在模塊中,并快速安裝在運行的網(wǎng)關(guān)和路由器上静浴,不需要重啟堰氓;同樣的模塊可以在所有設(shè)備上使用(通常是底層硬件設(shè)備的抽象),重要的是苹享,單元測試可以在更小的軟件集上執(zhí)行双絮,節(jié)省大量的時間浴麻、精力和金錢。一個具體的例子是 Qivicon囤攀,它是一個由德國電信公司成立的行業(yè)聯(lián)盟软免。Qivicon 提供家庭網(wǎng)關(guān),包括:基于OSGi的軟件棧焚挠,后端基礎(chǔ)設(shè)施膏萧、應(yīng)用程序開發(fā)工具以及維護(hù)和支持。通過使用OSGi來搭建基礎(chǔ)生態(tài)系統(tǒng)的方法蝌衔,使得Qivicon的合作伙伴能更快地將智能家居產(chǎn)品推向市場榛泛。
Qivicon 合作伙伴不斷整合新設(shè)備和開發(fā)新的創(chuàng)新增值服務(wù)。這需要復(fù)雜的設(shè)備管理和軟件供應(yīng)能力噩斟,以確保針對特定設(shè)備平臺的軟件組件的依賴性和兼容性管理曹锨。通過利用現(xiàn)有的工業(yè)標(biāo)準(zhǔn)(如 TR-069 和 OMA-DM),這些功能已經(jīng)被寫入OSGi標(biāo)準(zhǔn)規(guī)范中了剃允。
此外沛简,動態(tài)行為不僅僅體現(xiàn)在軟件更新上。
OSGi服務(wù)注冊表本質(zhì)上是動態(tài)的硅急。服務(wù)可以注冊和卸載覆享,與之綁定的相關(guān)組件也會在收到事件通知。服務(wù)允許真實世界不斷變化的狀態(tài)被表示和通知营袜。即使在相對穩(wěn)定的企業(yè)應(yīng)用領(lǐng)域撒顿,這也是相關(guān)的。例如荚板,OSGi服務(wù)可以表示外部數(shù)據(jù)輸入是否可用凤壁,或者是REST服務(wù)的負(fù)載均衡IP地址,甚至是金融市場的開放時間跪另。每個消費服務(wù)的組件可以決定服務(wù)不可用時的反應(yīng)行為:可以繼續(xù)拧抖,或者注銷自身提供的服務(wù)。因此免绿,狀態(tài)的變化被可靠地傳播到任何有影響的地方唧席。
協(xié)同工作與未來發(fā)展
JPMS會在Java 9中發(fā)布。目前有非常多的應(yīng)用程序是用OSGi寫的嘲驾,同時還有很多代碼正在被書寫淌哟。這些代碼是安全的嗎?它們是否需要在JPMS平臺上重新被改寫辽故?
首先需要明確地是:OSGi應(yīng)用可以不用修改代碼繼續(xù)在Java 9上運行徒仓,因為它沒有使用不被支持和內(nèi)部的Java API。對于其他的Java應(yīng)用代碼也是如此誊垢。OSGi只使用了被支持的Java API掉弛,并且Oracle承諾Java 9不會使這些應(yīng)用奔潰症见。你在使用Java 9時遇到的問題可能是來自一些使用了JDK內(nèi)部類型的類庫,因為這些類型在Java 9中無法被訪問殃饿,除非通過特殊的配置標(biāo)志進(jìn)行訪問谋作。OSGi的用戶將能更好地應(yīng)對這個改變,因為它們的模塊具有顯式的依賴列表壁晒。通常瓷们,Java應(yīng)用會在類路徑上放置多個Jar包,與它們相比秒咐,基于OSGi的應(yīng)用對于平臺依賴的范圍會更加清晰。
一個最基本兼容模式是碘裕,OSGi框架和bundle會存在于JPMS的“未命名”模塊中携取。OSGi還會繼續(xù)提供所有已經(jīng)存在的隔離性特性,包括它功能強(qiáng)大的服務(wù)注冊和動態(tài)加載能力帮孔。你對OSGi的投資是安全的雷滋,而且OSGi仍然是新項目的一個好選擇。
但我們希望能比這做得更好文兢。當(dāng)OSGi運行在已經(jīng)模塊了的Java 9平臺上時晤斩,我們應(yīng)該能夠充分利用平臺中的模塊。例如姆坚,可以為一個OSGi bundle聲明它依賴的平臺模塊澳泵,這意味著一個OSGi bundle可以直接依賴一個JPMS模塊。OSGi框架應(yīng)該關(guān)注那些運行時的依賴兼呵,并且工具應(yīng)該能夠根據(jù)這些依賴準(zhǔn)備運行時環(huán)境兔辅。
此時事情已經(jīng)看起來很不錯了。在我2015年11月的一篇博客中击喂,我對這個概念進(jìn)行了驗證性描述维苔,在JPMS上構(gòu)建并運行了OSGi程序。我詳細(xì)介紹了如何讓OSGi bundle在基礎(chǔ)平臺的JPMS模塊中聲明依賴懂昂。我展示了OSGi是如何拒絕這樣的一個bundle:它依賴的JPMS模塊不在平臺中介时。我沒有在運行時提供一個工具原型,但是通過所有描述的部分已經(jīng)可以構(gòu)建這樣的一個工具了凌彬。
圖3描繪了未來兩個機(jī)制可以如何一起工作沸柔。我們可以看到:Bundle A 導(dǎo)入了包 javax.activation
,這個包來自JPMS中導(dǎo)出的 javax.activation
模塊饿序。交互層知道平臺中包含了這個模塊勉失,會運行OSGi來處理它。當(dāng)Bundle A遷移到Java 9上時原探,并不需要做任何改變乱凿。Bundle B使用了 java.net.http
包顽素,這個包來自JPMS的 java.httpclient
模塊,但是它無法在OSGi中Import-Package部分進(jìn)行聲明徒蟆,因為它是以 java.
開頭的(需要注意的是胁出,所有的 bundle 和 moudle 都隱含地依賴 java.base
)。
因此段审,我們提出了一個新的OSGi頭部全蝶,稱作“Require-PlatformModule”,它用來表示對JPMS中模塊的依賴寺枉。這樣當(dāng)Bundle B沒有包含 java.httpclient module
模塊的時候抑淫,OSGi框架能夠在Bundle B中“快速失敗”。這同時還可以使得工具能夠為應(yīng)用構(gòu)建一個完整的運行時環(huán)境姥闪,這個環(huán)境是JPMS中的 modules 和OSGi中的 bundles 的最小集合始苇。
再次聲明,這個工作是一個非官方的概念證明筐喳。最終催式,OSGi如何和JPMS進(jìn)行交互將由規(guī)范來處理。
結(jié)論
JPMS避归,通過Jigsaw原型項目荣月,對Java平臺本身的模塊化工作是非常出色的。通過這個工作梳毙,可以構(gòu)建更小的運行時環(huán)境哺窄,只需要包括Java平臺中任務(wù)依賴的部分。
然而作為應(yīng)用的模塊化規(guī)范顿天,JPMS存在嚴(yán)重的缺陷堂氯。缺少對版本控制的支持是一個令人震驚的遺漏,而且在沒有外部工具提供額外元數(shù)據(jù)的情況下牌废,很難在構(gòu)建應(yīng)用程序的過程中實現(xiàn)版本管理咽白。整個模塊(whole-module)依賴聲明形式將會導(dǎo)致獲得更多的傳遞依賴項,這將削弱它們遷移到更小平臺的能力鸟缕。無法通過反射來獲得未導(dǎo)出模塊中的類型晶框,這樣將會無法使用Java生態(tài)系統(tǒng)中已有的一些框架。
這些設(shè)計對于JDK本身來說可能是合適的:這增加了平臺的健壯性和安全性懂从,同時沒有破壞所有Java應(yīng)用的向后兼容性授段。但是它們的做法只是一種折中,對于應(yīng)用的模塊化來說番甩,這個設(shè)計很不友好侵贵。
所以O(shè)SGi的未來看起來是光明的:通過將OSGi和整理過的模塊化Java平臺相結(jié)合,我們可以使這兩個生態(tài)都獲得更好的發(fā)展缘薛。OSGi已經(jīng)具有了16年的經(jīng)驗窍育,并且遇到和解決了JPMS還沒有考慮過的問題卡睦。OSGi的開發(fā)工具和運行時生態(tài)是廣泛和深入的。在一個長期有效漱抓、獨立的標(biāo)準(zhǔn)機(jī)構(gòu)的支持下表锻,它的未來是得到保障的。你還在等什么那乞娄?