Administrator
Published on 2024-04-17 / 20 Visits
0
0

java模块化和jlink

模块化

概念

Java模块化是Java 9中引入的一个重大特性,它通过引入模块系统(Project Jigsaw)来改进Java的依赖管理和封装。模块化允许开发者将Java应用程序组织成一系列模块,每个模块是一组相关功能的集合。

模块是一组相关类的集合,这些类和资源被打包在一起,并可以被其他模块使用。

模块的实现是在项目中增加一个名为 module-info.java 的文件描述,这个文件定义了模块的名称、依赖关系、导出的包和服务的提供与使用。

模块可以精确控制哪些类和成员可以被其他模块访问,增强了封装性。
模块声明了它们依赖于哪些其他模块,这使得依赖关系更加清晰。
模块化的应用程序结构更清晰,更容易理解和维护。
模块可以独立部署和更新,提高了系统的灵活性。
通过减少不必要的类和方法,可以优化JVM的启动时间和运行时内存占用。

module-info.java

文件描述可以声明以下内容:

  • 模块名称:定义模块的名称。
  • requires:声明模块依赖。
  • exports:声明哪些包可以被其他模块访问。
  • opens:类似于 exports,但它允许其他模块反射访问该模块的包中的类。
  • uses:声明模块使用哪些服务。
  • provides:声明模块提供哪些服务实现。

示例:


//声明了一个名为 com.heawill.datadebugtools 的模块
module com.heawill.datadebugtools {

    //本模块依赖了以下模块(具体到包名)
    requires javafx.controls;
    requires javafx.fxml;
    requires java.desktop;
    requires io.netty.all;

    //对模块javafx.fxml开放com.heawill.datadebugtools和com.heawill.datadebugtools.controller包,包含私有成员(可反射),FXML 加载器需要进行反射
    opens com.heawill.datadebugtools to javafx.fxml;
    opens com.heawill.datadebugtools.controller to javafx.fxml;

    //对外暴露下面两个包
    exports com.heawill.datadebugtools;
    exports com.heawill.datadebugtools.controller;

}

上面这个例子,可以发现,为什么controller在com.heawill.datadebugtools下,需要另外多写一行,这是在 Java 模块系统中,即使 controller 是 com.heawill.datadebugtools 的子包,每个包的开放声明都是独立的。这意味着,即使你开放了父包 com.heawill.datadebugtools,它也不会自动将子包 com.heawill.datadebugtools.controller 也开放给其他模块。这说明了模块可以做到包的精确控制和各个包存在独立边界。

检查jar包是否模块化

  1. 通过检查是否包含module-info.java文件,可以使用解压软件查看,或用jdk的jar工具:
    jar tf your-jar-file.jar
    检查输出是否包含module-info.java文件。

image.png

  1. 使用jdk的jdeps检查工具,如果有输出具体的一些模块名称,则代表支持:
    jdeps -summary your-jar-file.jar

image-sxqc.png

javafx的模块化

对于maven项目的javafx项目,使用模块化,module-info.java内容基本需要如下:

module com.heawill.datadebugtools {
    //1.依赖javafx的controls和fxml
    requires javafx.controls;
    requires javafx.fxml;

    //2.对模块javafx.fxml开放controller
    opens com.heawill.datadebugtools.controller to javafx.fxml;

    //3.对外本包Application的位置
    exports com.heawill.datadebugtools;
}

其中controller的确定,为存放fxml文件对应的controller的目录;其中Application的确认,为继承javafx.application.Application的启动类的位置,不一定是根包的位置,而是启动类的位置。

如果没3,则启动时会报javafx.graphics无法访问本模块Application的错误,如果没2,则启动时会报javafx.fxml无法访问本模块Controller的错误。

非模块化jar引入模块化项目

可以支持,涉及到自动模块的概念,比如上面在解释module-info.java时,引入了netty:
requires io.netty.all;
实际上,netty并不支持模块化,而是其jar包在META-INF/MANIFEST.MF文件中定义了Automatic-Module-Name属性,那么它将有一个自动模块名,可以在模块化项目中使用。通过这种方式,即使JAR包不是模块化的,它也可以与模块化代码兼容。

自动模块相当于将整个 JAR 文件引入模块化项目中,无法实现模块化的优点。

image-wjyt.png

假如 Netty 的 JAR 包没有 Automatic-Module-Name,那么通常情况下,自动模块的名称会基于 JAR 文件的文件名。例如,如果 Netty 的 JAR 文件名为 netty-all-x.x.x.jar,那么自动模块名可能是 netty.all(注意点号被替换成了破折号)。

非模块化jar引入模块化项目,无法通过jlik进行生成,比如:

jlink --module-path "target/classes;C:\Users\Heawill\.m2\repository\org\apache\commons\commons-lang3\3.8.1\commons-lang3-3.8.1.jar" --add-modules com.example.jlinktest --output jr2123

jlink --module-path "target/classes;C:\Users\Heawill\.m2\repository\org\apache\commons\commons-lang3\3.8.1\commons-lang3-3.8.1.jar" --add-modules org.apache.commons.lang3 --add-modules com.example.jlinktest --output jr2123

