Java 9 揭秘(6. 封裝模塊)


Tips
做一個終身學習的人。

Java 9

在這章節中, 主要介紹以下內容:

  • 封裝Java模塊的不同格式
  • JAR格式增強
  • 什么是多版本JAR
  • 如何創建和使用多版本JAR
  • JMOD是什么格式
  • 如何使用jmod工具來處理JMOD文件
  • 如何創建、解壓和描述JMOD文件
  • 如何列出JMOD文件的內容
  • 如何在JMOD文件中記錄模塊的哈希值以進行依賴性驗證

模塊可以以不同的格式打包,以便在編譯時,鏈接時和運行時三個階段中使用。 但並不是在所有階段都支持這四種格式。 JDK 9支持以下格式打包模塊:

  • 展開的目錄
  • JAR格式
  • JMOD格式
  • JIMAGE格式

在JDK 9之前支持展開的目錄和JAR格式。JDK 9中的JAR格式已得到增強,以支持模塊化JAR和多版本JAR。 JDK 9為封裝模塊引入了兩種新格式:JMOD格式和JIMAGE格式。 本節主要討論JAR格式和JMOD格式的增強。

一. JAR格式

在前面的第三者介紹了如何在jar工具中使用新的選項來創建模塊化的JAR。 jar工具還用於列出JAR文件中的條目,並提取和更新JAR文件的內容。 該jar工具在JDK 9之前已經支持這些操作,並且在JDK 9中沒有任何新的操作。在本章中,將介紹添加到JAR格式的新功能,稱為多版本JAR。

1. 什么是多版本JAR

作為一名經驗豐富的Java開發人員,你必須使用Java類庫/框架,例如Spring框架,Hibernate等。您可能正在使用Java 8,但這些類庫可能仍然在Java 6或Java 7中使用。為什么類庫開發人員不能使用最新版的JDK來使用新功能? 其中一個原因是不是所有的類庫使用者都使用最新的JDK。 更新類庫以使用較新版本的JDK意味着強制所有類庫用戶遷移到較新的JDK,這在實踐中是不可能的。 維護和發布針對不同JDK的類庫是打包代碼時的另一個痛苦。 通常,你將會找到一個用於不同JDK的單獨的庫JAR。 JDK 9通過為類庫開發人員提供一種打包類庫代碼的新方法來解決這個問題,使用單個JAR包含多個JDK的類庫的相同版本。 這樣的JAR被稱為多版本JAR。

多版本JAR(MRJAR)包含與多個JDK版本相同版本的類庫(提供相同的API)。 也就是說,可以將類庫作為可用於JDK 8和JDK 9的MRJAR。MRJAR中的代碼將包含在JDK 8和JDK 9中編譯的類文件。使用JDK 9編譯的類可以利用JDK 9提供的API,而使用JDK 8編譯的類可以提供使用JDK 8編寫的相同的類庫API。

MRJAR擴展了JAR的已有的目錄結構。 JAR包含其所有內容所在的根目錄。 它包含一個META-INF目錄,用於存儲有關JAR的元數據。 通常,JAR包含包含其屬性的META-INF/MANIFEST.MF文件。 典型的JAR中的條目如下所示:

- jar-root
  - C1.class
  - C2.class
  - C3.class
  - C4.class
- META-INF
  - MANIFEST.MF

JAR包含四個class文件和一個MANIFEST.MF文件。 MRJAR擴展了META-INF目錄以存儲特定於JDK版本的類。 META-INF目錄包含一個版本子目錄,其中可能包含許多子目錄,每個目錄命名與JDK主要版本相同。 例如,對於特定於JDK 9的類,可能有META-INF/versions/9目錄,對於JDK 10特定的類,可能有一個名為META-INF/versions/10的目錄等。典型的MRJAR 可能有以下條目:

- jar-root
  - C1.class
  - C2.class
  - C3.class
  - C4.class
- META-INF
  - MANIFEST.MF
  - versions
    - 9
      - C2.class
      - C5.class
    - 10
      - C1.class
      - C2.class
      - C6.class

如果該MRJAR在不支持MRJAR的環境中使用,則將被視為常規JAR ——根目錄中的內容將被使用,META-INF/version/9和 META-INF/versions/10目錄下的類將被忽略。 因此,如果這個MRJAR與JDK 8一起使用,則只能使用四個類:C1,C2,C3和C4。

當在JDK 9中使用這個MRJAR時,有五個類可以執行:C1,C2,C3,C4和C5。 將使用META-INF/versions/9目錄中的C2類,而不是根目錄中的C2類。 在這種情況下,MRJAR表示它具有JDK 9的C2類的較新版本,該版本覆蓋了JDK 8或更早版本的根目錄中的C2版本。 JDK 9版本還添加了一個名為C5的新類。

同樣,如果使用JDK 10,MRJAR會覆蓋類C1和C2類,並且包含JDK版本10的名為C6的新類。

