文 by / 林本托
Tips
做一個終身學習的人。
在這個章節中,主要介紹以下內容:
- 如何編寫模塊化的Java程序
- 如何編譯模塊化程序
- 如何將模塊的項目打包成模塊化的JAR文件
- 如何運行模塊化程序
在本章中,將介紹如何使用模塊 —— 從編寫源代碼到編譯,打包和運行程序。 本章分為兩部分。 第一部分顯示使用命令行編寫和運行模塊程序的所有步驟。 第二部分使用NetBeans IDE重復相同的步驟。
到目前,NetBeans IDE仍在開發中,並且不支持所有JDK 9功能。 例如,目前需要NetBeans為創建的每個模塊創建一個新的Java項目。 在最終版本中,NetBeans將允許在一個Java項目中擁有多個模塊。 當使用命令提示符時,會更具體的JDK 9選項。
本章介紹的程序非常簡單。 當程序運行時,它打印一個消息和主類所屬模塊的名稱。
一. 使用命令提示符
以下小節介紹使用命令提示符創建和運行第一個模塊的步驟。
1. 設置目錄
將使用以下目錄層次結構來編寫,編譯,打包和運行源代碼:
C:\Java9Revealed
| --> C:\Java9Revealed\lib
| --> C:\Java9Revealed\mods
| --> C:\Java9Revealed\src
| --> C:\Java9Revealed\src\com.jdojo.intro
這些目錄在Windows系統上設置的。 在非Windows操作系統上,你也可以設置類似的目錄層次結構。 C:\Java9Revealed是頂層目錄,它包含三個子目錄:lib,mods和src。
src目錄用於保存源代碼,其中包含一個com.jdojo.intro的子目錄,並創建一個同名的com.jdojo.intro的模塊,並將其源代碼保存在這個子目錄下。 在這種情況下,是否有必要將子目錄命名為com.jdojo.intro? 答案是不。 子目錄可以是不同的名字,或者可以將源直接存儲在src目錄中,而不需要com.jdojo.intro子目錄。 但是,最好將目錄命名為與模塊名稱相同的模塊的源代碼。 如果遵循這個命名約定,Java編譯器將有一些選項可一次編譯多個模塊的源代碼。
使用mods目錄將已編譯的代碼保存在展開的目錄層次結構中。 如果需要,可以使用此目錄中的代碼運行應用程序。
在編譯源代碼之后,將其打包成一個模塊化的JAR並將其存儲在lib目錄中。 可以使用模塊化JAR來運行程序,也可以將模塊JAR提供給可以運行程序的其他開發人員。
在本節的剩余部分會使用一個目錄(如src或src\com.jdojo.intro)的相對路徑。 這些相對路徑是相對於C:\Java9Revealed目錄。 例如,src表示C:\Java9Revealed\src。 如果使用非Windows操作系統或其他目錄層次結構,請進行適當的調整。
2. 編寫源代碼
你可以選擇自己喜歡的文本編輯器(例如Windows上的記事本)來編寫源代碼。 首先創建一個名為com.jdojo.intro的模塊。下面是模塊聲明的代碼。
// module-info.java
module com.jdojo.intro {
// No module statements
}
模塊聲明很簡單。 它不包含模塊語句。 將其保存在src\com.jdojo.intro目錄下名為module-info.java的文件中。
然后創建一個名為Welcome的類,它將保存在com.jdojo.intro包中。 請注意,包的名字與模塊具有相同的名稱。 但必須保持模塊和包名稱相同嗎? 答案是不。 也可以選擇所需的任何其他包名稱。 該類將具有一個主方法public status void main(String [] args)
。 該方法將作為應用程序的入口點。 在此方法內打印消息。
要打印模塊的名稱,Welcome 類是它的一個成員。 JDK 9在java.lang包中添加了一個名為Module的類。 Module類的一個實例代表一個模塊。 JDK 9中的每個Java類型都是模塊的成員,甚至是int,long和char等原始類型。 所有原始類型都是java.base模塊的成員。 JDK 9中的Class類有一個名為getModule()
的新方法,它返回該類作為其成員的模塊引用。 以下代碼打印了Welcome類的模塊名稱。
Class<Welcome> cls = Welcome.class;
Module mod = cls.getModule();
String moduleName = mod.getName();
System.out.format("Module Name: %s%n", moduleName);
Tips
所有原始數據類型都是java.base模塊的成員。 可以使用int.class.getModule()
獲取int基本數據類型的模塊的引用。
下面的代碼包含是Welcome類中代碼。並保存在名為Welcome.java的文件中,目錄是com\jdojo\intro,它是src\com.jdojo.intro目錄的子目錄。 此時,源代碼文件的路徑將如下所示:
- C:\Java9Revealed\src\com.jdojo.intro\module-info.java
- C:\Java9Revealed\src\com.jdojo.intro\com\jdojo\intro\Welcome.java
// Welcome.java
package com.jdojo.intro;
public class Welcome {
public static void main(String[] args) {
System.out.println("Welcome to the Module System.");
// Print the module name of the Welcome class
Class<Welcome> cls = Welcome.class;
Module mod = cls.getModule();
String moduleName = mod.getName();
System.out.format("Module Name: %s%n", moduleName);
}
}
3. 編譯源代碼
使用Java編譯器javac命令來編譯源代碼並將編譯的代碼保存在C:\ java9Revealed\mods目錄下。 javac命令位於JDK_HOME\bin目錄中。 以下命令編譯源代碼。 命令輸入是一行,而不是三行:
C:\Java9Revealed>javac -d mods --module-source-path src
src\com.jdojo.intro\module-info.java
src\com.jdojo.intro\com\jdojo\intro\Welcome.java
注意,運行此命令時,C:\Java9Revealed是當前目錄。 -d mods
選項告訴Java編譯器將所有編譯的類文件保存到mods目錄下。 注意,正在從C:\java9revealed目錄運行命令,因此命令中的mods目錄表示C:\Java9Revealed\mods目錄。 如果需要,可以用-d C:\Java9Revealed\mods
替換此選項。
第二個選項--module-source-path src
指定src目錄的子目錄包含多個模塊的源代碼,其中每個子目錄名稱與包含源代碼的子目錄的模塊名稱相同 。 這個選項有一些含義:
- 在src目錄下,必須將模塊的源文件保存在子目錄下,該目錄必須與模塊命名相同。
- Java編譯器將鏡像src目錄下的目錄結構,同時將生成的類文件保存在mods目錄中。 也就是說,com.jdojo.intro模塊的所有生成的類文件將保存在包層次結構的mods\com.jdojo.intro目錄中。
- 如果不指定此選項,生成的類文件將直接放在mods目錄下。
javac命令的最后兩個參數是源文件 —— 一個是模塊聲明,一個Welcome類。 如果javac
命令成功運行,則在C:\Java9Revealed\mods\com.jdojo.intro目錄下生成以下兩個類文件:
- module-info.class
- com\jdojo\intro\Welcome.class
你完成了源代碼的編譯。
以下命令使用JDK 9之前存在的樣式來編譯com.jdojo.intro模塊的源代碼。它僅使用-d
選項,該選項指定放置編譯的類文件的位置。
C:\Java9Revealed>javac -d mods\com.jdojo.intro src\com.jdojo.intro\module-info.java src\com.jdojo.intro\com\jdojo\intro\Welcome.java
第二個命令的輸出與上一個命令的輸出是相同的。 但是,如果要在一個命令中編譯多個模塊的源代碼,並將編譯的代碼放在特定於模塊的目錄中,則不起作用。
使用javac
的--module-version
選項,可以指定正在編譯的模塊的版本。 模塊版本保存在module-info.class文件中。 以下命令將生成與上一個命令相同的一組編譯文件,還在module-info.class文件中保存了1.0作為模塊版本:
C:\Java9Revealed>javac -d mods\com.jdojo.intro
--module-version 1.0
src\com.jdojo.intro\module-info.java
src\com.jdojo.intro\com\jdojo\intro\Welcome.java
如何確認javac
命令在module-info.class文件中保存了模塊版本? 您可以使用javap
命令來解析Java類文件。 如果指定了一個module-info.class文件的路徑,那么javap
命令會在模塊名稱之后打印模塊的定義,其中包含模塊的版本(如果存在)。 如果模塊版本存在,則打印的模塊名稱的格式為moduleName @ moduleVersion
。 運行以下命令以驗證上一個命令記錄的模塊名稱:
C:\Java9Revealed>javap mods\com.jdojo.intro\module-info.class
Compiled from "module-info.java"
module com.jdojo.intro@1.0 {
requires java.base;
}
在JDK 9中增強了jar工具。它允許在創建模塊化JAR時指定模塊版本。
如果要編譯多個模塊,則需要將每個源文件指定為javac命令的參數。 這里提供一個Windows和UNIX的快捷命令來一次性編譯所有的模塊。 在Windows中的一行中使用以下命令:
C:\Java9Revealed>FOR /F "tokens=1 delims=" %A in ('dir src\*.java /S /B') do javac -d mods --module-source-path src %A
該命令循環遍歷src目錄下的所有".java"文件,並一次編譯一個Java文件。
如果將命令保存在批處理文件中並運行批處理文件來編譯所有源文件,則需要將%A
替換為%%A
。
該命令的UNIX系統中等價於如下命令:
$ javac -d mods --module-source-path src $(find src -name "*.java")
4. 打包模塊代碼
我們將模塊的編譯代碼打包成一個模塊化的JAR。 需要使用位於JDK_HOME\bin目錄中的jar工具。注意, 該命令在一行中輸入,命令的最后一部分是一個點,表示當前目錄。
C:\Java9Revealed>jar --create
--file lib/com.jdojo.intro-1.0.jar
--main-class com.jdojo.intro.Welcome
--module-version 1.0
-C mods/com.jdojo.intro .
這個命令有如下選項:
--create
選項表示要創建一個新的模塊化JAR。--file
選項用於指定新的模塊化JAR的位置和名稱。將新的模塊化JAR保存在lib目錄中,其名稱將為com.jdojo.intro-1.0.jar。將模塊化JAR的版本指定為1.0。--main-class
選項指定public static void main(String[])
方法作為應用程序入口。當您指定此選項時,jar工具將在module-info.class文件中添加一個屬性,其值是指定類的名稱。 jar工具還使用此選項將Main-Class屬性添加到MANIFEST.MF文件中。--module-version
選項將模塊的版本指定為1.0。 jar工具將把這些信息記錄在module-info.class文件的屬性中。請注意,將模塊版本指定為1.0不會影響模塊化JAR的名稱。包含1.0以指示其文件名的版本。該模塊的實際版本由此選項指定。-C
選項用於指定執行jar命令時將用作設置當前目錄。將mods\com.jdojo.intro目錄指定為jar工具的當前目錄。這將使jar工具從該目錄中讀取所有要包含在模塊化JAR中的文件。- 命令的最后一部分是一個點(.),這意味着jar工具需要包括當前目錄mods\com.jdojo.intro下所有文件和目錄。請注意,這個參數和
-C
選項一起使用。如果不提供-C
選項,則該點將被解釋為C:\Java9Revealed目錄,因為該目錄是當前命令行運行的目錄。
當命令成功運行,它創建以下文件:
C:\Java9Revealed\lib\com.jdojo.intro-1.0.jar
要確保你的模塊化JAR包含com.jdojo.intro模塊的定義,請運行以下命令。
C:\Java9Revealed>java --module-path lib --list-modules com.jdojo.intro
該命令將模塊路徑指定為lib目錄,這意味着lib目錄將用於搜索應用程序模塊。 將com.jdojo.intro作為模塊名稱傳遞給--list-modules
選項,該選項將打印模塊描述以及模塊的位置。 如果獲得類似於以下內容的輸出,則模塊化JAR已正確創建:
module com.jdojo.intro@1.0 (file:///C:/Java9Revealed/lib/com.jdojo.intro-1.0.jar)
requires mandated java.base (@9-ea)
contains com.jdojo.intro
5. 運行程序
使用java
命令來運行Java程序。 語法如下:
java --module-path <module-path> --module <module>/<main-class>
這里,
--module
選項指定要與其主類一起運行的模塊。 如果您的模塊化JAR包含主屬性,則需要指定
Tips
可以分別使用-module-path
和-module
選項的簡寫版本-p
和-m
。
以下命令在com.jdojo.intro模塊中運行com.jdojo.intro.Welcome類。 當前的目錄是C:\ Java9Revealed,並且模塊化的JAR位於C:\ java9Revealed\lib\com.jdojo.intro-1.0.jar。
C:\Java9Revealed>java --module-path lib
--module com.jdojo.intro/com.jdojo.intro.Welcome
Welcome to the Module System.
Module Name: com.jdojo.intro
輸出表示程序已正確執行。 如果在模塊代碼打包到模塊化JAR中時指定主類,則可以從命令中省略主類名稱。 我們已經將com.jdojo.intro.Welcome類指定為此模塊的主類,因此以下命令的作用與上一個相同:
C:\Java9Revealed>java --module-path lib --module com.jdojo.intro
Welcome to the Module System.
Module Name: com.jdojo.intro
還可以指定包含模塊代碼的目錄作為模塊路徑。 已將模塊代碼編譯到mods目錄中。 以下命令的工作原理相同:
C:\Java9Revealed>java --module-path mods
--module com.jdojo.intro/com.jdojo.intro.Welcome
Welcome to the Module System.
Module Name: com.jdojo.intro
我們嘗試從mods目錄運行模塊,只使用模塊名稱:
C:\Java9Revealed>java --module-path mods --module com.jdojo.intro
module com.jdojo.intro does not have a MainClass attribute, use -m <module>/<main-class>
收到一個錯誤。 錯誤消息指示在mods\com.jdojo.intro目錄中找到的module-info.class不包含主類名稱。當聲明模塊時,不能在模塊聲明中指定主方法或版本。 編譯模塊時,只能指定模塊版本。 使用jar工具打包時,可以指定模塊的主類及其版本。 lib\com.jdojo.intro-1.0.jar中的module-info.class文件包含主類名,而mods\com.jdojo.intro目錄中的module-info.class文件則不包含。 如果要運行其編譯代碼位於分解目錄中的模塊,則必須指定主類名稱以及模塊名稱。
JDK還提供了-jar
選項來從JAR文件運行主類。 我們使用以下命令運行此模塊:
C:\Java9Revealed>java -jar lib\com.jdojo.intro-1.0.jar
Welcome to the Module System.
Module Name: null
看來只有輸出中的第一行是正確的,第二行是不正確的。 它找到了main()
方法中執行了代碼。 它正確打印消息,但模塊名稱為空。
需要了解JDK 9中java命令的行為。-jar
選項存在於JDK 9之前。在JDK 9中,類型作為模塊的一部分,可以通過模塊路徑或類路徑加載。如果通過類路徑加載類型,則該類型成為未命名模塊的成員。該類型從其原始模塊中失去其成員資格,即使該類型是從模塊化JAR加載的。實際上,如果一個模塊化的JAR放置在類路徑上,那么它被視為一個JAR(而不是一個模塊化的JAR),忽略它的module-info.class文件。每個應用程序類加載器都有一個未命名的模塊。類加載器從類路徑加載的所有類型都是該類加載器的未命名模塊的成員。一個未命名的模塊也被表示為一個Module類的實例,該類的getName()
方法返回null。
在上一個命令中,模塊化JAR com.jdojo.intro-1.0.jar被視為一個JAR,並在其中定義了所有類型 ,被加載為類加載器的未命名模塊的一部分。這就是為什么在輸出中將模塊名稱設為null的原因。
java
命令如何找到主類名?為jar工具指定主類名時,該工具將主類名稱存儲在兩個位置:
- module-info.class
- META-INF/MANIFEST.MF
該命令從META-INF/MANIFEST.MF文件讀取主類的名稱。
還可以使用--class-path
選項的java
命令來運行Welcome類。 可以將lib\com.jdojo.intro-1.0.jar模塊放置在類路徑上,在這種情況下,它將被視為JAR,Welcome類將被加載到應用程序類加載器的未命名模塊中。 就像在JDK 9之前運行類一樣。執行命令如下:
C:\Java9Revealed>java --class-path lib\com.jdojo.intro-1.0.jar
com.jdojo.intro.Welcome
Welcome to the Module System.
Module Name: null
二. 使用NetBeans IDE
如果你使用命令提示符按照上一節創建第一個模塊,則本部分將更容易掌握。 在本節中,將使用NetBeans IDE完成創建第一個模塊的步驟。 有關如何安裝支持JDK 9開發的NetBeans IDE,請參閱第1章。 從現在開始,我將使用NetBeans來編寫,編程,編譯,打包和運行所有程序。
1. 配置IDE
啟動NetBeans IDE。 如果首次打開IDE,將顯示一個名為起始頁的窗口, 如果不希望再次顯示,可以取消選中標簽為“啟動時顯示”的復選框,該復選框位於窗口的右上角。 可以通過單擊窗口標題中的X來關閉起始頁。
選擇“工具”➤“Java平台”,顯示“Java 平台管理器”對話框,如果之前配置過JDK 1.8,則會顯示在“平台”列表中。
如果在“平台”列表中看到JDK 9,則你的IDE已配置為使用JDK 9,單擊“關閉”按鈕關閉對話框。 如果在“平台”列表中沒有看到JDK 9,請單擊“添加平台”按鈕打開“添加Java平台”對話框,選擇Java Standard Edition單選按鈕。 單擊下一步。
接下來選擇JDK 9的安裝目錄:
然后點擊“完成”按鈕。
最后返回“Java 平台管理”對話框,JDK 9 已經顯示在其中。
2. 創建Java工程
選擇“文件” ➤ “新建項目”,彈出對話框,選中“Java 應用程序”,然后下一步。
在接下來的窗口中,輸入如下信息,你可以根據你自己的實際需要做出不同的調整。
項目名稱:com.jdojo.intro
項目位置: C:\Java9Revealed
取消下面兩個復選框的。
點擊“結束”按鈕,Java工程創建完成。
創建Java項目時,NetBeans會創建一組標准的目錄。 已在C:\ Java9Revealed目錄中創建了com.jdojo.intro NetBeans項目。 NetBeans創建子目錄來存儲源文件,編譯類文件和模塊化JAR。 它將為項目本身創建安裝目錄和文件。 創建以下子目錄來存儲源代碼,編譯代碼和模塊化JAR。
C:\Java9Revealed
com.jdojo.intro
build
classes
dist
src
com.jdojo.intro目錄保存此項目的所有類型的文件。 它是以NetBeans項目名稱命名的。 src目錄用於保存所有源代碼。 build目錄保存所有生成和編譯的代碼。 項目的所有編譯代碼保存在build\classes目錄下。 dist目錄存儲模塊化JAR。 請注意,當類添加到項目時,build和dist目錄是由NetBeans創建的。
3. 設置工程屬性
當前項目仍然設置為使用JDK 1.8。 需要將其更改為使用JDK 9。在項目上右鍵,彈出對話框。
再設置源/二進制格式:
最后點擊“確定”按鈕完整工程的配置。
4. 添加模塊聲明
在本節中,介紹如何在NetBeans項目中定義名為com.jdojo.intro的模塊。 要添加模塊定義,需要module-info.java的文件添加到項目中。 右鍵單擊項目,從菜單中選擇新建, 如果看到"Java Module Info"菜單項,請選擇該項。 否則,選擇其他。
單擊下一步按鈕,顯示“新建Java模塊信息”對話框。 單擊完成按鈕完成模塊的定義。 將包含模塊聲明的module-info.java文件添加到源代碼目錄的根目錄下。
默認情況下,NetBeans提供的模塊名稱與項目名稱相同不過刪除了“.”,名稱的每個部分的初始字母現在是大寫。com.jdojo.intro作為項目名稱,這就是為什么module-info.java文件中的模塊名稱是ComJdojoIntro的原因。 現在將模塊名稱更改為com.jdojo.intro。
module com.jdojo.intro {
}
5. 查看模塊圖
NetBeans IDE允許查看模塊圖。 在編輯器中打開模塊的module-info.java文件,並在編輯器中選擇“圖形”選項卡以查看模塊圖。
可以放大和縮小模塊圖,更改其布局,並將其另存為圖像。 在圖區域中右鍵單擊這些圖形相關選項。 可以在圖中選擇一個節點,僅查看以節點結尾或從節點結尾的依賴關系。 還可以通過移動節點來重新排列模塊圖。
6. 編寫源代碼
在本節中,向com.jdojo.intro項目添加一個Welcome類。 該類保存在com.jdojo.intro包中。 右鍵單擊項目,在彈出的菜單中,選擇新建➤Java類,打開“新建Java類”對話框。 輸入Welcome作為類名,com.jdojo.intro作為包。 單擊完成按鈕關閉對話框。
用下面的代碼替換掉自動生成的代碼。
package com.jdojo.intro;
public class Welcome {
public static void main(String[] args) {
System.out.println("Welcome to the Module System.");
// Print the module name of the Welcome class
Class<Welcome> cls = Welcome.class;
Module mod = cls.getModule();
String moduleName = mod.getName();
System.out.format("Module Name: %s%n", moduleName);
}
}
7. 編譯源代碼
使用NetBeans IDE時,Java源文件將在保存時自動編譯。 也可以通過取消選擇“保存時編譯”復選框,在“項目屬性”頁面上關閉項目的“保存編譯”功能默認情況下,此復選框被選中。
如果關閉“項目保存時編譯”功能,則需要通過構建項目手動編譯源文件。 可以選擇“運行”➤“構建項目”或按F11構建項目。 所以還是建議打開保存時自動編譯的功能。
8. 打包模塊代碼
需要構建項目,為你的模塊創建一個模塊化JAR。 選擇“運行”➤“構建項目”,或按F11構建項目。 模塊化的JAR是在
目前,NetBeans不支持在模塊化JAR中添加主類名稱和模塊版本名稱。 可以使用jar命令行工具來更新模塊化JAR中的模塊信息。 使用--update
選項,如下所示,要寫在一行。
C:\Java9Revealed>jar --update
--file com.jdojo.intro\dist\com.jdojo.intro.jar
--module-version 1.0
--main-class com.jdojo.intro.Welcome
可以使用以下命令驗證com.jdojo.intro的模塊化JAR是否正確更新。 應該得到類似的輸出:
C:\Java9Revealed>java --module-path com.jdojo.intro\dist
--list-modules com.jdojo.intro
module com.jdojo.intro@1.0 (file:///C:/Java9Revealed/com.jdojo.intro/dist/com.jdojo.intro.jar)
requires mandated java.base (@9-ea)
contains com.jdojo.intro
NetBeans IDE的最終版本將與JDK 9相同時間發布,到那時就允許通過IDE添加這些屬性。
9. 運行程序
選擇"運行"➤"運行項目"或按F6運行程序。
如果運行類,右鍵單擊NetBeans IDE中“項目”選項卡中包含main()
方法的源文件(.java文件),然后選擇運行文件或選擇文件,然后按Shift + F6。 Welcome類運行后將打印信息顯示在輸出面板中。
三. 總結
使用模塊開發Java應用程序不會改變Java類型被組織成包的方式。 模塊的源代碼在包層次結構的根目錄下包含一個module-info.java文件。 也就是說,module-info.java文件放在未命名的包中。 它包含模塊聲明。
JDK 9中已經增強了javac編譯器,jar工具和java啟動器以與模塊配合使用。 javac編譯器接受新的選項,例如用於定位應用程序模塊的--module-path
,找到模塊源代碼的--module-source-path
,以及--module-version
來指定正在編譯的模塊的版本。 該jar工具允許分別使用--main-class
和-module-version
選項為模塊化JAR指定主類和模塊版本。 java啟動器可以在類路徑模式,模塊模式或混合模式下運行。 要在模塊中運行類,需要使用--module-path
選項指定模塊路徑。 需要使用--module
選項指定主類名稱。 主類以
main()
方法的類的完全限定名稱,作為應用程序的執行入口。
在JDK 9中,每個類型都屬於一個模塊。 如果從類路徑加載類型,它屬於加載它的類加載器的未命名模塊。 在JDK 9中,每個類加載器維護一個未命名的模塊,其成員都是該類加載器從類路徑加載的所有類型。 從模塊路徑加載的類型屬於定義的模塊。
Module類的一個實例在運行時表示一個模塊。 Module類在java.lang包中。 使用Module類在運行時了解有關模塊的所有內容。 Class類在JDK 9中得到了增強。它的getModule()
方法返回一個表示此類成員的模塊的模塊實例。 Module類包含一個getName()
方法,它以String形式返回模塊的名稱; 對於未命名的模塊,該方法返回null。
NetBeans IDE正在更新,以支持JDK 9和開發模塊化Java應用程序。 目前,NetBeans允許創建模塊,編譯,將其打包成模塊化JAR,並從IDE中運行它們。 需要為模塊創建一個單獨的Java項目。 其最終版本將允許在一個Java項目中擁有多個模塊。 支持添加module-info.java文件。 NetBeans 具有非常酷的功能,可查看和保存模塊圖。