做一個(gè)終身學(xué)習(xí)的人。

在這章節(jié)中, 主要介紹以下內(nèi)容:

  • 封裝Java模塊的不同格式

  • JAR格式增強(qiáng)

  • 什么是多版本JAR

  • 如何創(chuàng)建和使用多版本JAR

  • JMOD是什么格式

  • 如何使用jmod工具來(lái)處理JMOD文件

  • 如何創(chuàng)建、解壓和描述JMOD文件

  • 如何列出JMOD文件的內(nèi)容

  • 如何在JMOD文件中記錄模塊的哈希值以進(jìn)行依賴(lài)性驗(yàn)證

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

  • 展開(kāi)的目錄

  • JAR格式

  • JMOD格式

  • JIMAGE格式

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

一. JAR格式

在前面的第三者介紹了如何在jar工具中使用新的選項(xiàng)來(lái)創(chuàng)建模塊化的JAR。 jar工具還用于列出JAR文件中的條目,并提取和更新JAR文件的內(nèi)容。 該jar工具在JDK 9之前已經(jīng)支持這些操作,并且在JDK 9中沒(méi)有任何新的操作。在本章中,將介紹添加到JAR格式的新功能,稱(chēng)為多版本JAR。

1. 什么是多版本JAR

作為一名經(jīng)驗(yàn)豐富的Java開(kāi)發(fā)人員,你必須使用Java類(lèi)庫(kù)/框架,例如Spring框架,Hibernate等。您可能正在使用Java 8,但這些類(lèi)庫(kù)可能仍然在Java 6或Java 7中使用。為什么類(lèi)庫(kù)開(kāi)發(fā)人員不能使用最新版的JDK來(lái)使用新功能? 其中一個(gè)原因是不是所有的類(lèi)庫(kù)使用者都使用最新的JDK。 更新類(lèi)庫(kù)以使用較新版本的JDK意味著強(qiáng)制所有類(lèi)庫(kù)用戶遷移到較新的JDK,這在實(shí)踐中是不可能的。 維護(hù)和發(fā)布針對(duì)不同JDK的類(lèi)庫(kù)是打包代碼時(shí)的另一個(gè)痛苦。 通常,你將會(huì)找到一個(gè)用于不同JDK的單獨(dú)的庫(kù)JAR。 JDK 9通過(guò)為類(lèi)庫(kù)開(kāi)發(fā)人員提供一種打包類(lèi)庫(kù)代碼的新方法來(lái)解決這個(gè)問(wèn)題,使用單個(gè)JAR包含多個(gè)JDK的類(lèi)庫(kù)的相同版本。 這樣的JAR被稱(chēng)為多版本JAR。

多版本JAR(MRJAR)包含與多個(gè)JDK版本相同版本的類(lèi)庫(kù)(提供相同的API)。 也就是說(shuō),可以將類(lèi)庫(kù)作為可用于JDK 8和JDK 9的MRJAR。MRJAR中的代碼將包含在JDK 8和JDK 9中編譯的類(lèi)文件。使用JDK 9編譯的類(lèi)可以利用JDK 9提供的API,而使用JDK 8編譯的類(lèi)可以提供使用JDK 8編寫(xiě)的相同的類(lèi)庫(kù)API。

MRJAR擴(kuò)展了JAR的已有的目錄結(jié)構(gòu)。 JAR包含其所有內(nèi)容所在的根目錄。 它包含一個(gè)META-INF目錄,用于存儲(chǔ)有關(guān)JAR的元數(shù)據(jù)。 通常,JAR包含包含其屬性的META-INF/MANIFEST.MF文件。 典型的JAR中的條目如下所示:

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

JAR包含四個(gè)class文件和一個(gè)MANIFEST.MF文件。 MRJAR擴(kuò)展了META-INF目錄以存儲(chǔ)特定于JDK版本的類(lèi)。 META-INF目錄包含一個(gè)版本子目錄,其中可能包含許多子目錄,每個(gè)目錄命名與JDK主要版本相同。 例如,對(duì)于特定于JDK 9的類(lèi),可能有META-INF/versions/9目錄,對(duì)于JDK 10特定的類(lèi),可能有一個(gè)名為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的環(huán)境中使用,則將被視為常規(guī)JAR ——根目錄中的內(nèi)容將被使用,META-INF/version/9和 META-INF/versions/10目錄下的類(lèi)將被忽略。 因此,如果這個(gè)MRJAR與JDK 8一起使用,則只能使用四個(gè)類(lèi):C1,C2,C3和C4。

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

