在特定於模組的目錄中整齊地組織專案、建立模組宣告並編寫應用程式後,就可以生成並(稍後)執行應用程式了。 要構建應用程式,您需要建立乙個模組工件,這是乙個兩步過程:編譯和打包。
在編譯時,編譯器需要知道宣告引用模組位置的位置,對j**a對於它自己的模組來說,這是一件輕而易舉的事,因為編譯器知道依賴項的位置(在執行時環境中)libs/modules檔案)。
關鍵點 為了能夠找到自己的模組,必須使用模組路徑,這是乙個與類路徑平行的概念。 顧名思義,它期望儲存模組化jar而且不普通jar。當編譯器搜尋引用的模組時,它會對其進行掃瞄。
為了定義模組路徑,j**ac新增了乙個新選項:--module-path,或者乾脆說-p(想法。jvm啟動應用程式時也是如此。 相應j**a引入了相同的選項--module-path 和 -p,它們具有相同的功能)。
選擇mods目錄儲存模組有兩點含義:
1) 模組路徑包含mods目錄;
mods目錄包含打包的專案。
某些模組具有外部依賴關係:persistence需要模組hibernate(hibernate.jpa)而rest需要模組sparkspark.core)。假設它們的工件已經是模組化的jar,連同其依賴項一起放置mods目錄。
如果是普通的jar在模組路徑上,或模組化jar如果你把它放在類路徑上,甚至混合搭配,會發生什麼? 如果依賴項還不是模組化的,但你想使用它怎麼辦? 這些是向模組化遷移的一部分,稍後將對此進行詳細解釋。
基於這些先決條件,可以對模組進行編譯和打包。 從monitor.observer首先,它沒有依賴項,也不包含任何新內容 - 使用舊版本j**a執行它也會得到相同的結果。
monitor.alpha模組具有依賴項,因此必須使用模組路徑告訴編譯器可以在 ** 找到所需的專案。 當然,隨著jar命令打包不受其影響。
大多數其他模組都差不多。 乙個例外是:monitor.rest,已找到libs目錄,所以你需要新增libs到模組路徑。
另乙個例外是:monitor,您需要告訴模組系統它有應用程式的入口點main功能。
最終內容如圖 2-6 所示。 這些jar該檔案就像乙個普通的舊檔案jar但有乙個例外:每個檔案都包含乙個模組描述符module-info.class檔案,標記為模組化jar
所有應用程式模組都編譯並打包到mods目錄,並準備好啟動。
所有模組都編譯為mods目錄,終於到了啟動應用程式的時候了。 正如您在下面的簡要說明中看到的,這就是模組宣告工作的價值所在。
Essentials 您需要做的就是致電j**a,指定模組路徑,讓j**a知道從**中可以找到應用程式包含的專案,並告訴它要啟動哪個模組。 模組系統負責處理所有依賴項,避免衝突或模稜兩可的版本,並從正確的模組開始。
當然,沒有乙個軟體專案真正結束(除非專案“死亡”),所以改變是不可避免的。 例如,如果要新增另乙個observer意識到了,那麼會發生什麼? 通常,您將執行以下步驟:
1)為其開發子專案。
2)構建它。
3)在現有的**中使用它。
這就是你現在需要做的。 對於新模組,新增模組宣告將允許將其整合到模組系統中。
像任何其他模組一樣編譯和打包它。
然後將其作為依賴項新增到現有 **。
就是這樣。 如果構建包括編譯和打包,則只需新增或修改模組宣告即可。 刪除或重構模組也是如此:除了通常的更改之外,您還需要考慮這將如何影響您的模組圖並更新相應的模組宣告。
到目前為止一切順利,不是嗎? 在以下章節中深入探討模組系統的細節之前,請花一些時間了解模組系統承諾的兩個好處,以及使用一些高階功能可以解決哪些拐角殘留問題。
在上一篇文章中,我們討論了模組系統的目標,並談到了兩個最重要的目標:可靠的配置和強大的封裝。 現在您已經構建了更具體的內容,讓我們回顧一下這些目標,看看它們如何幫助人們交付健壯且可維護的軟體。
01.可靠的配置
如果依賴項不能開啟mods當你找到它時會發生什麼? 如果兩個依賴項需要同乙個專案(例如log4j或gu**a不同的版本會發生什麼? 如果兩個模組有意或無意地匯出兩個相同的型別,會發生什麼情況?
在類路徑機制中,這些問題在執行時暴露出來,其中一些會使應用程式崩潰,而另一些則更微妙且無法檢測到,最終導致錯誤的程式行為。
在模組化系統中,許多像這樣的不可靠情況(尤其是剛才提到的情況)被更早地檢測到。 compiler 或jvm它終止執行並返回特定訊息,使人們有機會修復錯誤。
例如,當應用啟動但找不到時monitor.statistics,您將收到以下提示。
同樣,當模組路徑中有兩個時slf4j版本、開始servicemonitor應用程式將獲得以下結果。
您再也不會意外地依賴間接依賴關係了。 hibernate將使用slf4j,這意味著當應用程式啟動時,此庫始終存在。 但是一旦你開始匯入slf4j(它不會出現在任何模組宣告中),編譯器將阻止它並告訴您您正在沒有顯式依賴項的模組中使用 **。
即使您找到了繞過編譯器檢查的方法,模組系統也會在啟動時執行相同的檢查。
02.強封裝
現在讓我們從模組消費者的角度切換到模組維護者的角度。 想象一下,為了修復乙個bug或者提高效能,對吧monitor.observer.alpha重構。
發布新版本後,您會發現:monitor其中一些無法正常工作,這會在應用程式中造成不穩定。 如果您更改公有所有權api,那就錯了。
但是,如果您更改了標記為不受支援但仍被呼叫的型別的內部實現詳細資訊,該怎麼辦? 此型別可能應該是公共的,因為您想在兩個包中使用它; 這也是可能的monitor的開發人員通過反射訪問了它。 在這種情況下,您無法阻止使用者依賴實現。
在模組化系統的幫助下,可以避免這種情況。 事實上,你已經做到了:
只有匯出包中的型別是可見的,其餘的都是安全的,即使通過反射也無法訪問。
注意:如果必須這樣做,則需要向下鑽取模組內部。
雖然servicemonitor模組化進展順利,但仍有一些缺點值得討論。 您目前無能為力,但本書第 3 部分中描述的高階功能可以幫助您解決這些問題。 本部分將預覽這些高階功能。
01.標記不可或缺的模組依賴
monitor.observer.alphamodules 和monitor.observer.beta該模組宣告了權利monitor.observer依存。 這是有道理的,因為他們實現了後一種曝光serviceobserverinterface,並返回屬於同一模組的模組diagnosticdatapoint例項。
這會導致任何使用實現模組的**上都產生有趣的結果。
包含這兩行的模組也需要依賴monitor.observer,否則將無法訪問serviceobserver型別和diagnosticdatapoint型別。
如果呼叫沒有依賴項monitor.observer完全monitor.observer.alpha然後模組變得毫無意義。
只有當呼叫顯式依賴於另乙個模組時,它才可用,這很愚蠢。 幸運的是,有辦法! 隱式可讀性 (impliedreadability
02.解耦 API 實現和呼叫
想想吧monitor.observer及其實現模組monitor.observer.alpha跟monitor.observer.beta會發現一些其他問題。 為什麼monitor必須知道實現嗎?
目前,monitor您需要例項化乙個具體的類,但隨後只能與相關接互。 依靠整個模組來呼叫建構函式似乎有些多餘。
事實上,任何時候,都要移除乙個被丟棄的serviceobserver實現或引入新的實現,你必須更新monitor以及重新編譯、打包和部署專案。
為了做到這一點api實現和呼叫方之間有乙個更鬆散的耦合,比如monitor這樣的呼叫者不需要依賴以下內容:monitor.observer.alpha跟monitor.observer.beta通過這種實現,模組化系統能夠實現這一目標。 這個問題將在後面討論。
03.使匯出更加明確
請記住,該包含被注釋為僅由hibernate是否使用資料傳輸物件包? 持久化模組如何匯出?
這似乎不太對勁——只是hibernate需要訪問這些實體。 但是現在,其他依賴項monitor.persistence模組,例如:monitor,您也可以看到它們。 您將接觸到模組化系統的高階功能。 合規匯出允許模組將包匯出到多個特定模組,而不是所有模組。 稍後會詳細介紹此機制。
04.僅為反射製作包
即使將包匯出到特定模組有時也太複雜了。
1) 您將基於:api例如j**a持久層apijpa) 編譯模組,而不是基於特定的實現(例如hibernate),所以在合規匯出中需要仔細提及實現模組。
2)您可以使用基於反射的工具(例如hibernate或guice它只能在執行時通過反射訪問,那麼為什麼要在編譯時訪問它呢?
3)您將依靠對私人成員的反思(hibernate這是在配置字段注入後完成的),這在匯出包中是不可能的。
本文後面將介紹乙個解決方案 - 開放模組和開放包的引入。 這允許某些包僅在執行時可用。 作為交換,它允許私人成員進行反射,因為基於反射器的工具通常會要求。 還有 Qualified Open,它類似於 Export,它允許您僅開啟幾個特定模組的包。
如果你用過它hibernate作為 JPA 提供者,您可能已經付出了很多努力來阻止它hibernate直接依賴。 在這種情況下,將依賴項硬編碼為模組宣告絕不是您希望看到的。 稍後將更詳細地討論此方案。
某些 ** 僅在正在執行的應用程式中存在依賴項時執行的情況並不少見。 例如:monitor.statistics模組中可能有一些**使用時髦的靜態類庫,可能是因為許可證問題servicemonitor在啟動時,此庫並不總是存在。
另乙個例子是乙個庫,其功能僅在存在第三方依賴項時才會激發使用者的興趣 - 例如,當斷言儲存庫存在時,該框架適用於斷言儲存庫。
正如我們前面所討論的,必須在模組宣告中宣告依賴項。 這強制要求在編譯時必須存在依賴項才能使編譯成功。 但不幸的是requires關鍵字表示依賴項在啟動時也必須存在,否則jvm應用程式將拒絕執行。
這很難令人滿意。 但正如預期的那樣,模組系統為它留下了一條出路,那就是可選的依賴。 它必須在編譯時存在,但不能在執行時存在。 這將在後面討論。 在討論了所有高階功能之後,稍後將展示servicemonitor,它使用大多數高階功能。
下面介紹了定義、構建和執行模組化應用程式的三個步驟。 它們都很重要,但下面的文章尤其重要,因為它解釋了模組系統的基本概念和基礎知識。
1)在模組化應用程式時,可以根據跨越模組邊界的型別依賴來推斷模組依賴關係,這使得建立初始模組依賴關係圖非常直觀。
2)多模組專案的目錄結構與以下方面有關j**a 9以前的目錄結構類似,因此現有的工具和工具可以繼續工作。
3) 模組宣告 – 在專案的根目錄中module-info.j**a檔案是模組系統在**級別帶來的最明顯的變化。 它命名模組並宣告依賴項和公有所有權api。除此之外,**的寫法基本沒有變化。
j**ac、jar跟j**a該命令已更新為支援模組。 最明顯的相關變化是模組路徑(命令列引數--module-path或-p)。它與類路徑具有相同的狀態,但為模組提供服務。