其中commons-lang3为不支持模块化的jar,上面两个写法,都会提示:

错误: 自动模块不能用于来自 file:///C:/Users/Heawill/.m2/repository/org/apache/commons/commons-lang3/3.8.1/commons-lang3-3.8.1.jar 的 jlink: org.apache.commons.lang3

唯一方式是将jar变成模块化,我们将commons-lang3转为含module-info.java的jar引入maven项目后,我们再用下面命令即可生成:

jlink --module-path "target/classes;C:\Users\Heawill\.m2\repository\org\apache\commons\commons-lang3\3.8.1\commons-lang3-3.8.1.jar" --add-modules com.example.jlinktest --output jr2123

转换工具可参考:https://gitee.com/matkurban/modularization

参考

https://zhuanlan.zhihu.com/p/40623222

概念

jlink本质上是一个能够生成携带用户程序的精简JRE(Java Runtime Environment)的工具。在Java模块化系统中,jlink的作用是将用户应用程序模块和必要的Java平台模块组合成一个自定义的运行时环境。这个运行时环境可以独立于完整的JRE运行,因为它包含了所有必需的模块和类。

jlink生成自定义JRE的基本步骤:

  1. 模块解析: jlink解析用户应用程序模块的依赖关系,并确保所有依赖的模块(包括Java平台模块)都被添加到运行时映像中。
  2. 模块组合: jlink将所有必要的模块组合成一个自定义的Java运行时环境,这个环境包含了运行用户应用程序所需的所有模块和类。
  3. 运行时映像创建: jlink创建了一个包含所有必要模块、类、资源和类路径的运行时映像。这个映像可以被分发给没有安装完整JRE的用户,从而减少了应用程序的部署复杂性。
  4. 可选优化: 根据需要,jlink可以对运行时映像进行优化,如移除调试信息、头文件和手册页。

使用

说明

jlink的使用是用命令行的方式来生成程序,使用的前提需要系统安装jdk9以上的版本。

基本命令

jlink --module-path target/classes --add-modules org.example.demo --output jre

参数说明:

  • --module-path:
  1. 指定要查找的模块的路径(目录),target/classes则具体的目录值。
  2. 该值支持多个路径,如果多个,则参数用"包含起来,且路径用;(windows)或:(linux)分割,比如"target/classes1;target/classes2"。
  3. 目录值一般为maven项目执行过mvn package后,生成的target/classes目录。
  4. 会去扫描对应目录下的jar文件或module-info.class,不一定要在根目录下,子目录也可以扫描出来,比如直接填target,但是可能会有Two versions of module异常,应该有信息相同的jar和module-info.class同时存在。
  • --add-modules:
  1. 将从目录中扫到的模块中,指定要包含在运行时映像中的模块。
  2. --module-path参数是负责扫描,本参数是负责指定。
  3. 该参数也支持多个,直接使用多个--add-modules实现。
  • --output:要输出jre目录路径

运行说明:
本命令执行后,如无错误,将在执行目录生成jre目录,进入bin目录,执行下面命令运行程序:
./java --module org.example.demo/org.example.demo.HelloApplication
其中org.example.demo为模块名,org.example.demo.HelloApplication为要运行的main函数的类。

注意要使用bin目录里的java,而不是系统环境变量的java。

更精简jre命令

为了使导出的jre更小(大概可以少10多m),可以额外添加下面三个参数。

jlink --module-path target/classes --add-modules org.example.demo --output jre --strip-debug --no-header-files --no-man-pages

  • --strip-debug:移除调试信息。
  • --no-header-files:移除头文件。
  • --no-man-pages:移除手册页。

带运行脚本命令

由于通过基本命令的方式,我们需要手动执行,我们可以利用本命令自动生成可执行脚本。

jlink --module-path target/classes --add-modules org.example.demo --output jre --launcher APP=org.example.demo/org.example.demo.HelloApplication

  • --launcher:
  1. 指定要启动应用程序的模块,指定后,将生成可执行文件/脚本在生成的jre目录/bin下,具体名称为上面指令的APP值,即APP,那么生成的就叫APP(linux)/APP.bat(windows)。
  2. 由于生成的在/bin中,我们可以修改脚本内容的执行目录,将其放在jre根目录,方便执行。
  3. 其中org.example.demo为模块名,org.example.demo.HelloApplication为要运行的main函数的类。

示例1

创建一个maven项目,项目结构如图:
屏幕截图 2024-04-13 223259.png

常见错误

  • java.lang.module.FindException: Module java.base not found
    该错误通常意味着Java运行时环境(JRE)中没有找到名为java.base的模块。这个模块是Java平台模块化系统的基础,它包含了Java运行时环境的核心功能,如类加载器、内存模型、异常处理等。
    这个出现时是因为系统环境变量配置是的IDEA自带的jdk17,后面重新下载一个正式的jdk17,并改好环境变量后就正常了,猜测可能是IDEA自带的idea不支持模块化。

Comment