拼图项目动手指南
Jigsaw項目將把模塊化引入Java平臺,根據原始計劃,它將在12月10日完成功能。 所以我們在這里,但是拼圖在哪里?
在過去的六個月中肯定發生了很多事情: 原型問世 ,內部API的迫在眉睫的刪除引起了很大的騷動 , 郵件列表中充斥著有關項目設計決策的重要討論 ,而JavaOne看到了一系列很棒的介紹性演講 。拼圖團隊。 然后Java 9因拼圖而延遲了半年 。
但是,讓我們暫時忽略所有這些,僅關注代碼。 在本文中,我們將使用一個現有的演示應用程序并將其與Java 9進行模塊化。如果要繼續學習,請轉到GitHub ,在該處可以找到所有代碼。 設置說明對于使腳本與Java 9一起運行很重要。為簡便起見,我從本文的所有程序包,模塊和文件夾名稱中刪除了前綴org.codefx.demo 。
拼圖之前的應用
即使我竭盡全力不理會整個圣誕節,但讓演示程序秉承本季的精神似乎是審慎的做法。 因此,它為出現日歷建模:
- 有一個日歷,其中包含24個日歷表。
- 每張紙都知道該月的某天,并包含一個驚喜。
- 即將到圣誕節的死亡行軍是通過在控制臺上打印床單(以及由此帶來的驚喜)來象征的。
當然,需要首先創建日歷。 它可以自己做到這一點,但它需要一種創造驚喜的方法。 為此,它得到了一個驚喜工廠清單。 main方法如下所示:
public static void main(String[] args) {List<SurpriseFactory> surpriseFactories = Arrays.asList(new ChocolateFactory(),new QuoteFactory());Calendar calendar =Calendar.createWithSurprises(surpriseFactories);System.out.println(calendar.asText()); }該項目的初始狀態絕不是拼圖之前最好的。 恰恰相反,這是一個簡單的起點。 它由一個包含所有必需類型的單個模塊(從抽象意義上講,不是Jigsaw解釋)組成:
- “驚喜API” – Surprise和SurpriseFactory (均為接口)
- “日歷API” – Calendar和CalendarSheet用于創建日歷
- 驚喜–幾個Surprise和SurpriseFactory實現
- Main –連接并運行整個過程。
編譯和運行非常簡單(Java 8的命令):
# compile javac -d classes/advent ${source files} # package jar -cfm jars/advent.jar ${manifest and compiled class files} # run java -jar jars/advent.jar進入拼圖土地
下一步雖小,但很重要。 它不會更改代碼或其組織,只會將其移至Jigsaw模塊中。
模組
那么什么是模塊? 引用強烈推薦的模塊系統狀態 :
模塊是命名的,自描述的代碼和數據集合。 它的代碼被組織為一組包含類型(即Java類和接口)的軟件包。 其數據包括資源和其他種類的靜態信息。
為了控制其代碼如何引用其他模塊中的類型,模塊聲明要編譯和運行它需要哪些其他模塊。 為了控制其他模塊中的代碼如何引用其包中的類型,模塊聲明了要導出的包中的哪個。
因此,與JAR相比,模塊具有JVM可以識別的名稱,聲明其依賴于其他模塊,并定義哪些包是其公共API的一部分。
名稱
模塊的名稱可以是任意的。 但是為了確保唯一性,建議堅持使用包的反向URL命名模式。 因此,雖然這不是必需的,但通常意味著模塊名稱是它包含的軟件包的前綴。
依存關系
一個模塊列出了要編譯和運行的其他模塊。 對于應用程序和庫模塊而言,這都是正確的,但對于JDK本身中的模塊而言,這是正確的,它被分成了大約80個(請使用java -listmods )。
再次從設計概述中:
當一個模塊直接依賴于模塊圖中的另一個模塊時,第一個模塊中的代碼將能夠引用第二個模塊中的類型。 因此,我們說第一模塊讀取第二模塊,或者等效地,第二模塊可被第一模塊讀取 。
[…]
模塊系統確保每個依賴關系都由另一個模塊準確地滿足,沒有兩個模塊互相讀取,每個模塊最多讀取一個定義給定程序包的模塊,并且定義同名程序包的模塊不會互相干擾。
當違反任何一個屬性時,模塊系統將拒絕編譯或啟動代碼。 這是對脆弱類路徑的巨大改進,在脆弱類路徑中,例如丟失的JAR僅在運行時才發現,從而使應用程序崩潰。
還需要指出的是,只有模塊直接依賴于另一個模塊,才能訪問另一個模塊的類型。 因此,如果A依賴于B ,而B依賴于C ,那么除非明確要求A ,否則A將無法訪問C。
出口產品
一個模塊列出了它導出的軟件包。 這些包中的公共類型只能從模塊外部訪問。
這意味著public不再是真正的公眾。 非導出包中的公共類型與導出包中的非公共類型一樣,對外界隱藏。 它比當今的私有包類型更加隱藏,因為模塊系統甚至不允許反射訪問它們。 由于拼圖是當前實現的,命令行標志是解決此問題的唯一方法。
實作
為了能夠創建模塊,項目需要在其根源目錄中包含module-info.java :
module advent {// no imports or exports }等等,我不是說我們也必須聲明對JDK模塊的依賴嗎? 那么,為什么我們在這里沒有提到什么呢? 所有Java代碼都需要Object ,并且該類以及演示使用的其他少數幾個類也是模塊java.base一部分。 因此,實際上每個 Java模塊都依賴于java.base ,這導致Jigsaw團隊決定自動要求它。 因此,我們不必明確提及它。
最大的變化是要編譯和運行的腳本(Java 9的命令):
# compile (include module-info.java) javac -d classes/advent ${source files} # package (add module-info.class and specify main class) jar -c \--file=mods/advent.jar \--main-class=advent.Main \${compiled class files} # run (specify a module path and simply name to module to run) java -mp mods -m advent我們可以看到編譯幾乎相同–我們只需要在類列表中包括新的module-info.java 。
jar命令將創建所謂的模塊化JAR,即包含模塊的JAR。 與之前不同,我們不再需要任何清單,而是可以直接指定主類。 注意如何在目錄mods創建JAR。
完全不同的是啟動應用程序的方式。 這樣做的目的是告訴Java在哪里可以找到應用程序模塊(使用-mp mods ,這稱為模塊路徑 ),以及我們想啟動哪個模塊(使用-m advent )。
分成模塊
現在是時候真正了解Jigsaw并將其拆分為單獨的模塊了。
虛構理由
“驚喜API”(即Surprise和SurpriseFactory取得了巨大的成功,我們希望將其與整體分離。
創造驚喜的工廠非常活躍。 這里要做很多工作,它們經常更改,并且使用的工廠因版本而異。 因此,我們想隔離它們。
同時,我們計劃創建一個大型的圣誕節應用程序,日歷僅是其中的一部分。 因此,我們也希望為此提供一個單獨的模塊。
我們最終得到以下模塊:
- 驚喜 – Surprise and SurpriseFactory
- 日歷 –日歷,使用Surprise API
- 工廠 – SurpriseFactory實現
- main –原來的應用程序,現在已經鏤空到Main類
通過查看它們之間的依賴關系,我們可以發現驚喜并不取決于其他模塊。 日歷和工廠都使用它的類型,因此必須依賴它。 最后, main使用工廠來創建日歷,因此它依賴于兩者。
實作
第一步是重新組織源代碼。 我們將遵循官方快速入門指南建議的目錄結構,并將所有模塊放在src的自己的文件夾中:
src- advent.calendar: the "calendar" module- org ...module-info.java- advent.factories: the "factories" module- org ...module-info.java- advent.surprise: the "surprise" module- org ...module-info.java- advent: the "main" module- org ...module-info.java .gitignore compileAndRun.sh LICENSE README為了保持可讀性,我刪節了org下面的文件夾。 缺少的是軟件包以及每個模塊的最終源文件。 完整地在GitHub上查看。
現在,讓我們看看這些模塊信息必須包含什么以及如何編譯和運行應用程序。
沒有必要的條款,因為Surprise沒有依賴性。 (除了java.base ,它始終是隱式必需的。)它導出包advent.surprise因為它包含兩個類Surprise和SurpriseFactory 。
因此module-info.java如下所示:
module advent.surprise {// requires no other modules// publicly accessible packagesexports advent.surprise; }編譯和打包與上一節非常相似。 實際上,這甚至更容易,因為意外事件不包含任何主要類別:
# compile javac -d classes/advent.surprise ${source files} # package jar -c --file=mods/advent.surprise.jar ${compiled class files}日歷使用來自Surprise API的類型,因此模塊必須依賴Surprise 。 向模塊添加requires advent.surprise即可實現。
該模塊的API由Calendar類組成。 為了使其可公開訪問,必須導出包含軟件包advent.calendar 。 請注意,同一包私有的CalendarSheet在模塊外部將不可見。
但有一個附加的扭曲:我們剛剛作出Calendar.createWithSurprises( List<SurpriseFactory> )公布,從驚喜模塊暴露類型。 因此,除非讀取日歷的模塊也需要驚訝 ,否則Jigsaw將阻止它們訪問這些類型,這將導致編譯和運行時錯誤。
將require子句標記為public可解決此問題。 有了它,任何依賴日歷的模塊也會讓人吃驚 。 這稱為隱式可讀性 。
最終的模塊信息如下所示:
module advent.calendar {// required modulesrequires public advent.surprise;// publicly accessible packagesexports advent.calendar; }編譯幾乎像以前一樣,但是當然必須在此反映對驚奇的依賴。 為此,將編譯器指向目錄mods就足夠了,因為它包含所需的模塊:
# compile (point to folder with required modules) javac -mp mods \-d classes/advent.calendar \${source files} # package jar -c \--file=mods/advent.calendar.jar \${compiled class files}該工廠實現SurpriseFactory所以這個模塊必須依靠驚喜 。 并且由于它們從已發布的方法返回Surprise實例,因此與上述相同的思路導致了requires public子句的出現。
可以在包advent.factories找到工廠,因此必須將其導出。 請注意,在另一個模塊中找到的公共類AbstractSurpriseFactory在此模塊外部無法訪問。
這樣我們得到:
module advent.factories {// required modulesrequires public advent.surprise;// publicly accessible packagesexports advent.factories; }編譯和打包類似于日歷 。
我們的應用程序需要日歷和工廠這兩個模塊進行編譯和運行。 它沒有要導出的API。
module advent {// required modulesrequires advent.calendar;requires advent.factories;// no exports }編譯和打包與上一節的單個模塊相似,不同之處在于編譯器需要知道在哪里尋找所需的模塊:
#compile javac -mp mods \-d classes/advent \${source files} # package jar -c \--file=mods/advent.jar \--main-class=advent.Main \${compiled class files} # run java -mp mods -m advent服務
拼圖通過實現服務定位器模式實現松散耦合,在該模式中 ,模塊系統本身充當定位器。 讓我們看看情況如何 。
虛構理由
最近有人讀了一篇關于冷松耦合的博客文章。 然后她從上面看我們的代碼,抱怨主機和工廠之間的緊密關系。 主為什么還要知道工廠 ?
因為…
public static void main(String[] args) {List<SurpriseFactory> surpriseFactories = Arrays.asList(new ChocolateFactory(),new QuoteFactory());Calendar calendar =Calendar.createWithSurprises(surpriseFactories);System.out.println(calendar.asText()); }真? 只是為了實例化完美抽象的某些實現( SurpriseFactory )?
而且我們知道她是對的。 讓其他人為我們提供實現將消除直接依賴。 更好的是,如果說中間人能夠在模塊路徑上找到所有實現,則可以通過在啟動之前添加或刪除模塊來輕松配置日歷的驚喜。
拼圖確實可以做到這一點。 我們可以有一個模塊來指定它提供接口的實現。 另一個模塊可以表示它使用所述接口,并使用ServiceLocator查找所有實現。
我們利用這個機會將工廠分割成巧克力,然后報價并最終得到以下模塊和依賴項:
- 驚喜 – Surprise and SurpriseFactory
- 日歷 –日歷,使用Surprise API
- 巧克力 – ChocolateFactory即服務
- quote – QuoteFactory即服務
- 主要 –應用程序; 不再需要單個工廠
實作
第一步是重新組織源代碼。 之前的唯一變化是src/advent.factories被src/advent.factory.chocolate和src/advent.factory.quote 。
讓我們看一下各個模塊。
兩者都沒有改變。
除某些名稱外,兩個模塊都是相同的。 讓我們看一下巧克力,因為它更美味。
和工廠以前一樣,該模塊requires public 驚喜模塊。
更有趣的是其出口。 它提供了SurpriseFactory的實現,即ChocolateFactory ,其指定如下:
provides advent.surprise.SurpriseFactorywith advent.factory.chocolate.ChocolateFactory;由于此類是其公共API的全部,因此不需要導出其他任何內容。 因此,沒有其他出口條款是必要的。
我們最終得到:
module advent.factory.chocolate {// list the required modulesrequires public advent.surprise;// specify which class provides which serviceprovides advent.surprise.SurpriseFactorywith advent.factory.chocolate.ChocolateFactory; }編譯和打包很簡單:
javac -mp mods \-d classes/advent.factory.chocolate \${source files} jar -c \--file mods/advent.factory.chocolate.jar \${compiled class files}關于main的最有趣的部分是它如何使用ServiceLocator查找SurpriseFactory的實現。 從其主要方法 :
List surpriseFactories = new ArrayList<>(); ServiceLoader.load(SurpriseFactory.class).forEach(surpriseFactories::add);我們的應用程序現在僅需要日歷,但必須指定它使用SurpriseFactory 。 它沒有要導出的API。
module advent {// list the required modulesrequires advent.calendar;// list the used servicesuses advent.surprise.SurpriseFactory;// exports no functionality }編譯和執行與以前一樣。
我們確實可以通過簡單地從模塊路徑中刪除工廠模塊之一來更改日歷最終將包含的驚喜。 整齊!
摘要
就是這樣了。 我們已經看到了如何將單片應用程序移動到單個模塊中以及如何將其拆分為多個模塊。 我們甚至使用服務定位器將我們的應用程序與服務的具體實現分離開來。 所有這些都在GitHub上,因此請查看更多代碼!
但是還有更多要討論的! 拼圖帶來了一些不兼容問題,但也解決了許多不兼容問題。 而且我們還沒有討論反射如何與模塊系統交互以及如何遷移外部依賴項。
如果您對這些主題感興趣,請在我的博客上觀看Jigsaw標簽 ,因為在接下來的幾個月中我一定會寫關于它們的。
翻譯自: https://www.javacodegeeks.com/2015/12/project-jigsaw-hands-guide.html
總結
- 上一篇: 花毛茛怎么读 花毛茛的读音
- 下一篇: 虚拟现实技术有哪几大分类?