同樣,如果使用JDK 10,MRJAR會(huì)覆蓋類(lèi)C1和C2類(lèi),并且包含JDK版本10的名為C6的新類(lèi)。

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

  • JDK的主版本是針對(duì)使用MRJAR的環(huán)境決定的。 這里假設(shè)JDK的主版本是N。

  • 要查找名為R的資源或類(lèi)文件,從版本N的目錄搜索META-INF/versions目錄下的特定平臺(tái)的子目錄。

  • 如果在子目錄N中找到R,則返回。 否則搜索低于版本N的子目錄。 對(duì)于META-INF/versions目錄下的所有子目錄,此過(guò)程將繼續(xù)。

  • 當(dāng)在META-INF/versions/N子目錄中找不到R時(shí),將搜索MRJAR的根目錄。

我們來(lái)看一下使用以前顯示的MRJAR結(jié)構(gòu)的例子。 假設(shè)程序正在尋找C3.class,當(dāng)前版本的JDK是10,搜索將從META-INF/versions/10開(kāi)始,其中找不到C3.class。 在META-INF/versions/9中繼續(xù)搜索,其中找不到C3.class。 現(xiàn)在搜索繼續(xù)在根目錄中,最后找到C3.class。

另一個(gè)例子,假設(shè)你想在JDK版本為10時(shí)找到C2.class。搜索從META-INF/versions/10開(kāi)始,其中找到并返回C2.class。

另一個(gè)例子,假設(shè)你想在JDK版本為9時(shí)找到C2.class。搜索從META-INF/versions/9開(kāi)始,其中找到并返回C2.class。

另一個(gè)例子,假設(shè)你想在JDK版本為8時(shí)找到C2.class。沒(méi)有名為META-INF/versions/8的JDK 8特定目錄。 因此,搜索從根目錄開(kāi)始,找到并返回C2.class。

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

2. 創(chuàng)建多版本JAR

當(dāng)在特定的JDK版本中搜索資源或類(lèi)文件后,我們已經(jīng)知道MRJAR中目錄的搜索順序,很容易了解如何找到類(lèi)和資源。 有關(guān)JDK版本特定目錄內(nèi)容的一些規(guī)則。 將在后面的章節(jié)中描述這些規(guī)則。 在本節(jié)中,將重點(diǎn)介紹創(chuàng)建MRJAR。

要運(yùn)行此示例,需要在計(jì)算機(jī)上安裝JDK 8和JDK 9。 如果沒(méi)有JDK 8,除JDK 9之外的任何其他JDK都可以。 對(duì)于除版本8以外的JDK,將需要更改示例中的代碼,因此代碼將使用你本地版本的JDK進(jìn)行編譯。

使用MRJAR來(lái)存儲(chǔ)應(yīng)用程序的JDK 8和JDK 9版本。 該應(yīng)用程序由以下兩個(gè)類(lèi)組成:

com.jdojo.mrjar.Maincom.jdojo.mrjar.TimeUtil

Main類(lèi)創(chuàng)建一個(gè)TimeUtil類(lèi)的對(duì)象,并調(diào)用它的一個(gè)方法。Main類(lèi)可以用作運(yùn)行應(yīng)用程序的主類(lèi)。 TimeUtil類(lèi)包含一個(gè)getLocalDate(Instant now)方法,它將Instant作為參數(shù),并返回一個(gè)LocalDate類(lèi)來(lái)表示當(dāng)前時(shí)區(qū)的時(shí)間。 JDK 9已經(jīng)為LocalDate類(lèi)添加了一個(gè)新方法,它被命名為ofInstant(Instant instant, ZoneId zone)。 我們將更新應(yīng)用程序以使用JDK 9并利用這種新方法,并保留使用JDK 8 中實(shí)現(xiàn)相同功能的Time API的舊應(yīng)用程序。

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

