Java 9將在明年發(fā)布,,一個(gè)標(biāo)志性的特性是新的模塊化系統(tǒng):Java平臺(tái)模塊化系統(tǒng)(JPMS),。雖然JPMS的細(xì)節(jié)還沒(méi)有完全確定,我們已經(jīng)了解了很多有關(guān)它方向性的內(nèi)容,。 Java已經(jīng)有一個(gè)預(yù)先存在的模塊化系統(tǒng),,自2000年以來(lái)一直以各種形式存在。它就是被稱為OSGi的模塊化系統(tǒng),,是一個(gè)獨(dú)立于供應(yīng)商的行業(yè)標(biāo)準(zhǔn),。它由OSGi聯(lián)盟發(fā)布,由領(lǐng)先的軟件供應(yīng)商,、電信公司和其他組織(包括Adobe,、博世、華為,、IBM,、Liferay,、NTT、Oracle,、Paremus 以及Software AG)組成,。它推進(jìn)了幾乎所有的Java EE應(yīng)用服務(wù)器、最流行的IDE,、Web應(yīng)用程序(像eBay,、Salesforce.com和Liferay),并用于政府和軍隊(duì),,如美國(guó)空軍和聯(lián)邦航空管理局,。 OSGi是為物聯(lián)網(wǎng)提供的——OSGi一開始是專為嵌入式設(shè)備設(shè)計(jì)的,那是在很多年前,,當(dāng)時(shí)內(nèi)存和CPU資源明顯受到局限?,F(xiàn)在設(shè)備有了更多的能力。這提供了構(gòu)建復(fù)雜應(yīng)用程序和解決方案的機(jī)會(huì),,并催生了蓬勃發(fā)展的生態(tài)系統(tǒng),,在這個(gè)生態(tài)系統(tǒng)中組織和個(gè)人貢獻(xiàn)的軟件和硬件元素可以添加到整體解決方案中。這樣的生態(tài)系統(tǒng)在市場(chǎng)上很廣泛,,包括互聯(lián)家庭,、車聯(lián)網(wǎng)、智能城市和工業(yè)4.0(IIoT),。網(wǎng)關(guān)通常用于傳感器和設(shè)備之間相互連接,,并連接到后端系統(tǒng)。應(yīng)用程序和服務(wù)可以在本地網(wǎng)關(guān)和/或云上運(yùn)行,。 OSGi還提供多種規(guī)范啟用構(gòu)建開放的物聯(lián)網(wǎng)生態(tài)系統(tǒng)的基本特性,。這些特性包括設(shè)備管理、軟件配置以及設(shè)備抽象,,即從底層通信協(xié)議歸納設(shè)備,。在今天,像AT&T,、博世,、NTT、德國(guó)電信,、美國(guó)通用電氣,、日立、美諾,、施耐德電氣等公司都受益于采用OSGi構(gòu)建物聯(lián)網(wǎng)的解決方案,并且做了很多年,。目前已經(jīng)有上百萬(wàn)的設(shè)備連接采用OSGi和物聯(lián)網(wǎng),。 當(dāng)然,,OSGi的用戶都很好奇Java 9中新的模塊化系統(tǒng)在短期和長(zhǎng)期將會(huì)如何影響OSGi。 Java生態(tài)系統(tǒng)中很快會(huì)出現(xiàn)兩個(gè)模塊化系統(tǒng),,這有技術(shù),、政治和商業(yè)的原因。本文中,,我們避開政治原因,,從技術(shù)的角度對(duì)兩者進(jìn)行比較。我們總結(jié)了JPMS和OSGi如何協(xié)同工作,,思考它們各自的領(lǐng)域是什么以及在嶄新的世界中存在什么樣的機(jī)遇,。 請(qǐng)注意,本文中,,我們使用的信息在2016年8月已公開發(fā)布,。在該規(guī)范確定之前一些細(xì)節(jié)可能會(huì)改變。 自1990年代末誕生以來(lái)Java平臺(tái)增長(zhǎng)顯著,。綜觀下載文件的大小,,JDK 1.1為10Mb,而Mac OS X下載JDK 8u77卻非常大,,有227Mb,。安裝占用的空間和內(nèi)存需求也有了相應(yīng)的增加。這些增加是因?yàn)樵黾恿诵碌墓δ?,而且大部分功能是受歡迎并且有用的,。然而,每一個(gè)新的功能都為不需要這個(gè)功能的用戶創(chuàng)造了膨脹——沒(méi)有人會(huì)使用平臺(tái)所有的功能,。而且即使已經(jīng)過(guò)時(shí),,所有現(xiàn)有的功能都會(huì)保留,因?yàn)镴ava管理員提供了令人欽佩的奉獻(xiàn)精神——向后兼容性,。 多年來(lái),,Java體重的增加并不是一個(gè)大問(wèn)題。它是最流行的企業(yè)平臺(tái),,它的主要競(jìng)爭(zhēng)對(duì)手是微軟的.NET,,然而.NET也有著相似的軌跡。在當(dāng)今世界,,Java面臨不同的挑戰(zhàn),。物聯(lián)網(wǎng)推動(dòng)了空間占用新一輪的關(guān)注,新的,、靈活的平臺(tái)和語(yǔ)言(比如Node.js,、Go)都是非常有競(jìng)爭(zhēng)力的對(duì)手。 安全也是一個(gè)大問(wèn)題:Java攻擊引起了組織對(duì)安全意識(shí)的重視,,把它從用戶桌面完全移除,。如果內(nèi)部JVM和用戶空間應(yīng)用程序代碼之間有更好的隔離,,這些攻擊是不可能發(fā)生的。 很早之前我們就清楚需要為模塊化平臺(tái)做一些事情了,。在2000年中期有一系列失敗的嘗試,,例如,JSR 294和它的“superpackages”,,JSR 277的“Java模塊化系統(tǒng)”——最終名為Jigsaw的原型項(xiàng)目出現(xiàn)了,。這本來(lái)是在2011年Java 7中提交的,但被推遲到Java 8再推遲到Java 9,。作為一個(gè)原型項(xiàng)目,,Jigsaw為JPMS規(guī)范提供了參考實(shí)現(xiàn)。 而與此同時(shí),,OSGi用了16年時(shí)間不斷發(fā)展和完善,。OSGi是應(yīng)用程序模塊化的標(biāo)準(zhǔn):由于它不是Java平臺(tái)的一部分,它不能影響平臺(tái)本身的模塊化,。但是,,許多應(yīng)用已經(jīng)受益于它提供的高于JVM的模塊化模型。 JPMS和OSGi之間有很多小的差異,,但是有一個(gè)很大的不同,,就是隔離的實(shí)現(xiàn)。 隔離是模塊化系統(tǒng)最基本的特征,。每個(gè)模塊必須有一些保護(hù)措施防止運(yùn)行在同一應(yīng)用程序中其他模塊的干擾,。隔離是一個(gè)連續(xù)的而不是二進(jìn)制的概念:無(wú)論OSGi還是JPMS都需要做一些事情來(lái)避免那些表現(xiàn)不好的模塊的影響,這些模塊占用了JVM中所有可用的內(nèi)存,,運(yùn)行了數(shù)千個(gè)線程或者讓CPU處于繁忙的循環(huán),。如果一個(gè)模塊可以作為操作系統(tǒng)上獨(dú)立的進(jìn)程運(yùn)行,是可以提供這類保護(hù)的,,但即使是這樣,,它也是不完美的;有人仍然可以使操作系統(tǒng)崩潰或者擦除磁盤,。 OSGi和JPMS都提供了代碼級(jí)隔離,,這意味著一個(gè)模塊不能訪問(wèn)另一個(gè)模塊的內(nèi)部類型,除非該模塊有明確的許可,。 OSGi通過(guò)類加載器實(shí)現(xiàn)隔離,。每個(gè)模塊(或者在OSGi術(shù)語(yǔ)中稱為“bundle”)有一個(gè)類加載器,它知道如何在bundle中加載類型,。它也可以將類加載請(qǐng)求委托給它所依賴的其他bundle的加載器,。該系統(tǒng)是高度優(yōu)化的,例如,,OSGi不會(huì)為一個(gè)bundle創(chuàng)建一個(gè)類加載器直到最后一刻,,而且事實(shí)上每個(gè)加載器會(huì)處理一個(gè)更小的類型,,這樣每個(gè)類型可以加載得更快。 這個(gè)系統(tǒng)最大的優(yōu)勢(shì)是,,bundle可以包含重疊的包和類型,而且不會(huì)相互干擾,。實(shí)際的結(jié)果是,,可能某些包和庫(kù)有多個(gè)版本同時(shí)運(yùn)行在相同的JVM。在處理像Maven這樣的構(gòu)建工具帶來(lái)的復(fù)雜的傳遞依賴圖時(shí),,這是個(gè)福音,。在許多企業(yè),Java應(yīng)用程序幾乎不可能有這樣的一套依賴,,該依賴中每個(gè)庫(kù)只包含一個(gè)版本,。 例如,我們來(lái)看看JitWatch庫(kù)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版本,,如果包含測(cè)試范圍的依賴,我們會(huì)有另一個(gè)slf4j-api的版本1.6,。這種混亂是很常見的,,傳統(tǒng)的Java中沒(méi)有真正的解決方案,只能逐步在依賴樹中添加“excludes”直到奇跡般地得到一套可以運(yùn)行的依賴庫(kù),。不幸的是對(duì)于這個(gè)問(wèn)題JPMS也沒(méi)有答案,,我們很快就會(huì)看到。 使用類加載器進(jìn)行隔離確實(shí)有一個(gè)缺點(diǎn):它打破了每一個(gè)類型最多可以在一個(gè)位置找到的假設(shè),。這是模塊化的一個(gè)自然結(jié)果,。如果一個(gè)模塊可以不受其他模塊的干擾使用自己的類型,那么不可避免地一個(gè)單一類型的名稱可能會(huì)在多個(gè)模塊中發(fā)現(xiàn),。遺憾的是,,這造成了一個(gè)問(wèn)題,因?yàn)楹芏啾A舻腏ava代碼不是用模塊化的思想編寫的,。特別是,,調(diào)用Class.forName(String)通過(guò)名字查詢類型時(shí),在真正模塊化的環(huán)境中不是總能得到正確的結(jié)果,,因?yàn)橛卸鄠€(gè)可能的返回類型,。 正是由于這個(gè)缺點(diǎn),不能使用OSGi模塊化JDK本身,。JDK的許多地方都有一個(gè)隱含的假設(shè),,任何JDK類型可以從JDK的任何其他部分加載,,所以很多事情在OSGi下會(huì)被打破,比如模型,。為了解決這個(gè)問(wèn)題,,也為了減少使用Class.forName代碼的遷移,JPMS選擇在隔離時(shí)不使用類加載器,。當(dāng)你在“modulepath”使用一組模塊來(lái)啟動(dòng)應(yīng)用程序時(shí),,所有這些模塊將由相同的類加載器加載。相反,,JPMS引入新的訪問(wèn)規(guī)則實(shí)現(xiàn)隔離,。 OSGi的隔離屏障是可見的。在OSGi,,我們不能加載一個(gè)模塊的內(nèi)部類,,因?yàn)樗鼈兪遣豢梢姷摹R簿褪钦f(shuō),,自己模塊的類加載器只能看到自己模塊內(nèi)部的類型以及從其他模塊明確導(dǎo)入的類型,。如果我試圖從其他的模塊中加載一個(gè)內(nèi)部類,我的類加載器是看不到該類型的,。就好像是根本不存在的類型,。如果試圖繼續(xù)加載該類,就會(huì)得到NoClassDefFoundError或者ClassNotFoundException的異常,。 在JPMS,,每一個(gè)類型對(duì)于任何其他類型都是可見的,因?yàn)樗麄兇嬖谟谕粋€(gè)類加載器,。但是,,JPMS增加了輔助檢查以確定加載類有權(quán)訪問(wèn)它試圖加載的類型。其他模塊的內(nèi)部類型實(shí)際上是private的,,即使它們被聲明為public,。如果我們?cè)噲D繼續(xù)加載它,那么我們會(huì)得到IllegalAccessError或者IllegalAccessException的異常,。如果我們?cè)噲D加載private的或者另一個(gè)包的默認(rèn)訪問(wèn)類型也會(huì)得到相同的錯(cuò)誤,,而且在這個(gè)類型上調(diào)用setAccessible也是無(wú)用的。這改變了Java中public修飾符的語(yǔ)義,,以前它是普遍可訪問(wèn)的,,現(xiàn)在只可在一個(gè)模塊和它的require對(duì)象中訪問(wèn)。 JPMS方法的缺點(diǎn)是,,它不可能有重疊內(nèi)容的模塊,。也就是說(shuō),如果兩個(gè)模塊都包含一個(gè)私有(非導(dǎo)出)的包org.example.util,這些模塊不能同時(shí)在模塊路徑上被加載——它會(huì)導(dǎo)致layerinstantiationexception異常,。通過(guò)應(yīng)用程序?qū)嵗惣虞d器可能會(huì)解決此限制——但這正是OSGi已經(jīng)為我們做的,! 再次強(qiáng)調(diào),完全是通過(guò)設(shè)計(jì)允許JPMS模塊化JDK的內(nèi)部,。但結(jié)果是,,你會(huì)有不能完全一起工作的模塊,因?yàn)樗鼈儍?nèi)部的實(shí)現(xiàn)細(xì)節(jié)有沖突,。 對(duì)于OSGi最常見的抱怨之一是,,它給開發(fā)人員增加了復(fù)雜性。這有一定的道理,,但是有這些抱怨的人都搞錯(cuò)了復(fù)雜性的原因。 模塊化并不是一個(gè)在應(yīng)用程序發(fā)布前灑在上面的神奇的塵埃,。它是在設(shè)計(jì)和開發(fā)各個(gè)階段必須遵循的準(zhǔn)則,。一些開發(fā)人員已經(jīng)意識(shí)到了OSGi帶來(lái)的巨大收益,他們?cè)谠缙诰烷_始使用OSGi并且在編寫一行代碼之前會(huì)運(yùn)用模塊化思想,,他們發(fā)現(xiàn)OSGi實(shí)際上是非常簡(jiǎn)單的,,尤其是在使用現(xiàn)代化OSGi工具鏈時(shí),它自動(dòng)生成元數(shù)據(jù)并且在運(yùn)行前做了大量的一致性檢查捕獲異常,。 而另一方面,開發(fā)人員試圖把OSGi引入現(xiàn)有的大型代碼庫(kù)時(shí)遭遇了困難,因?yàn)檫@些代碼很少能夠模塊化以便遷移,。沒(méi)有執(zhí)行模塊化的準(zhǔn)則,,很容易走捷徑,打破封裝性,。BEA WebLogic的一個(gè)開發(fā)人員告訴我,,在Oracle收購(gòu)BEA之前:“我們以為我們是模塊化的,直到我們開始使用OSGi,?!?/p> 除了非模塊化的應(yīng)用程序,OSGi的采用也受到非模塊化庫(kù)的阻礙,。一些流行的Java庫(kù)中類加載和全局可見性的假設(shè)在模塊化結(jié)構(gòu)中被打破了,。OSGi做了大量工作,讓它可以使用這些庫(kù),,這是OSGi規(guī)范明顯復(fù)雜性的來(lái)源,。我們需要有一定的復(fù)雜性來(lái)處理混亂的、復(fù)雜的現(xiàn)實(shí)世界,。 我們很快就會(huì)看到,,JPMS也會(huì)有同樣的問(wèn)題——可能更是如此。如果你的組織曾試圖采用OSGi,卻因?yàn)檫w移工作量過(guò)大而放棄了,,那么當(dāng)你要遷移到JPMS時(shí),,至少應(yīng)該預(yù)期會(huì)有同樣多的工作量。只需要看看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)就是越來(lái)越簡(jiǎn)單,,但JPMS規(guī)范大大增加了復(fù)雜性:與類裝載器模塊的相互作用;分層結(jié)構(gòu)和配置,;re-exporting要求,;弱模塊;靜態(tài)要求,;qualified導(dǎo)出,;dynamic導(dǎo)出;跨層繼承的可讀性,;多模塊JAR文件,;自動(dòng)模塊;未命名的模塊等等,,已經(jīng)非常清晰所有的這些功能都會(huì)作為需求添加進(jìn)來(lái),。類似的過(guò)程也發(fā)生在OSGi,只是它有16年領(lǐng)先的優(yōu)勢(shì),。 隔離只是模塊化的一個(gè)難題:模塊仍然需要協(xié)同工作和通信,。模塊之間建立“墻”后,它們需要以一個(gè)可控的方式重新連接,。一個(gè)模塊化系統(tǒng)必須定義模塊訪問(wèn)其他模塊功能的方式,。可以通過(guò)在類型級(jí)別上靜態(tài)地或者動(dòng)態(tài)地使用對(duì)象來(lái)實(shí)現(xiàn),。 靜態(tài)依賴在編譯時(shí)就是已知的和可控的,。如果一個(gè)類型在一個(gè)模塊的邊界引用另一個(gè)類型,那么模塊系統(tǒng)需要提供一個(gè)方法讓該類型可見并且可訪問(wèn),。有兩種方式:模塊需要有選擇性地暴露一些內(nèi)部類型,,模塊需要指定自己使用了其他模塊的哪些類型。 在OSGi和JPMS中,,類型暴露在Java包級(jí)別就完成了,。在OSGi使用Export-Package語(yǔ)句聲明指定名稱的包對(duì)其他bundle是可見的。它看起來(lái)像這樣:
該聲明在META-INF/ MANIFEST.MF文件中,。OSGi初期大多數(shù)開發(fā)人員會(huì)手工指定這樣的聲明,;但我們?cè)絹?lái)越傾向于使用構(gòu)建工具生成。現(xiàn)在最流行的方式是在Java源代碼中添加注解,Java 5中引入了package-info.java文件允許包級(jí)別的注解和文檔,,所以O(shè)SGi中可以如下編寫:
這是一個(gè)有用的模式,,因?yàn)橄胍獙?dǎo)出一個(gè)包時(shí)可以直接在該包中表示。版本也可以在這里顯示,,包的內(nèi)容變化時(shí)在附近就可以更新,。 JPMS中包的導(dǎo)出在module-info.java文件中,如下:
請(qǐng)注意,,如果缺少version,,JPMS中模塊和包都不能被版本化;稍后我們會(huì)討論這一點(diǎn),。 雖然在導(dǎo)出時(shí)OSGi和JPMS是類似的,,但是導(dǎo)入或?qū)ζ渌K的依賴卻有顯著的差異。 在OSGi,,導(dǎo)入包是對(duì)導(dǎo)出包的補(bǔ)充,。使用Import-Package聲明導(dǎo)入包,例如:
OSGi中bundle必須導(dǎo)入它所依賴的所有包,,除了java.*開頭的包,如java.util,。例如,,如果你的bundle中的代碼依賴org.slf4j.Logger(并且你的bundle中實(shí)際上并不包含org.slf4j包),那么這個(gè)包必須被導(dǎo)入,。同樣,,如果你依賴于org.w3c.dom.Element,那么你必須導(dǎo)入org.w3c.dom,。但是,,如果你依賴java.math.BigInteger,你不需要導(dǎo)入java.math,,因?yàn)镴ava.*包是由JVM的bootstrap類加載器加載的,。 OSGi對(duì)于引用所有的bundle還有一個(gè)并行機(jī)制,稱為Require-Bundle,,但在OSGi規(guī)范中它已經(jīng)過(guò)時(shí)了,,現(xiàn)在它的存在只是為了支持很小的邊緣的案例。Import-Package最大的優(yōu)勢(shì)是它允許模塊在不影響下游模塊的前提下被重構(gòu)或被重命名,。如圖1和2所示,。 在圖1中,模塊A被重構(gòu)為兩個(gè)新的模塊,,A和A',,但模塊B不受該操作影響,因?yàn)樗蕾囉谔峁┑能浖T趫D2中,,我們對(duì)模塊A執(zhí)行完全相同的重構(gòu),,但現(xiàn)在B可能是壞的,因?yàn)樗玫陌锌赡懿辉俅嬖谟谀KA(在這里我們必須說(shuō)“可能”,,因?yàn)槲覀儾恢滥KB使用模塊A哪些包——這正是問(wèn)題所在?。?/p> 圖1:通過(guò) Imported Packages 重構(gòu)模塊 圖2:通過(guò) Requires 重構(gòu)模塊 Import-Package語(yǔ)句手動(dòng)寫是很繁瑣的,,所以我們不這樣做,。通過(guò)OSGi工具檢查依賴生成該語(yǔ)句,并將編譯類型內(nèi)置到bundle中,。這是非??煽康模乳_發(fā)人員自己聲明運(yùn)行時(shí)依賴更可靠,。當(dāng)然,,開發(fā)人員仍然需要管理自己的編譯依賴,按照Maven正常的方式去做(或者你選擇的構(gòu)建工具),。如果編譯時(shí)把太多的依賴放在classpath下并不會(huì)有影響:可能發(fā)生的最壞情況是編譯失敗,,這只會(huì)影響源頭的開發(fā)人員并且很容易修復(fù)。另一方面,,太多的運(yùn)行時(shí)依賴會(huì)降低模塊的可移植性,,因?yàn)橐浦矔r(shí)所有這些依賴關(guān)系必須一起移植,而且可能與另一個(gè)模塊的依賴發(fā)生沖突,。 這導(dǎo)致了OSGi和JPMS之間另一個(gè)關(guān)鍵的理論差異,。在OSGi我們始終認(rèn)為,編譯時(shí)依賴和運(yùn)行時(shí)依賴可以并且經(jīng)常會(huì)不同,。例如,,它的標(biāo)準(zhǔn)做法是,有一套編譯時(shí)API和一套運(yùn)行時(shí)API,。此外,,開發(fā)人員通常在我們所能兼容的最老的API版本上編譯,但會(huì)選擇可以找到的最新的版本來(lái)運(yùn)行,。甚至非OSGi開發(fā)人員也很熟悉這種方法:你通常會(huì)在準(zhǔn)備支持的最低版本的JDK上編譯,,卻鼓勵(lì)用戶在最高的版本(包含所有的安全補(bǔ)丁和增強(qiáng)性能)上運(yùn)行。 另一方面JPMS采取了不同的策略,。JPMS旨在實(shí)現(xiàn)“跨越所有階段的保真度”,,這樣“模塊化系統(tǒng)應(yīng)該…在編譯時(shí)、運(yùn)行時(shí)以及在開發(fā)或部署的各個(gè)階段可以以完全相同的方式工作”(來(lái)自JPMS需求),。因此,,依賴關(guān)系是在整個(gè)模塊運(yùn)行時(shí)定義的,,因?yàn)檫@就是它們?cè)诰幾g時(shí)定義的方式。例如:
require語(yǔ)句和OSGi過(guò)時(shí)的Require-Bundle有相同的效果:模塊B可以訪問(wèn)所有模塊A的導(dǎo)出包,。因此,,它也存在Require-Bundle同樣的問(wèn)題:從模塊的聲明無(wú)法確定重構(gòu)模塊A的內(nèi)容是否是安全的,所以這樣做一般是不安全的,。 我們發(fā)現(xiàn),,依賴樹使用requirements而不是imports有更高程度的扇出:每個(gè)模塊攜帶比它真正需要的更多的依賴。這些問(wèn)題是真實(shí)和重要的,。尤其是Eclipse插件作者深受其害,,因?yàn)闅v史原因Eclipse bundle 傾向于使用requires而不是imports。非常不幸地,,JPMS也遵循了這條路線,。 有趣的是,雖然編譯/運(yùn)行時(shí)的保真度是JPMS的根本目標(biāo),,但最近的變化明顯減弱了保真度,。目前的早期試用版本允許用static修飾符聲明requirement,這意味著在編譯時(shí)依賴是強(qiáng)制性的,,但在運(yùn)行時(shí)是可選的,。相反,可以用dynamic修飾符聲明導(dǎo)出,,這可以使導(dǎo)出包在編譯時(shí)無(wú)法訪問(wèn),,但在運(yùn)行時(shí)可以訪問(wèn)(使用反射)。有了這些新特性可能會(huì)創(chuàng)建出成功編譯和鏈接,,但在運(yùn)行時(shí)拋出IllegalAccessError/Exception異常的模塊,。 Java生態(tài)系統(tǒng)是巨大的,,包含了用于各種目的的各種各樣的框架:從依賴注入到mocking框架,、遠(yuǎn)程調(diào)用、O/R映射等,。從用戶提供的代碼來(lái)看,,許多框架使用反射來(lái)實(shí)例化和管理對(duì)象。例如,,Java持久化架構(gòu)(JPA),,它是Java EE套件規(guī)范的一部分:作為對(duì)象關(guān)系映射,為了將domain類與從數(shù)據(jù)庫(kù)加載的記錄一一映射,,必須從用戶代碼加載和實(shí)例化domain類,。另一個(gè)例子是,Spring框架加載和實(shí)例化“bean”類實(shí)現(xiàn)接口,。 這會(huì)為包括OSGi和JPMS的模塊化系統(tǒng)帶來(lái)問(wèn)題,。理想情況下,,domain或bean類應(yīng)該隱藏在一個(gè)模塊內(nèi)部:如果它被導(dǎo)出,就會(huì)成為公共API,,這樣會(huì)對(duì)依賴于它的消費(fèi)者造成破壞,,但是我們希望能夠靈活地隨意改變我們的內(nèi)部類。另一方面,,如所述的,,通過(guò)反射訪問(wèn)非導(dǎo)出類型支持框架是非常有效的。 由于OSGi的類加載器是基于設(shè)計(jì)的,,模塊可以獲得其他模塊非導(dǎo)出包和類型的可見性——只要他們知道該類型的全限定名以及知道是哪個(gè)模塊發(fā)出的請(qǐng)求(請(qǐng)記住幾個(gè)模塊可以包含任何給定的類型名稱),。Java長(zhǎng)期使用反射的精神有效地減少了隔離,在這里甚至所謂的私有字段都可以通過(guò)調(diào)用setAccessible方法被公開,。 在OSGi中使用此功能是常見的做法,,用來(lái)提供根本沒(méi)有導(dǎo)出模塊的實(shí)現(xiàn)!相反,,它們可能包含引用內(nèi)部類型的聲明,,這些內(nèi)部類型可以通過(guò)框架加載。例如,,使用JPA做持久化的模塊可以引用persistence.xml文件的domain類型,,并且在需要時(shí)JPA實(shí)現(xiàn)模塊將會(huì)加載引用類型。 最大的用例是實(shí)施服務(wù)組件,。OSGi規(guī)范包含一章節(jié)叫聲明式服務(wù)(DS),,定義了一個(gè)模塊如何聲明組件:類的生命周期是由框架管理的。組件可以綁定到OSGi服務(wù)注冊(cè)表中的服務(wù),,并且可以自選地為自己提供服務(wù),。例如:
這個(gè)例子中,CartMgrComponent是一個(gè)提供CartManager服務(wù)的組件,。它引用了一個(gè)服務(wù)——UserAdmin,,類的生命周期由DS框架管理。當(dāng)UserAdmin服務(wù)可用,,CartMgrComponent就會(huì)被創(chuàng)建,,并且它會(huì)發(fā)布CartManager服務(wù),該服務(wù)同樣可以在其他模塊的其他組件中引用,。 這個(gè)框架可以工作是因?yàn)樗虞d了CartMgrComponent類,,該類已經(jīng)被@Component注解標(biāo)記為組件。定義組件和服務(wù)是OSGi應(yīng)用設(shè)計(jì)和編寫的主要方式,。 在JPMS,,只有導(dǎo)出包的類型可以被訪問(wèn),即使是反射,。雖然在非導(dǎo)出包中類型是可見的(你可以調(diào)用Class.forName獲取一個(gè)類對(duì)象),,但在模塊外它們是不可訪問(wèn)的,。當(dāng)一個(gè)框架試圖調(diào)用newInstance實(shí)例化一個(gè)對(duì)象,會(huì)拋出IllegalAccessException異常,。這似乎切斷了框架的許多可能性,,但是也有一些解決方法。 一種方法是提供個(gè)別類型作為服務(wù),,可以通過(guò)java.util.serviceloader加載,。自Java 6開始serviceloader就是標(biāo)準(zhǔn)平臺(tái)的一部分,在Java 9它已被更新支持跨模塊工作,。serviceloader可以訪問(wèn)非導(dǎo)出包的類型,,只要提供包含provides聲明的模塊。不幸的是,,serviceloader是古老的,,不能為現(xiàn)代化框架,如DS或spring,,提供所需的靈活性,。 第二種可能是使用“qualified”導(dǎo)出包。這種導(dǎo)出,,只允許指定模塊訪問(wèn),,而不是所有模塊都通用。例如,,你可以導(dǎo)出bean包到Spring Framework模塊,。但是這可能無(wú)法用于其他方面像JPA,因?yàn)镴PA是一個(gè)規(guī)范而不是一個(gè)指定的模塊,,并且它可以由不同的模塊實(shí)現(xiàn),,如Hibernate、EclipseLink等,。 第三種可能是“dynamic”導(dǎo)出,,這種包任何人都可以訪問(wèn),但只能自己使用反射,,而且不是在編譯時(shí),。這是JPMS一個(gè)非常新的特性,,在郵件列表上它仍然是有爭(zhēng)議的,。它最接近OSGi的permissive方法,但它仍然需要模塊作者為某些包明確添加dynamic導(dǎo)出,,這些包中可能包含需要反射地加載的類型,。作為一個(gè)OSGi用戶感覺它是不必要的復(fù)雜性。 |
|
來(lái)自: openlabzeng > 《待分類》