博文目錄
文章目錄
- Module System
- 原因
- JDK 模塊化
- 模塊描述文件
- 關鍵字
- 啟用模塊化
- 測試
- 結論
- Multi-Release jar (MRJAR)
- 原因
- 原理
- 結論
- 用 IDEA 創建多版本兼容 Jar
- 項目結構
- pom.xml
- 測試
Module System
原因
Java 9引入了模塊化系統的主要原因是為了解決Java平臺面臨的復雜性和可維護性方面的挑戰。以下是一些采用模塊化系統的主要原因:
- 更好的代碼組織:傳統的Java應用程序通常由大量的JAR文件組成,這些JAR文件之間的依賴關系可能變得非常復雜。模塊化系統允許開發人員將代碼組織為模塊,將相關的類和資源放在一起,使代碼更易于理解和維護。模塊化還提供了更細粒度的可見性和封裝,使開發人員能夠更好地控制代碼的可訪問性。
- 更好的可重用性:模塊化系統鼓勵開發人員將功能劃分為獨立的模塊,這些模塊可以在不同的項目中重復使用。通過明確聲明模塊之間的依賴關系,模塊化系統提供了更好的可重用性和解耦性。
- 更好的性能和安全性:模塊化系統允許JVM在運行時僅加載所需的模塊,而不是加載整個類路徑上的所有類。這可以提高應用程序的啟動時間和內存利用率。此外,模塊化系統通過明確聲明模塊之間的依賴關系,可以提供更好的安全性,防止不受信任的代碼訪問和修改模塊之間的內部實現。
- 改進的可擴展性:模塊化系統引入了明確的導出和使用規范,使開發人員能夠更好地定義模塊之間的接口和依賴關系。這使得應用程序的功能可以更容易地進行擴展和定制,同時保持模塊之間的清晰界限。
總的來說,Java 9的模塊化系統提供了一種更好的方式來組織、重用和管理Java代碼,改善了傳統的類路徑模型面臨的復雜性和可維護性問題。它提供了更好的可見性、封裝性、性能、安全性和可擴展性,從而改進了Java開發的體驗和應用程序的質量。
JDK 模塊化
自 Java 9 起, JDK 自身也被模塊化, 按照功能劃分為一系列有依賴關系的模塊
- java.base: 核心模塊, 最基礎模塊, 其他模塊都會依賴到它. 包含基本類庫和運行時環境, 如 Object, String, 集合框架, 輸入輸出, 并發工機具等, 是構建 Java 應用程序的基礎
- java.se: 這個模塊表示 Java 標準版 (Java SE). 它是一個聚合模塊 (只有模塊描述文件 module-info.java), 其中引入了許多常用的模塊, 包含了大部分 Java SE 規范中定義的 API. 就相當于以前的 rt.jar
# 查看模塊列表
java --list-modules
# 查看模塊信息 (module-info.java)
java -d <模塊名稱>
java --describ-module <模塊名稱>
以下是 JDK 11.0.20 的模塊列表
> java --list-modules
java.base@11.0.20
java.compiler@11.0.20
java.datatransfer@11.0.20
java.desktop@11.0.20
java.instrument@11.0.20
java.logging@11.0.20
java.management@11.0.20
java.management.rmi@11.0.20
java.naming@11.0.20
java.net.http@11.0.20
java.prefs@11.0.20
java.rmi@11.0.20
java.scripting@11.0.20
java.se@11.0.20
java.security.jgss@11.0.20
java.security.sasl@11.0.20
java.smartcardio@11.0.20
java.sql@11.0.20
java.sql.rowset@11.0.20
java.transaction.xa@11.0.20
java.xml@11.0.20
java.xml.crypto@11.0.20
jdk.accessibility@11.0.20
jdk.attach@11.0.20
jdk.charsets@11.0.20
jdk.compiler@11.0.20
jdk.crypto.cryptoki@11.0.20
jdk.crypto.ec@11.0.20
jdk.crypto.mscapi@11.0.20
jdk.dynalink@11.0.20
jdk.editpad@11.0.20
jdk.hotspot.agent@11.0.20
jdk.httpserver@11.0.20
jdk.internal.ed@11.0.20
jdk.internal.jvmstat@11.0.20
jdk.internal.le@11.0.20
jdk.internal.opt@11.0.20
jdk.internal.vm.ci@11.0.20
jdk.internal.vm.compiler@11.0.20
jdk.internal.vm.compiler.management@11.0.20
jdk.jartool@11.0.20
jdk.javadoc@11.0.20
jdk.jcmd@11.0.20
jdk.jconsole@11.0.20
jdk.jdeps@11.0.20
jdk.jdi@11.0.20
jdk.jdwp.agent@11.0.20
jdk.jfr@11.0.20
jdk.jlink@11.0.20
jdk.jshell@11.0.20
jdk.jsobject@11.0.20
jdk.jstatd@11.0.20
jdk.localedata@11.0.20
jdk.management@11.0.20
jdk.management.agent@11.0.20
jdk.management.jfr@11.0.20
jdk.naming.dns@11.0.20
jdk.naming.ldap@11.0.20
jdk.naming.rmi@11.0.20
jdk.net@11.0.20
jdk.pack@11.0.20
jdk.rmic@11.0.20
jdk.scripting.nashorn@11.0.20
jdk.scripting.nashorn.shell@11.0.20
jdk.sctp@11.0.20
jdk.security.auth@11.0.20
jdk.security.jgss@11.0.20
jdk.unsupported@11.0.20
jdk.unsupported.desktop@11.0.20
jdk.xml.dom@11.0.20
jdk.zipfs@11.0.20
以下是 JDK 11.0.20 中, 聚合模塊 java.se 的模塊描述信息, 里面全是可傳遞的 requires
> java -d java.se
java.se@11.0.20
requires java.rmi transitive
requires java.management.rmi transitive
requires java.base mandated
requires java.instrument transitive
requires java.desktop transitive
requires java.transaction.xa transitive
requires java.security.jgss transitive
requires java.management transitive
requires java.prefs transitive
requires java.security.sasl transitive
requires java.xml transitive
requires java.sql transitive
requires java.naming transitive
requires java.datatransfer transitive
requires java.xml.crypto transitive
requires java.logging transitive
requires java.compiler transitive
requires java.scripting transitive
requires java.sql.rowset transitive
requires java.net.http transitive
模塊描述文件
模塊描述文件(module-info.java)是 Java 模塊化系統中的一個特殊文件,用于定義和配置模塊的屬性,依賴關系和訪問控制
- 文件位置:模塊描述文件位于模塊的根目錄下(源碼目錄下,Maven 項目的源碼文件夾通常是 src/main/java 中的 java 文件夾),并以
module-info.java
作為文件名 - 文件格式:模塊描述文件是一個普通的 Java 源代碼文件,使用 Java 編程語言的語法和結構進行編寫
- 模塊聲明:模塊描述文件以
module
關鍵字開頭, 后跟模塊的名稱. 例如module com.example.moduleName {}
關鍵字
module
:在模塊描述文件(module-info.java)中,使用 “module” 關鍵字定義一個Java模塊。模塊是一組相關的類和資源的集合,具有明確的依賴關系和訪問控制requires
:使用 “requires” 關鍵字在模塊描述文件中聲明一個模塊依賴于其他模塊。例如,模塊 A 可以聲明 “requires B;”,表示模塊 A 依賴于模塊 B。transitive
:當一個模塊(例如模塊A)依賴于另一個模塊(例如模塊B)時,可以在 “requires” 語句中使用 “transitive” 關鍵字。例如,模塊 A 可以聲明 “requires B transitive;”,這意味著除了模塊 A 直接依賴于模塊 B 之外,任何依賴于模塊 A 的模塊也將隱式地依賴于模塊 B。這樣的傳遞性依賴關系可以簡化模塊之間的依賴管理。
exports
:使用 “exports” 關鍵字在模塊描述文件中聲明一個模塊的包對其他模塊可見。通過 “exports” 語句,可以將模塊的公共 API 公開給其他模塊使用。例如,模塊 A 可以聲明 “exports com.example.package;”,表示模塊 A 將其名為 com.example.package 的包對其他模塊可見。opens
:與 “exports” 類似,“opens” 關鍵字也用于在模塊描述文件中聲明一個模塊的包對其他模塊可見。然而,與 “exports” 不同的是,“opens” 關鍵字還允許其他模塊通過反射訪問該包中的非公共類型。例如,模塊 A 可以聲明 “opens com.example.package;”,這樣其他模塊可以通過反射訪問 com.example.package 包中的非公共類型。需要與 “exports” 一起使用, 兩者合起來才是完整的權限- uses:使用 “uses” 關鍵字在模塊描述文件中聲明一個模塊使用某個服務接口。這表示該模塊需要在運行時從其他模塊中獲取實現該服務接口的提供者。例如,模塊 A 可以聲明 “uses com.example.ServiceInterface;”,表示模塊 A 需要使用 com.example.ServiceInterface 這個服務接口。
- provides:使用 “provides” 關鍵字在模塊描述文件中聲明一個模塊提供某個服務接口的實現。這表示該模塊是服務接口的一個提供者。例如,模塊 A 可以聲明 “provides com.example.ServiceInterface with com.example.ServiceImpl;”,表示模塊 A 提供了com.example.ServiceInterface 接口的實現 com.example.ServiceImpl。
- to:與 “provides” 關鍵字一起使用,指定一個或多個使用該服務接口的模塊。例如,模塊 A 可以聲明 “provides com.example.ServiceInterface with com.example.ServiceImpl to moduleB, moduleC;”,表示模塊 A 提供的 com.example.ServiceInterface實現將供模塊 B 和模塊 C 使用。
啟用模塊化
- JDK 9 及以上
- 添加模塊描述文件 module-info.java
測試
生成一個模塊化項目, 做一個工具類, 打包安裝到 Maven 本地倉庫, 用另一個普通項目依賴該 Jar 做測試, 將普通項目改成模塊化項目做測試
結論
- 非模塊化項目可以依賴模塊化的 Jar
- 模塊化的項目依賴非模塊化的 Jar 則比較麻煩
- 需要手動修改 Jar 的 MANIFEST.MF, 通過 Automatic-Module-Name 屬性指定模塊名(文件名), 將普通 Jar 修改為隱式模塊, 然后通過 requires 引用
- 由于很多第三方 Jar 都沒有做模塊化適配, 且模塊化的項目依賴其他普通 Jar 的時候會比較麻煩, 所以暫不推薦搭建模塊化項目, 這樣就可以直接使用非模塊化 Jar 和模塊化 Jar
- 如果依賴的第三方 Jar 都是模塊化的, 那還是推薦把項目改成模塊化項目, 可戰未來
Multi-Release jar (MRJAR)
JEP 238: Multi-Release JAR Files
原因
Creating Multi-Release JAR Files in IntelliJ IDEA
在過去,庫開發人員在支持較新版本的 Java 時有三種選擇:
- 提供兩個(或更多!)不同的JAR文件,每個JAR文件對應于他們想要支持的Java版本。這些可能帶有版本號,如“1.2-java-5”和“1.2-java-1.3”。
- 將您的每一個版本都綁定到特定版本的Java,迫使用戶要么升級他們的Java版本,要么停留在舊版本的庫中。例如,“5.0版以后的版本需要Java 8”。
- 堅持為用戶發布最低公分母版本。對于許多庫開發人員來說,這意味著他們仍然是根據Java 6進行編譯的,并且在幾乎所有用戶都已經遷移到Java 8之前,不能遷移到使用Java 8的功能,如lambdas和流。
- "最低公分母版本"是指在開發和發布軟件時所考慮的目標用戶群體中所使用的最舊版本的共同特性或功能子集。這意味著開發人員會選擇支持最舊的、被廣泛采用的版本,以確保盡可能多的用戶能夠使用他們的軟件。
對于庫開發人員或用戶來說,這些方法都不是特別有趣。它們要么涉及大量工作,要么疏遠/混淆用戶,要么庫無法利用新功能(因此也沒有給用戶提供太多升級Java版本的動力)。
從Java 9開始,還有一種選擇。現在,庫開發人員可以發布一個JAR文件,該文件:
- 如果您在Java 9上運行它,會使用Java 9的特性和功能
- 如果您在Java 9之前的版本上運行它,那么您將使用Java 9之前版本的實現。
這適用于Java 9以后的版本——因此這些多版本JAR文件將支持Java 9版本、Java 10(或18.3)、11、12版本等……但Java 9之前的任何內容都被歸為“Java 9之前”。這有點令人難過,因為顯然Java 8有一些不錯的功能,如果你運行Java 8,你可能會想要這些功能,但Java 9之前對庫的支持可能會像許多庫一樣,以Java6為目標,Java 8本身無法決定在運行多版本JAR文件時要做什么,因為該功能僅在Java 9中可用。
原理
root- A.class- B.class- C.class- D.class- META-INF- MANIFEST.MF- versions- 9- A.class- B.class- 11- B.class
基本上,您有一個標準的JAR文件,像往常一樣,在根目錄中包含應用程序中的所有類,在META-INF中還有一個額外的 “versions” 文件夾,其中包含每個額外支持的Java版本的特定實現(在本例中,只有Java 9)。這個“9”文件夾只需要包含那些具有特定Java 9功能的類的類文件。如果不存在類(例如C.class),則將使用默認版本。
翻譯成人話就是說, Java 9 在現有 Jar 結構的基礎上擴展了 versions 機制, 如果使用 Java 6/7/8, 會使用到 root 下的 A/B/C/D 類, 如果使用 Java 9, 則會使用到 root/META-INF/versions/9 下的 A/B 類和 root 下的 C/D 類, 如果使用 Java 10, 和 Java 9 的情況一樣, 如果使用 Java 11, 則會使用到 root/META-INF/versions/11 下的 B 類, root/META-INF/versions/9 下的 A 類, 還有 root 下的 C/D 類
只要將 Jar 包各部分用指定的 JDK 編譯, 然后按照上述規范打包, 在 MANIFEST.MF 中額外指定 Multi-Release: true
, 那么這個 Jar 就是一個多版本兼容 Jar 了, 在不同的 Java 環境下, 會自動選擇合適版本的類. 這里有一個隱藏規范, 就是多個版本的類的 API 需要完全一致, 這個不是必須, 但是建議一致, 不然在使用中可能會出問題
結論
是一種好方法, 推薦開發庫時使用
Java多版本兼容JAR的好處主要有以下幾點:
- 平滑遷移:Java多版本兼容JAR使得開發人員在升級Java版本時能夠平滑遷移他們的應用程序。通過使用兼容的JAR文件,可以確保應用程序在不同版本的Java運行時環境中都能正常運行,而無需對現有的代碼進行重寫或修改。
- 擴展用戶基礎:通過使用多版本兼容JAR,開發人員可以支持廣泛的用戶群體,包括那些仍在使用較舊Java版本的用戶。這使得應用程序能夠覆蓋更多的用戶,并且不會因為某些用戶無法升級到最新Java版本而失去潛在的用戶和市場份額。
- 提高可靠性和穩定性:多版本兼容JAR可以提高應用程序的可靠性和穩定性。通過確保應用程序在不同Java版本上的兼容性,可以降低因為Java版本升級而引入的潛在兼容性問題和錯誤。
- 簡化部署和維護:Java多版本兼容JAR使得應用程序的部署和維護更加簡化。開發人員只需提供一個通用的JAR文件,而不需要為每個Java版本提供單獨的構建或分發。這簡化了應用程序的構建、部署和維護流程,并減少了潛在的錯誤和混亂。
- 充分利用新功能:多版本兼容JAR使得開發人員能夠利用每個Java版本引入的新功能和改進。通過將新功能包裝在兼容的JAR中,開發人員可以將這些功能提供給那些使用較新Java版本的用戶,而無需犧牲對較舊Java版本的兼容性。
總的來說,Java多版本兼容JAR提供了一種靈活和可靠的方式,使得Java應用程序能夠在各種不同版本的Java運行時環境中運行,并且能夠充分利用每個版本的新功能。這為開發人員提供了更大的靈活性、更廣泛的用戶覆蓋范圍和更簡化的部署維護流程。
用 IDEA 創建多版本兼容 Jar
Creating Multi-Release JAR Files in IntelliJ IDEA
Java SE 9 多版本兼容 JAR 包示例, 兩個例子應該都沒有使用 IDEA, 可以參考
Maven 支持在一個模塊中創建兩個源碼文件夾, 并設置編輯級別, 以此來編譯并打包, 但是在 IDEA 中, 同一個模塊只能設置一個編輯級別, 且兩個源碼文件夾中不允許有完全相同的包和類. 用 IDEA 構建 Multi-Release Jar 需要忍受幾個點, 一是不能按照源碼文件夾設置語言級別(開發時無對應語言級別檢查), 二是兩個完全相同的類會報重復錯誤(不能直接運行測試). 其他的使用 IDEA 的 Maven 插件就可以搞定了
項目結構
新建一個 Maven 項目, JDK 選一個高版本的, 這里是 21.0.1, 項目結構如下, java 源碼包用 8 編譯, java.11 源碼包用 11 編譯, 工具類里可以針對不同編譯級別使用不同 API, 如 Paths.get 與 Path.of, 還有 Files.readString 等, 查看 @since
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.coder</groupId><artifactId>demo.multi.release.jar</artifactId><version>1.0.0</version><properties><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.11.0</version><executions><execution><!--覆蓋默認階段和目標綁定的配置--><id>default-compile</id><goals><goal>compile</goal></goals><configuration><!--從 JDK 9 起, 使用 release 替代 source 和 target, 因為后者不能保證代碼在使用指定版本的JDK編譯, 具體可參考下面兩個鏈接--><!-- https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-release.html --><!-- https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html --><release>8</release><compileSourceRoots><compileSourceRoot>${project.basedir}/src/main/java</compileSourceRoot></compileSourceRoots></configuration></execution><execution><id>compile-java-11</id><phase>compile</phase><goals><goal>compile</goal></goals><configuration><release>11</release><compileSourceRoots><compileSourceRoot>${project.basedir}/src/main/java.11</compileSourceRoot></compileSourceRoots><outputDirectory>${project.build.outputDirectory}/META-INF/versions/11</outputDirectory></configuration></execution><execution><!--覆蓋默認階段和目標綁定的配置--><id>default-testCompile</id><phase>test-compile</phase><goals><goal>testCompile</goal></goals><configuration><skip>true</skip></configuration></execution></executions></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.3.0</version><configuration><archive><manifestEntries><Multi-Release>true</Multi-Release></manifestEntries></archive></configuration></plugin></plugins></build></project>
測試
新建一個 java maven 項目, 依賴該多版本兼容 Jar, 分別設置項目 SDK 版本為 8/9/11/21 等, 查看輸出內容, 查看 class 文件內容