博文目录
Java 9引入了模块化系统的主要原因是为了解决Java平台面临的复杂性和可维护性方面的挑战。以下是一些采用模块化系统的主要原因:
总的来说,Java 9的模块化系统提供了一种更好的方式来组织、重用和管理Java代码,改善了传统的类路径模型面临的复杂性和可维护性问题。它提供了更好的可见性、封装性、性能、安全性和可扩展性,从而改进了Java开发的体验和应用程序的质量。
自 Java 9 起, JDK 自身也被模块化, 按照功能划分为一系列有依赖关系的模块
# 查看模块列表
java --list-modules
# 查看模块信息 (module-info.java)
java -d <模块名称>
java --describ-module <模块名称>
以下是 JDK 11.0.20 的模块列表
> java --list-modules
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
以下是 JDK 11.0.20 中, 聚合模块 java.se 的模块描述信息, 里面全是可传递的 requires
> java -d java.se
[email protected]
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 模块化系统中的一个特殊文件,用于定义和配置模块的属性,依赖关系和访问控制
module-info.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” 一起使用, 两者合起来才是完整的权限生成一个模块化项目, 做一个工具类, 打包安装到 Maven 本地仓库, 用另一个普通项目依赖该 Jar 做测试, 将普通项目改成模块化项目做测试
JEP 238: Multi-Release JAR Files
Creating Multi-Release JAR Files in IntelliJ IDEA
在过去,库开发人员在支持较新版本的 Java 时有三种选择:
对于库开发人员或用户来说,这些方法都不是特别有趣。它们要么涉及大量工作,要么疏远/混淆用户,要么库无法利用新功能(因此也没有给用户提供太多升级Java版本的动力)。
从Java 9开始,还有一种选择。现在,库开发人员可以发布一个JAR文件,该文件:
这适用于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应用程序能够在各种不同版本的Java运行时环境中运行,并且能够充分利用每个版本的新功能。这为开发人员提供了更大的灵活性、更广泛的用户覆盖范围和更简化的部署维护流程。
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
<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.0modelVersion>
<groupId>com.codergroupId>
<artifactId>demo.multi.release.jarartifactId>
<version>1.0.0version>
<properties>
<maven.compiler.source>21maven.compiler.source>
<maven.compiler.target>21maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.11.0version>
<executions>
<execution>
<id>default-compileid>
<goals>
<goal>compilegoal>
goals>
<configuration>
<release>8release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/javacompileSourceRoot>
compileSourceRoots>
configuration>
execution>
<execution>
<id>compile-java-11id>
<phase>compilephase>
<goals>
<goal>compilegoal>
goals>
<configuration>
<release>11release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java.11compileSourceRoot>
compileSourceRoots>
<outputDirectory>${project.build.outputDirectory}/META-INF/versions/11outputDirectory>
configuration>
execution>
<execution>
<id>default-testCompileid>
<phase>test-compilephase>
<goals>
<goal>testCompilegoal>
goals>
<configuration>
<skip>trueskip>
configuration>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-jar-pluginartifactId>
<version>3.3.0version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>trueMulti-Release>
manifestEntries>
archive>
configuration>
plugin>
plugins>
build>
project>
新建一个 java maven 项目, 依赖该多版本兼容 Jar, 分别设置项目 SDK 版本为 8/9/11/21 等, 查看输出内容, 查看 class 文件内容