下面包含使用JDK 8的TimeUtilMain類(lèi)的代碼。

// TimeUtil.javapackage 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.javapackage 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類(lèi)的代碼模塊聲明為com.jdojo.mrjar。

// module-info.javamodule com.jdojo.mrjar {
    exports com.jdojo.mrjar;
}
// TimeUtil.javapackage 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.javapackage 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);
    }
}

這個(gè)例子的目的不是單獨(dú)運(yùn)行這兩個(gè)類(lèi),而是將它們?nèi)堪b在MRJAR中并運(yùn)行它們。

JDK 9中的jar工具已得到增強(qiáng),以支持創(chuàng)建MRJAR。 在JDK 9中,jar工具接受一個(gè)新的選項(xiàng),叫做--release。 其語(yǔ)法如下:

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

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

以下命令創(chuàng)建名為com.jdojo.mrjar.jar的MRJAR,并將其放在C:\ Java9Revealed\mrjars目錄下,該目錄是已經(jīng)存在的目錄:

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 .

請(qǐng)注意在此命令中使用--release 9選項(xiàng)。 來(lái)自com.jdojo.mrjar.jdk9\ build\classes目錄的所有文件將被添加到MRJAR中的META-INF/versions/9目錄中。 來(lái)自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

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

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.classadding: 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%)

假設(shè)要為MRJAR創(chuàng)建JDK的版本8,9和10。com.jdojo.mrjar.jdk10\build\classes目錄包含特定于JDK 10的類(lèi),則以下命令將執(zhí)行該任務(wù):

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選項(xiàng)驗(yàn)證MRJAR中的條目,如下所示:

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

輸出結(jié)果為:

META-INF/
META-INF/MANIFEST.MF
com/
com/jdojo/com/jdojo/mrjar/
com/jdojo/mrjar/Main.classcom/jdojo/mrjar/TimeUtil.classMETA-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.classMETA-INF/versions/9/com/jdojo/mrjar/TimeUtil.classMETA-INF/versions/9/module-info.classMETA-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

假設(shè)有一個(gè)包含JDK 8的資源和類(lèi)文件的JAR,并且希望通過(guò)為JDK 9添加資源和類(lèi)文件來(lái)更新JAR以使其成為MRJAR。可以通過(guò)使用以下命令來(lái)更新JAR的內(nèi)容: --update。 以下命令創(chuàng)建僅具有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 .

看看這個(gè)MRJAR的運(yùn)作。 以下命令運(yùn)行com.jdojo.mrjar包中的Main類(lèi),將MRJAR放在類(lèi)路徑上。 JDK 8用于運(yùn)行類(lèi)。

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

輸出結(jié)果為:

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

輸出顯示,從MRJAR的根目錄使用了兩個(gè)類(lèi)MainTimeUtil類(lèi),因?yàn)镴DK 8不支持MRJAR。 以下命令使用模塊路徑運(yùn)行相同的類(lèi)。 在JDK 9中用于運(yùn)行命令:

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

輸出結(jié)果為:

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

輸出顯示,從MRJAR的META-INF/versions/9目錄中使用了兩個(gè)類(lèi)MainTimeUtil,因?yàn)镴DK 9支持MRJAR,MRJAR具有JDK 9特有的這些類(lèi)的版本。

讓我們給這個(gè)MRJAR一點(diǎn)點(diǎn)變化。 創(chuàng)建具有相同內(nèi)容的MRJAR,但在META-INF/versions/9目錄中沒(méi)有Main.class文件。 在現(xiàn)實(shí)世界的場(chǎng)景中,只有TimeUtil類(lèi)在應(yīng)用程序的JDK 9版本中發(fā)生變化,因此不需要為JDK 9打包Main類(lèi)。JDK 8的Main類(lèi)也可用于JDK 9。 以下命令打包我們上次執(zhí)行的所有操作,除了JDK 9的Main類(lèi)之外。生成的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

可以使用以下命令驗(yàn)證新MRJAR的內(nèi)容:

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

輸出的結(jié)果為:

http://www.cnblogs.com/IcanFixIt/p/7095839.html