在單個MRJAR中定位多個JDK版本,MRJAR中的搜索過程與常規JAR不同。 在MRJAR中搜索資源或類文件使用以下規則:

  • JDK的主版本是針對使用MRJAR的環境決定的。 這里假設JDK的主版本是N。
  • 要查找名為R的資源或類文件,從版本N的目錄搜索META-INF/versions目錄下的特定平台的子目錄。
  • 如果在子目錄N中找到R,則返回。 否則搜索低於版本N的子目錄。 對於META-INF/versions目錄下的所有子目錄,此過程將繼續。
  • 當在META-INF/versions/N子目錄中找不到R時,將搜索MRJAR的根目錄。

我們來看一下使用以前顯示的MRJAR結構的例子。 假設程序正在尋找C3.class,當前版本的JDK是10,搜索將從META-INF/versions/10開始,其中找不到C3.class。 在META-INF/versions/9中繼續搜索,其中找不到C3.class。 現在搜索繼續在根目錄中,最后找到C3.class。

另一個例子,假設你想在JDK版本為10時找到C2.class。搜索從META-INF/versions/10開始,其中找到並返回C2.class。

另一個例子,假設你想在JDK版本為9時找到C2.class。搜索從META-INF/versions/9開始,其中找到並返回C2.class。

另一個例子,假設你想在JDK版本為8時找到C2.class。沒有名為META-INF/versions/8的JDK 8特定目錄。 因此,搜索從根目錄開始,找到並返回C2.class。

Tips
在JDK 9中,處理JAR的所有工具(如java,javac和javap)都被修改為使用多版本的JAR。 處理JAR的API也已經更新,以處理多版本的JAR。

2. 創建多版本JAR

當在特定的JDK版本中搜索資源或類文件后,我們已經知道MRJAR中目錄的搜索順序,很容易了解如何找到類和資源。 有關JDK版本特定目錄內容的一些規則。 將在后面的章節中描述這些規則。 在本節中,將重點介紹創建MRJAR。

要運行此示例,需要在計算機上安裝JDK 8和JDK 9。 如果沒有JDK 8,除JDK 9之外的任何其他JDK都可以。 對於除版本8以外的JDK,將需要更改示例中的代碼,因此代碼將使用你本地版本的JDK進行編譯。

使用MRJAR來存儲應用程序的JDK 8和JDK 9版本。 該應用程序由以下兩個類組成:

com.jdojo.mrjar.Main
com.jdojo.mrjar.TimeUtil

Main類創建一個TimeUtil類的對象,並調用它的一個方法。 Main類可以用作運行應用程序的主類。 TimeUtil類包含一個getLocalDate(Instant now)方法,它將Instant作為參數,並返回一個LocalDate類來表示當前時區的時間。 JDK 9已經為LocalDate類添加了一個新方法,它被命名為ofInstant(Instant instant, ZoneId zone)。 我們將更新應用程序以使用JDK 9並利用這種新方法,並保留使用JDK 8 中實現相同功能的Time API的舊應用程序。

源代碼包含兩個名為com.jdojo.mrjar.jdk8com.jdojo.mrjar.jdk9的NetBeans項目,它們分別配置為使用JDK 8和JDK 9。 在NetBeans中,需要將com.jdojo.mrjar.jdk8項目的源和庫屬性更改為JDK 8,並將 com.jdojo.mrjar.jdk9項目更改為JDK 9。這些項目的源代碼很簡單。 可以在TimeUtil類中創建一個靜態方法的getLocalDate()方法。 在這里作為一個實例方法,所以你可以看到在輸出(稍后討論)哪個版本的實例化。 運行Main類時,會打印當前的本地日期,當你運行此示例時結果可能會有所不同。

下面包含使用JDK 8的TimeUtilMain類的代碼。

// TimeUtil.java
package com.jdojo.mrjar;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public class TimeUtil {
    public TimeUtil() {
        System.out.println("Creating JDK 8 version of TimeUtil...");
    }
    public LocalDate getLocalDate(Instant now) {
        return now.atZone(ZoneId.systemDefault())
                  .toLocalDate();
    }
}
// Main.java
package com.jdojo.mrjar;
import java.time.Instant;
import java.time.LocalDate;
public class Main {
    public static void main(String[] args) {
        System.out.println("Inside JDK 8 version of Main.main()...");
        TimeUtil t = new TimeUtil();
        LocalDate ld = t.getLocalDate(Instant.now());
        System.out.println("Local Date: " + ld);
    }
}

下面包含使用JDK 9的TimeUtilMain類的代碼模塊聲明為com.jdojo.mrjar。

// module-info.java
module com.jdojo.mrjar {
    exports com.jdojo.mrjar;
}
// TimeUtil.java
package com.jdojo.mrjar;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
public class TimeUtil {
    public TimeUtil() {
        System.out.println("Creating JDK 9 version of TimeUtil...");
    }
    public LocalDate getLocalDate(Instant now) {
        return LocalDate.ofInstant(now, ZoneId.systemDefault());
    }
}
// Main.java
package com.jdojo.mrjar;
import java.time.Instant;
import java.time.LocalDate;
public class Main {
    public static void main(String[] args) {
        System.out.println("Inside JDK 9 version of Main.main()...");
        TimeUtil t = new TimeUtil();
        LocalDate ld = t.getLocalDate(Instant.now());
        System.out.println("Local Date: " + ld);
    }
}

這個例子的目的不是單獨運行這兩個類,而是將它們全部包裝在MRJAR中並運行它們。

JDK 9中的jar工具已得到增強,以支持創建MRJAR。 在JDK 9中,jar工具接受一個新的選項,叫做--release。 其語法如下:

jar <options> --release N <other-options>

這里,N是一個JDK主版本,如JDK 9中的9。N的值必須大於或等於9。所有在--release N選項之后的所有文件將被添加到 MRJAR的META-INF/versions/N目錄下。

以下命令創建名為com.jdojo.mrjar.jar的MRJAR,並將其放在C:\ Java9Revealed\mrjars目錄下,該目錄是已經存在的目錄:

C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
-C com.jdojo.mrjar.jdk8\build\classes .
--release 9 -C com.jdojo.mrjar.jdk9\build\classes .

請注意在此命令中使用--release 9選項。 來自com.jdojo.mrjar.jdk9\ build\classes目錄的所有文件將被添加到MRJAR中的META-INF/versions/9目錄中。 來自com.jdojo.mrjar.jdk8\build\classes目錄的所有文件將被添加到MRJAR的根目錄下。 MRJAR中的條目將如下所示:

- jar-root
  - com
    - jdojo
      - mrjar
        - Main.class
        - TimeUtil.class
- META-INF
  - MANIFEST.MF
  - versions
    - 9
      - module-info.class
      - com
        - jdojo
          - mrjar
            - Main.class
            - TimeUtil.class

在創建MRJAR時,使用--verbose選項在jar工具中非常有幫助。 該選項打印出許多有用的信息,幫助診斷錯誤。 以下是與以前相同的命令,但使用了--verbose選項。 輸出顯示哪些文件被復制以及它們的位置:

C:\Java9Revealed>jar --create --verbose --file mrjars\com.jdojo.mrjar.jar
-C com.jdojo.mrjar.jdk8\build\classes .
--release 9 -C com.jdojo.mrjar.jdk9\build\classes .

輸出信息為:

added manifest
added module-info: META-INF/versions/9/module-info.class
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/jdojo/(in = 0) (out= 0)(stored 0%)
adding: com/jdojo/mrjar/(in = 0) (out= 0)(stored 0%)
adding: com/jdojo/mrjar/Main.class(in = 1100) (out= 592)(deflated 46%)
adding: com/jdojo/mrjar/TimeUtil.class(in = 884) (out= 503)(deflated 43%)
adding: META-INF/versions/9/(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/.netbeans_automatic_build(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/.netbeans_update_resources(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/com/(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/com/jdojo/(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/com/jdojo/mrjar/(in = 0) (out= 0)(stored 0%)
adding: META-INF/versions/9/com/jdojo/mrjar/Main.class(in = 1328) (out= 689)(deflated 48%)
adding: META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class(in = 814) (out= 470)(deflated 42%)

假設要為MRJAR創建JDK的版本8,9和10。com.jdojo.mrjar.jdk10\build\classes目錄包含特定於JDK 10的類,則以下命令將執行該任務:

C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
-C com.jdojo.mrjar.jdk8\build\classes .
--release 9 -C com.jdojo.mrjar.jdk9\build\classes .
--release 10 -C com.jdojo.mrjar.jdk10\build\classes .

可以使用--list選項驗證MRJAR中的條目,如下所示:

C:\Java9Revealed>jar --list --file mrjars\com.jdojo.mrjar.jar

輸出結果為:

META-INF/
META-INF/MANIFEST.MF
com/
com/jdojo/
com/jdojo/mrjar/
com/jdojo/mrjar/Main.class
com/jdojo/mrjar/TimeUtil.class
META-INF/versions/9/
META-INF/versions/9/com/
META-INF/versions/9/com/jdojo/
META-INF/versions/9/com/jdojo/mrjar/
META-INF/versions/9/com/jdojo/mrjar/Main.class
META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class
META-INF/versions/9/module-info.class
META-INF/versions/10/
META-INF/versions/10/com/
META-INF/versions/10/com/jdojo/
META-INF/versions/10/com/jdojo/mrjar/
META-INF/versions/10/com/jdojo/mrjar/TimeUtil.class

假設有一個包含JDK 8的資源和類文件的JAR,並且希望通過為JDK 9添加資源和類文件來更新JAR以使其成為MRJAR。可以通過使用以下命令來更新JAR的內容: --update。 以下命令創建僅具有JDK 8文件的JAR:

C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
-C com.jdojo.mrjar.jdk8\build\classes .

以下命令更新JAR以使其成為MRJAR:

C:\Java9Revealed>jar --update --file mrjars\com.jdojo.mrjar.jar
--release 9 -C com.jdojo.mrjar.jdk9\build\classes .

看看這個MRJAR的運作。 以下命令運行com.jdojo.mrjar包中的Main類,將MRJAR放在類路徑上。 JDK 8用於運行類。

C:\Java9Revealed> c:\java8\bin\java -classpath mrjars\com.jdojo.mrjar.jar com.jdojo.mrjar.Main

輸出結果為:

Inside JDK 8 version of Main.main()...
Creating JDK 8 version of TimeUtil...
Local Date: 2017-06-27

輸出顯示,從MRJAR的根目錄使用了兩個類MainTimeUtil類,因為JDK 8不支持MRJAR。 以下命令使用模塊路徑運行相同的類。 在JDK 9中用於運行命令:

C:\Java9Revealed> c:\java9\bin\java --module-path mrjars\com.jdojo.mrjar.jar --module com.jdojo.mrjar/com.jdojo.mrjar.Main

輸出結果為:

Inside JDK 9 version of Main.main()...
Creating JDK 9 version of TimeUtil...
Local Date: 2017-06-27

輸出顯示,從MRJAR的META-INF/versions/9目錄中使用了兩個類MainTimeUtil,因為JDK 9支持MRJAR,MRJAR具有JDK 9特有的這些類的版本。

讓我們給這個MRJAR一點點變化。 創建具有相同內容的MRJAR,但在META-INF/versions/9目錄中沒有Main.class文件。 在現實世界的場景中,只有TimeUtil類在應用程序的JDK 9版本中發生變化,因此不需要為JDK 9打包Main類。JDK 8的Main類也可用於JDK 9。 以下命令打包我們上次執行的所有操作,除了JDK 9的Main類之外。生成的MRJAR命名為com.jdojo.mrjar2.jar。

C:\Java9Revealed>jar --create --verbose --file mrjars\com.jdojo.mrjar2.jar
-C com.jdojo.mrjar.jdk8\build\classes .
--release 9
-C com.jdojo.mrjar.jdk9\build\classes module-info.class
-C com.jdojo.mrjar.jdk9\build\classes com\jdojo\mrjar\TimeUtil.class

可以使用以下命令驗證新MRJAR的內容:

C:\Java9Revealed>jar --list --file mrjars\com.jdojo.mrjar2.jar

輸出的結果為:

META-INF/
META-INF/MANIFEST.MF
META-INF/versions/9/module-info.class
com/
com/jdojo/
com/jdojo/mrjar/
com/jdojo/mrjar/Main.class
com/jdojo/mrjar/TimeUtil.class
META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class

如果在JDK 8上運行Main類,將獲得與以前相同的輸出。 但是,在JDK 9上運行它會給你一個不同的輸出:
命令行命令:

C:\Java9Revealed>c:\java9\bin\java --module-path mrjars\com.jdojo.mrjar2.jar --module com.jdojo.mrjar/com.jdojo.mrjar.Main

輸出結果為:

Inside JDK 8 version of Main.main()...
Creating JDK 9 version of TimeUtil...
Local Date: 2017-06-27

輸出顯示Main類是在JAR根目錄使用的,而TimeUtil類使用的是META-INF/versions/9目錄下的。

4. 多版本JAR的規則

創建多版本JAR時,需要遵循一些規則。 如果你犯了一個錯誤,jar工具會打印錯誤信息。 有時,錯誤信息不直觀。最好使用--verbose選項運行jar工具來獲取有關錯誤的更多詳細信息。

大多數規則基於一個事實:MRJAR包含用於多個JDK平台的類庫(或應用程序)的同一個版本的API。 例如,有一個名為jdojo-lib-1.0.jar的MRJAR,可能包含名為jdojo-lib的庫的1.0版本,該庫可能使用JDK 8和JDK 9中的API。這意味着該MRJAR應該提供一份相同的API(在公共類型及其公共成員方面),用在JDK 8的類路徑上,或JDK 9的類路徑或模塊路徑上。 如果MRJAR在JDK 8和JDK 9上提供不同的API,那么這不是有效的MRJAR。

二. 模塊化多版本JAR

MRJAR可以是模塊化JAR,在這種情況下,它可以在根目錄中包含一個模塊描述符module-info.class,在一個或多個版本化目錄中,或兩者的組合。 版本化描述符必須與根模塊描述符相同,但有一些例外:

  • 一個版本描述符可以有不同的以java.* jdk.*開頭模塊的非傳遞性requires 語句。
  • 不同的模塊描述符對於非JDK模塊不能有不同的非傳遞性requires語句。
  • 一個版本描述符可以有不同的uses語句。

這些規則是基於實現細節的變化是允許的,但是API本身並不是這樣。 允許對非JDK模塊的requires語句的更改被認為是API中的一個變化 —— 它要求你為不同版本的JDK擁有不同的用戶自定義的模塊。 這就是為什么這不允許的原因。

模塊化MRJAR不需要在根目錄中有一個模塊描述符。 這是我們在上一節的例子中所說的。 我們在根目錄中沒有模塊描述符,但在META-INF/versions/9目錄中有一個描述符。 這種安排可以在一個MRJAR中具有用於JDK 8的非模塊化代碼和用於JDK 9的模塊代碼。

三. 模塊化多版本JAR和封裝

如果在版本目錄(在根目錄中不存在)中添加新的公共類型,則在創建MRJAR時收到錯誤。假設在我們的示例中,為JDK 9版本添加一個名為Test的公共類。如果Test類在com.jdojo.mrjar包中,它將被該模塊導出,並且可用於MRJAR之外的代碼。請注意,根目錄不包含Test類,因此此MRJAR為JDK 8和JDK 9提供不同的公共API。在這種情況下,當您創建MRJAR時,在JDK 9的com.jdojo.mrjar包中添加公共Test類將會生成錯誤。

繼續使用相同的示例,假設將Test類添加到JDK 9的com.jdojo.test包中。請注意,該模塊不導出此包。當在模塊路徑上使用此MRJAR時,Test類將無法訪問外部代碼。在這個意義上,這個MRJAR為JDK 8和JDK 9提供了相同的公共API。但是,有一個隱情!你也可以將此MRJAR放在JDK 9中的類路徑上,在這種情況下,外部代碼可以訪問Test類,但這是對模塊化封裝的一種違反,以及違反MRJAR應該提供相同公共API的規則跨越不同的JDK版本。因此,不允許在MRJAR中為模塊添加公共類型到未導出的包。如果嘗試這樣做,將收到類似於以下內容的錯誤消息:

entry: META-INF/versions/9/com/jdojo/test/Test.class, contains a new public class not found in base entries
invalid multi-release jar file mrjars\com.jdojo.mrjar.jar deleted

有時,需要為同一個類庫添加更多類型來支持較新版本的JDK。 必須添加這些類型才能支持較新的實現。 可以通過將包級別的私有類型添加到MRJAR中的版本化目錄中來實現。 在這個例子中,如果使類非公開類型,可以添加JDK 9的Test類。

四. 多版本JAR和引導加載器

引導加載器不支持多版本JAR,例如,使用-Xbootclasspath / a選項指定MRJAR。支持這個將使得很少需要的功能的引導加載器實現變得復雜化。

五. 相同JDK版本的文件

MRJAR應該在版本目錄中包含相同文件的不同版本。 如果資源或類文件在不同的平台版本中是相同的,那么這樣一個文件應該被添加到根目錄。 目前,如果jar工具在具有相同內容的多版本目錄中看到相同的條目,則會發出警告。

讓我們看看這個規則的實際效果。 將com.jdojo.mrjar.jdk9\build\目錄的內容復制到com.jdojo.mrjar.jdk10\build\classes目錄,因此這兩個目錄具有相同的內容。 運行以下命令創建一個包含JDK版本8,9和10的代碼的MRJAR。請注意,版本化目錄9和10中的文件將是相同的。 執行命令如下。

C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
-C com.jdojo.mrjar.jdk8\build\classes .
--release 9 -C com.jdojo.mrjar.jdk9\build\classes .
--release 10 -C com.jdojo.mrjar.jdk10\build\classes .

報出的警告信息為:

Warning: entry META-INF/versions/9/com/jdojo/mrjar/Main.class contains a class that
is identical to an entry already in the jar
Warning: entry META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class contains a class that
is identical to an entry already in the jar

1. 多版本JAR和JAR URL

在MRJAR之前,JAR中的所有資源都存在於根目錄中。 當從類加載器(ClassLoader.getResource(“com/jdojo /mrjar/TimeUtil.class”))請求資源時,返回的URL類似於以下內容:

jar:file:/C:/Java9Revealed/mrjars/com.jdojo.mrjar.jar! com/jdojo/mrjar/TimeUtil.class
With MRJARs, a resource may be returned from the root directory or from a versioned directory. If you are looking for the TimeUtil.class file on JDK 9, the URL will be as follows:
jar:file:/C:/Java9Revealed/mrjars/com.jdojo.mrjar.jar!/META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class

如果你現有的代碼期望特定格式的資源的jar文件的URL,或者手工編寫了了一個URL,當與MRJAR一起工作時,可能會獲得意想不到的結果。如果正在使用MRJAR重新打包JAR,則需要再次查看代碼並將其更改,能夠在MRJAR下工作。

2. 多版本Manifest屬性

MRJAR在其MANIFEST.MF文件中包含一個特殊屬性:

Multi-Release: true

Multi-Release屬性在使用jar工具創建MRJAR時添加。 如果此屬性的值為true,則表示JAR是多版本JAR。 如果其值為false或屬性缺失,則不是多版本JAR。

名為MULTI_RELEASE的新常量已添加到Attributes.Name類(位於java.util.jar包中),以表示manifest 文件中的新屬性Multi-Release。 因此,Attributes.Name.MULTI_RELEASE常量表示Java代碼中Multi-Release屬性的值。

六. JMOD格式

JDK 9引入了一種稱為JMOD的新格式來封裝模塊。 JMOD文件旨在處理比JAR文件更多的內容類型。 JMOD文件可以打包本地代碼,配置文件,本地命令和其他類型的數據。 目前,JMOD格式基於ZIP格式,將在將來會發生變化。 JDK 9模塊以JMOD格式打包,可以在編譯時和鏈接時使用。 運行時不支持JMOD格式。 可以在JDK_HOME\jmods目錄中找到它們,其中JDK_HOME是安裝JDK 9的目錄。可以使用JMOD格式打包自己的模塊。 JMOD格式的文件具有.jmod擴展名。 例如,名為java.base的平台模塊已打包在java.base.jmod文件中。

JMOD文件可以包含本地代碼,這在運行時提取和鏈接有點棘手。 這就是為什么JMOD文件在編譯時和鏈接時都被支持,運行時卻不可以。

1. 使用jmod工具

JDK 9附帶了一個名為jmod的新工具。 它位於JDK_HOME\bin目錄中。 它可以用於創建一個JMOD文件,列出一個JMOD文件的內容,打印一個模塊的描述,並記錄使用的模塊的哈希值。 使用jmod工具的一般語法如下:

jmod <subcommand> <options> <jmod-file>

其中在jmod命令中至少包含以下一個子命令:

  • create
  • extract
  • list
  • describe
  • hash

list和describe子命令不接受任何選項。 <jmod-file>是要創建的JMOD文件或要描述的現有JMOD文件。 下面包含該工具支持的選項列表。

選項 描述
--class-path <path> 指定可以找到要打包的類的類路徑。 <path>可以是包含應用程序類的JAR文件或目錄的路徑列表。 <path>中的內容將被復制到JMOD文件。
--cmds <path> 指定包含本地命令的目錄列表,這些目錄需要復制到JMOD文件中。
--config <path> 指定包含要復制到JMOD文件的用戶可編輯配置文件的目錄列表。
--dir <path> 指定提取指定的JMOD文件的內容的目標目錄。
--do-not-resolve-by-default 如果使用此選項創建JMOD文件,JMOD文件中包含的模塊將從默認的根模塊中排除。 要解決此類模塊,必須使用 --add-modules命令行選項將其添加到默認的根模塊中。
--dry-run 模塊的哈希值。 使用此選項可以計算和打印哈希值,但不將它們記錄在JMOD文件中。
--exclude <pattern-list> 排除與提供的逗號分隔模式列表匹配的文件,每個元素使用以下格式之一: <glob-pattern>glob:<glob-pattern>,或 regex:<regex-pattern>
--hash-modules <regex-pattern> 計算和記錄哈希值,以將打包的模塊與符合給定的<regex-pattern>的模塊進行綁定,並直接或間接依賴於它。 在正在創建的JMOD文件中記錄哈希值,或者在使用jmod哈希命令指定的模塊路徑上的JMOD文件或模塊化JAR。
--help, -h 打印使用說明和jmod命令的所有選項列表。
--header-files <path> 將路徑列表指定為<path>,將要復制到JMOD文件的本機代碼的頭文件放置於此。
--help-extra 打印jmod工具支持的其他選項的幫助信息。
--legal-notices <path> 指定要復制到JMOD文件的合法聲明的位置。
--libs <path> 指定包含要復制到JMOD文件的本地類庫的目錄列表。
--main-class <class-name> 指定要用於運行應用程序的主類名稱。
--man-pages <path> 指定手冊主頁的位置。
--module-version <version> 指定要記錄在module-info.class文件中的模塊版本。
--module-path <path>, -p <path> 指定找到散列模塊的模塊路徑。
--os-arch <os-arch> 指定要記錄在module-info.class文件中的操作系統體系結構。
--os-name <os-name> 指定要記錄在module-info.class文件中的操作系統名稱。
--version 打印jmod 工具的版本。
--warn-if-resolved <reason> 指定一個jmod工具的提示,如果一個模塊被解決,發出一個警告。 <reason>的值可能是三種: deprecated, deprecated-for-remova, 或incubating
@<filename> 從指定的文件中讀取選項。

以下部分將詳細介紹如何使用jmod命令。 本章中使用的所有命令均輸入一行。 有時候,為了顯示更加清晰,將它們顯示在多行上。

七. 創建JMOD文件

你可以使用jmod工具命令的子命令create來創建一個jmod文件。一個jmod文件的內容是一個模塊的內容。假設下列目錄和文件已經存在:

  • C:\Java9Revealed\jmods
  • C:\Java9Revealed\lib\com.jdojo.prime.jar

以下命令在C:\ Java9Revealed\jmods目錄中創建一個com.jdojo.prime.jmod文件。 JMOD文件的內容來自com.jdojo.prime.jar文件。

C:\Java9Revealed>jmod create --class-path lib\com.jdojo.prime.jar
jmods\com.jdojo.prime.jmod

通常,JMOD文件的內容來自包含模塊編譯代碼的一系列目錄。 以下命令創建一個com.jdojo.prime.jmod文件。它的內容來自一個mods\com.jdojo.prime目錄。 該命令使用--module-version選項來設置將記錄在com.jdojo.prime\build\classes目錄中的module-info.class文件中的模塊版本。 確保刪除在上一步中創建的JMOD文件。

C:\Java9Revealed>jmod create --module-version 1.0
  --class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod

你可以用這個JMOD文件做什么? 可以將其放在模塊路徑上,以便在編譯時使用它。 可以使用它與jlink工具來創建可用於運行應用程序的自定義運行時映像。 回想一下,你不能在運行時使用它。 如果嘗試在運行時使用JMOD文件放在模塊路徑上,將收到以下錯誤:

Error occurred during initialization of VM
java.lang.module.ResolutionException: JMOD files not supported: jmods\com.jdojo.prime.jmod

八. 提取JMOD文件內容

可以使用extract子命令提取JMOD文件的內容。 以下命令將jmods\com.jdojo.prime.jmod文件的內容提取到名為extract的目錄中。

C:\Java9Revealed>jmod extract --dir extracted jmods\com.jdojo.prime.jmod

如果沒有--dir選項,則JMOD文件的內容直接提取到當前目錄下。

九. 列出JMOD文件內容

可以使用list子命令與jmod工具打印JMOD文件中所有條目的名稱。 以下命令列出了在上一節中創建的com.jdojo.prime.jmod文件的內容:

C:\Java9Revealed>jmod list jmods\com.jdojo.prime.jmod

以下命令列出了java.base.jmod的JMOD文件發布的java.base模塊的內容。 該命令假設已經在C:\java9目錄中安裝了JDK 9。 輸出超過120頁。 下面顯示的是部分輸出。 請注意,JMOD文件內部將不同類型的內容存儲在不同的目錄中。

C:\Java9Revealed>jmod list C:\java9\jmods\java.base.jmod

輸出結果為(部分內容):

classes/module-info.class
classes/java/nio/file/WatchEvent.class
classes/java/nio/file/WatchKey.class
bin/java.exe
bin/javaw.exe
native/amd64/jvm.cfg
native/java.dll
conf/net.properties
conf/security/java.policy
conf/security/java.security
...

十. 描述一個JMOD文件

可以使用describe子命令與jmod工具來描述JMOD文件中包含的模塊。 以下命令描述com.jdojo.prime.jmod文件中包含的模塊:

C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod

輸出結果為:

com.jdojo.prime@1.0
  requires mandated java.base
  uses com.jdojo.prime.PrimeChecker
  exports com.jdojo.prime

可以使用此命令來描述平台模塊。 以下命令描述了java.sql.jmod中包含的模塊,假設已在C:\java9目錄中安裝了JDK 9:

C:\Java9Revealed>jmod describe C:\java9\jmods\java.sql.jmod
java.sql@9-ea

輸出結果為:

 requires mandated java.base
  requires transitive java.logging
  requires transitive java.xml
  uses java.sql.Driver
  exports java.sql
  exports javax.sql
  exports javax.transaction.xa
  operating-system-name Windows
  operating-system-architecture amd64

十一. 記錄模塊哈希值

可以使用jmod工具的hash子命令記錄其他模塊的哈希值,包含在一個jmod文件模塊的module-info.class文件中。哈希值將用於以后的依賴性驗證。假設你在四個jmod文件中有四個模塊:

  • com.jdojo.prime
  • com.jdojo.prime.generic
  • com.jdojo.prime.faster
  • com.jdojo.prime.client

假設希望將這些模塊提供給給客戶,並確保模塊代碼保持不變。可以通過為這四個模塊記錄哈希值來實現。讓我們看看如何做到這一點。

如果要計算其他模塊的哈希值,首先jmod能夠找到這些模塊。 你需要使用 --module-path選項在模塊路徑中,以便找到其他模塊。 同時,也需要使用--hash-modules選項來指定要使用的哈希值記錄模塊的模式列表。

Tips
當你把一個模塊打包成一個JAR格式時,可以在jar命令中使用--hash-modules--module-path來記錄依賴模塊的哈希值。

使用以下四個命令為四個模塊創建jmod文件。在創建com.jdojo.prime.client.jmod時使用了--main-class選項。 如果在運行這些命令時,給你一個“file already exists”的錯誤,從jmods目錄刪除現有的jmod文件並重新運行命令。

C:\Java9Revealed>jmod create --module-version 1.0
--class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod
C:\Java9Revealed>jmod create --module-version 1.0
--class-path com.jdojo.prime.generic\build\classes
jmods\com.jdojo.prime.generic.jmod
C:\Java9Revealed>jmod create --module-version 1.0
--class-path com.jdojo.prime.faster\build\classes
jmods\com.jdojo.prime.faster.jmod
C:\Java9Revealed>jmod create --main-class com.jdojo.prime.client.Main
--module-version 1.0
--class-path com.jdojo.prime.client\build\classes
jmods\com.jdojo.prime.client.jmod

現在你准備好記錄名字以com.jdojo.prime的模塊的哈希值。在com.jdojo.prime模塊中使用下面的命令:

C:\Java9Revealed>jmod hash --module-path jmods
  --hash-modules com.jdojo.prime.? jmods\com.jdojo.prime.jmod

讓我們看看記錄在com.jdojo.prime模塊的哈希值。下面命令打印記錄有哈希值的模塊描述:

C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod

輸出結果為:

com.jdojo.prime@1.0
  requires mandated java.base
  uses com.jdojo.prime.PrimeChecker
  exports com.jdojo.prime
  hashes com.jdojo.prime.client SHA-256 2ffb0d4413501e389d6712450bd138bbe82ca8abeb4e8b5d29b0c307d90a2e91
  hashes com.jdojo.prime.faster SHA-256 687e07c429080c48bed89a649dca20fa26dc28fab88a4905f1b5070560622a0c
  hashes com.jdojo.prime.generic SHA-256 f24556ef69c4345ad7a8e5e59d31ea2d52c8749714ede0c0dedf128255450708

當你使用create子命令創建一個新的jmod文件時,也可以記錄其他模塊的哈希值。假設三個模塊com.jdojo.prime.genericcom.jdojo.prime.faster,和com.jdojo.prime.client存在於模塊的路徑,你可以使用下面的命令來創建com.jdojo.prime.jmod文件同時也記錄了其他三個模塊的哈希值:

C:\Java9Revealed>jmod create --module-version 1.0
--module-path jmods
--hash-modules com.jdojo.prime.?
--class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod

可以使用--dry-run選項運行哈希過程的JMOD文件,其中將打印哈希值,但不會記錄。 此選項有助於確保所有設置是正確的,而無需創建JMOD文件。 以下的命令順序將會引導你完成整個過程。 首先,刪除在上一步中創建的jmods\com.jdojo.prime.jmod文件。

以下命令創建jmods\com.jdojo.prime.jmod文件,而不會為其他模塊記錄哈希值:

C:\Java9Revealed>jmod create --module-version 1.0
--module-path jmods
--class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod

以下命令運行hash --dry-run子命令。 它計算並打印其他模塊的哈希值,與--hash-modules選項中指定的正則表達式匹配。 jmods\com.jdojo.prime.jmod文件中不會記錄哈希值。

C:\Java9Revealed>jmod hash --dry-run --module-path jmods
 --hash-modules com.jdojo.prime.? jmods\com.jdojo.prime.jmod

輸出結果為:

Dry run:
com.jdojo.prime
  hashes com.jdojo.prime.client SHA-256 2ffb0d4413501e389d6712450bd138bbe82ca8abeb4e8b5d29b0c307d90a2e91
  hashes com.jdojo.prime.faster SHA-256 687e07c429080c48bed89a649dca20fa26dc28fab88a4905f1b5070560622a0c
  hashes com.jdojo.prime.generic SHA-256 f24556ef69c4345ad7a8e5e59d31ea2d52c8749714ede0c0dedf128255450708

以下命令驗證上一個命令在JMOD文件中沒有記錄哈任何希值:

C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod

輸出結果為:

com.jdojo.prime@1.0
  requires mandated java.base
  uses com.jdojo.prime.PrimeChecker
  exports com.jdojo.prime

十二. 總結

JDK 9支持四種格式來打包模塊:展開的目錄,JAR文件,JMOD文件和JIMAGE文件。 JAR格式在JDK 9中得到了增強,以支持模塊化JAR和多版本JAR。多版本JAR允許你打包相同版本的類庫或面向JDK不同版本的應用程序。例如,多版本JAR可能包含包含JDK 8和JDK 9代碼的庫版本為1.2的代碼。當在JDK 8上使用多版本JAR時,將使用JDK 8版本的庫代碼。當它在JDK 9上使用時,將使用JDK 9版本的庫代碼。特定於JDK版本N的文件存儲在多版本JAR的META-INF\versions\N目錄中。所有JDK版本通用的文件都存儲在根目錄中。對於不支持多版本JAR的環境,此類JAR將被視為常規JAR。在多版本JAR中,文件的搜索順序是不同的,所有從當前平台的主版本開始的所有版本化目錄都在根目錄之前進行搜索。

JMOD文件旨在處理比JAR文件更多的內容類型。它們可以打包本地代碼,配置文件,本地命令和其他類型的數據。目前,JMOD格式基於ZIP格式,將在將來會發生變化。 JDK 9模塊以JMOD格式打包,可以在編譯時和鏈接時使用。運行時不支持JMOD格式。可以使用jmod工具來處理JMOD文件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM