赞
踩
JavaFX 是一个开源的下一代客户端应用程序平台,适用于基于 Java 构建的桌面、移动和嵌入式系统。它是许多个人和公司的协作成果,目标是为开发富客户端应用程序生成一个现代、高效且功能齐全的工具包。
JavaFX 主要致力于富客户端开发,以弥补 swing 的缺陷,主要提供图形库与 media 库,支持 audio,video,graphics,animation,3D 等,同时采用现代化的 css 方式支持界面设计。同时又采用 XUI 方式以 XML 方式设计 UI 界面,达到显示与逻辑的分离。与 android 这方面确实有点相似性。
截止到 20240102, JavaFX 的版本如上图所示. JavaFX 11/17/21 是长期支持版本, 但 11 已到期不再更新, 建议选长期支持版本
JavaFX 建立在 JDK 之上,是一个独立的组件。从 JDK 11 开始, JavaFX 与 JDK 分开发布, JavaFX 不再集成于 JDK 中
在 Java 8 之后,JavaFX 从 JDK 中分离出来,然后在 Java 9 时,Java 引入了 Java 模块化系统。从那之后,JavaFX 要求使用 Java 模块化系统来运行 JavaFX。因此,当直接使用 Java 8 以上的环境运行非模块化 (无 module-info.java) 的 JavaFX 项目时就会出现如下报错。
错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
解决方法也有多种
--module-path "javafx jmods (javafx.xxx.jmod) / sdk (javafx.xxx.jar) 路径" --add-modules javafx.controls,javafx.fxml
, 相当于给 JDK 补充了 JavaFX 的模块, 如果路径里有空格, 则路径要加上双引号 ""
1月 2, 2024 10:19:53 下午 com.sun.javafx.application.PlatformImpl startup
警告: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @491dbe27'
在 Java 9 之前的版本中,JDK / JRE 包含了一个名为 rt.jar 的文件,它是 Java 核心库的一部分,包含了 Java 平台的类和资源。从 Java 9 开始,Java 平台引入了模块化系统,其中核心库被拆分为一组模块。rt.jar 被废弃,取而代之的是使用 JMOD 格式来打包和管理模块。
The new JMOD format goes beyond JAR files to include native code, configuration files, and other kinds of data that do not fit naturally, if at all, into JAR files.
JMOD files can be used at compile time and link time, but not at run time.
JMODs 可以用于编译 (javac) 和连接 (jlink), 但不能用于运行 (java)
Scene Builder 是针对 JavaFX FXML UI 的拖拽式页面设计编码工具, 免费且开源
当前版本是 Scene Builder 21.0.0, 运行需要 Java 17 and higher
如果使用 JDK 8 的 JavaFX, 可以下载 Scene Builder 8.5.0
下载好 SceneBuilder 并运行, 选择合适的安装位置, 安装即可
在 Idea 里右键某个 fxml 文件, 选择使用 SceneBuilder 打开, 即可设置与 SceneBuilder.exe 的关联, 设置过后, 后续即可直接用 SceneBuilder 打开 fxml 文件, 编辑好后 Ctrl+S 保存即可直接作用于 fxml 文件
javafx.application.Application
类, 然后重写其 start 方法, 此方法是所有 JavaFX 应用程序的入口点舞台(Stage)
与 场景(Scene)
, Stage 是顶级容器, 它对应于窗体, 其内容有 Scene 决定. Scene 是所有可视化内容的 容器(Container)
. JavaFX 应用程序的可视化界面通常由 控件(Control)
和 形状(Shape)
构成, 放到 Scene 中图形节点(Node)
构成的分层 场景图(Scene Graph)
来展现. SceneGraph 其实就是一颗多叉树, 各种控件都是树中的节点, 最底层的节点通常是诸如按钮之类的控件, 也可以是 Circle 之类的图形. 拥有子树的节点称为容器, 在 JavaFX 中称为 布局(Layout)
init
- start
- running
- stop
, init / start / stop 由抽象类 javafx.application.Application 定义, 可以自定义覆盖事件驱动
的, 事件(Event)
表示程序所感兴趣的某件事情发生了, 比如鼠标移动事件, 按键按下事件等JavaFX Bean Property
/ ObservableValue
/ ObservableList<T>
JavaFX Bean Property
/ ObservableValue
/ ObservableList<T>
具备值变化通知能力的工具来做成员属性, 其与 UI 上的某些控件的值相对应javafx.fxml.Initializable
接口, 在其提供的 initialize 方法中做如下事情
JavaFX Application Thread
线程负责, 自行创建的线程不允许操作 UIPlatform.runLater(() - > {})
的方式, 这里 Lambda 表达式执行的操作会被推送到 JavaFX Application Thread 线程中执行, 所以可以访问 UI 控件通过 Idea (2023.3.2) 创建时选 JavaFX
项目, 使用 JDK 21 和 JavaFX 21, 无需勾选可选的依赖库. 默认工程结构如下, 默认就是 Modular (存在 module-info.java) 的
默认 pom 内容如下, 可根据实际情况修改, 比如 javafx-controls 和 javafx-fxml 的版本和 javafx-maven-plugin 插件的配置等
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.coder</groupId>
<artifactId>demo</artifactId>
<version>1.0-SNAPSHOT</version>
<name>demo</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.10.0</junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>21</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>21</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<executions>
<execution>
<!-- Default configuration for running with: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>com.coder.demo/com.coder.demo.HelloApplication</mainClass>
<launcher>app</launcher>
<jlinkZipName>app</jlinkZipName>
<jlinkImageName>app</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
module com.coder.demo {
requires javafx.controls;
requires javafx.fxml;
opens com.coder.demo to javafx.fxml;
exports com.coder.demo;
}
通过 Idea 可以便捷编译与运行, 直接点击 运行 按钮即可, 下面讲述通过 命令行 运行, 便于理解编译和运行的过程
该工程非常简单, 如上图, src 是源码文件夹, 采用命令行的方式编译只需要 src 即可
编译该工程需要依赖 JavaFX 的 javafx.controls 和 javafx.fxml 两个模块, 使用 SDK 或者 JMODs 都可以, 以下是本机的依赖路径
编译需要使用对应版本的 JDK 21, 需要使用 --module-path
指定查找模块的路径
# 切换工作目录
cd C:\mrathena\develop\workspace\idea\mrathena\demo
# 编译 (使用 SDK)
dir /s /b src\*.java > sources.txt & javac --module-path C:\mrathena\develop\javafx-sdk-21.0.1\lib -d mods/javafx @sources.txt & del sources.txt
# 编译 (使用 JMODs)
dir /s /b src\*.java > sources.txt & javac --module-path C:\mrathena\develop\javafx-jmods-21.0.1 -d mods/javafx @sources.txt & del sources.txt
# 指令说明
# &: 将多个操作连到一起, 写成一行命令
# 第一部分
# dir: 列出当前目录下的所有文件夹和文件, 包含详细信息
# dir /s: 列出当前目录及其所有子目录下的所有文件和文件夹, 包含详细信息
# dir /b: 列出当前目录下的所有文件夹和文件, 只有文件名, 不包含其他信息
# dir /s /b: 列出当前目录及其所有子目录下的所有文件和文件夹的完整路径
# dir /s /b src\*.java: 列出当前目录下的 src 文件夹及其所有子文件夹下的 java 文件的完整路径
# > sources.txt: 将列出的 java 文件的完整路径保存到 sources.txt 文件中
dir /s /b src\*.java > sources.txt
# 第二部分
# javac: java 编译工具
# javac -help: 查看 javac 的选项
# 用法: javac <options> <source files>
# @<filename>: 从文件读取选项和文件名, 如下方的 @sources.txt, 就是要从该文件中读取 javac 命令需要的 选项 和 源文件名称
# --module-path <path>, -p <path>: 指定查找应用程序模块的位置(依赖的模块的位置), 类似之前的 -classpath 指定依赖的位置
# -d <directory>: 指定放置生成的类文件的位置
# 指定 JavaFX SDK 下的 lib 为依赖模块的位置, 指定将生成的 class 文件存放在当前目录下的 mods/javafx 中, 指定源文件从 sources.txt 中读取
javac --module-path C:\mrathena\develop\javafx-sdk-21.0.1\lib -d mods/javafx @sources.txt
# 第三部分
# 最后删掉生成的 sources.txt 文件
del sources.txt
注意: 编译生成的 mods/javafx 只是一个存放编译好的模块的文件夹而已, 可以是任意位置, 在 javafx 文件夹下的模块的名称由其中的 module-info.class 定义, 和文件夹名称没有关系. 本工程中我们定义的模块名称是 com.coder.demo, 而不是 javafx
# 运行 (只能使用 SDK, 不能使用 JMODs)
java --module-path C:\mrathena\develop\javafx-sdk-21.0.1\lib;mods --module com.coder.demo/com.coder.demo.HelloApplication
# java: java 运行工具
# 用法: java [options] <主类> [args...]: 执行类
# 用法: java [options] -jar <jar 文件> [args...]: 执行 jar 文件
# 用法: java [options] -m <模块>[/<主类>] [args...]: 执行模块中的主类
# 用法: java [options] --module <模块>[/<主类>] [args...]: 执行模块中的主类
# 用法: java [options] <源文件> [args]: 执行单个源文件程序. 现在能直接执行源文件了?
# --module-path <模块路径>...: 用 ; 分隔的目录列表, 每个目录都是一个包含模块的目录。除了依赖的 JavaFX SDK 的 lib 外, 还依赖上刚刚编译出的放到 mods 文件夹下的模块
# java [options] -m <模块>[/<主类>] [args...]
# java [options] --module <模块>[/<主类>] [args...]
这样直接运行会报错, 因为上一步我们编译只覆盖到了所有的 java 文件, 而 JavaFX 应用还包括 fxml 文件, 所以需要手动将 resources 下的内容 (包括完整层级路径) 拷贝到编译输出路径 mods/javafx 下, 这里只有一个 hello-wiew.fxml , 和 HelloApplication.class 放在一起即可, 放一起的原因是因为放一起, HelloApplication 中才能读取到资源
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
也可以使用 javafx-maven-plugin 插件提供的 mvn 命令运行
mvn clean javafx:run
使用 jlink 可以定制化 JRE, 可以做到如下的一些事情
jlink 只能使用 JavaFX JMODs 而不能使用 JavaFX SDK
jlink --help
用法: jlink <选项> --module-path <模块路径>[;<模块路径>...] --add-modules <模块>[,<模块>...]
可能的选项包括:
--add-modules <mod>[,<mod>...] 除了初始模块之外要解析的根模块。<mod> 还可以为 ALL-MODULE-PATH。
--bind-services 链接服务提供方模块及其被依赖对象
-c, --compress=<0|1|2> Enable compression of resources:
Level 0: No compression
Level 1: Constant string sharing
Level 2: ZIP
--disable-plugin <pluginname> Disable the plugin mentioned
--endian <little|big> 所生成 jimage 的字节顺序 (默认值: native)
-h, --help, -? 输出此帮助消息
--ignore-signing-information 在映像中链接已签名模块化 JAR 的情况下隐藏致命错误。已签名模块化 JAR 的签名相关文件将不会复制到运行时映像。
--launcher <名称>=<模块>[/<主类>] 为模块和主类添加给定名称的启动程序命令 (如果指定)
--limit-modules <模块>[,<模块>...] 限制可观察模块的领域
--list-plugins List available plugins
-p, --module-path <path> 模块路径。如果未指定,将使用 JDK 的 jmods 目录(如果存在该目录)。如果指定,但它不包含 java.base 模块,则将添加 JDK 的 jmods 目录(如果存在该目录)。
--no-header-files Exclude include header files
--no-man-pages Exclude man pages
--output <路径> 输出路径的位置
--save-opts <文件名> 将 jlink 选项保存在指定文件中
-G, --strip-debug Strip debug information
--suggest-providers [<名称>,...] 建议可从模块路径中实现给定服务类型的提供方
-v, --verbose 启用详细跟踪
--version 版本信息
@<文件名> 从文件中读取选项
重要参数说明
--module-path: 依赖模块的查找路径, 默认值是 JDK 的 jmods 目录, 如果指定了该选项, 但目录中不包含 java.base 模块, 将会自动添加 JDK 的 jmods 目录
--add-modules: 将指定的模块及其可以传递到的依赖模块都打包到新的 JRE 中
java.base 模块是 JDK 中最基础的模块, 是唯一一个没有引用其他任何模块的模块, 该模块暴露出了 JavaSE 的核心工具, 如java.lang / java.io / java.math / java.text / java.time / java.util 等
# 生成只包含 java.base 模块的 JRE
jlink --add-modules java.base --output jre
# 查看该 JRE, 只有一个 java.base 模块
jre\bin\java --list-modules
java.base@21.0.1
看了一下还有 40MB 大小
java.se 模块是一个聚合模块, 该模块没有任何代码, 只有一个 module-info.class 文件, 用于声明一些依赖, 产生聚合的作用, 达到引用该模块就相当于引用很多其他模块的效果
不建议直接引用 java.se 模块,因为它就相当于 Java 9 以前版本的 rt.jar 的内容
# 检查 java.se 中的模块
java --describe-module java.se
java.se@21.0.1
requires java.datatransfer transitive
requires java.naming transitive
requires java.management transitive
requires java.base mandated
requires java.transaction.xa transitive
requires java.instrument transitive
requires java.sql.rowset transitive
requires java.management.rmi transitive
requires java.sql transitive
requires java.compiler transitive
requires java.scripting transitive
requires java.security.sasl transitive
requires java.prefs transitive
requires java.security.jgss transitive
requires java.xml transitive
requires java.desktop transitive
requires java.rmi transitive
requires java.net.http transitive
requires java.logging transitive
requires java.xml.crypto transitive
# 生成包含 java.se 所依赖的全部模块的 JRE
jlink --add-modules java.se --output jre
# 查看该 JRE 中包含的模块
jre\bin\java --list-modules
java.base@21.0.1
java.compiler@21.0.1
java.datatransfer@21.0.1
java.desktop@21.0.1
java.instrument@21.0.1
java.logging@21.0.1
java.management@21.0.1
java.management.rmi@21.0.1
java.naming@21.0.1
java.net.http@21.0.1
java.prefs@21.0.1
java.rmi@21.0.1
java.scripting@21.0.1
java.se@21.0.1
java.security.jgss@21.0.1
java.security.sasl@21.0.1
java.sql@21.0.1
java.sql.rowset@21.0.1
java.transaction.xa@21.0.1
java.xml@21.0.1
java.xml.crypto@21.0.1
看了一下还有 88MB 大小
# 检查 JavaFX 模块的声明
java --module-path C:\mrathena\develop\javafx-sdk-21.0.1\lib --describe-module javafx.base
java --module-path C:\mrathena\develop\javafx-sdk-21.0.1\lib --describe-module javafx.controls
# ...
# 生成包含 java.se 和 JavaFX 全部模块的 JRE
jlink --module-path C:\mrathena\develop\javafx-jmods-21.0.1 --add-modules java.se,javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web --output jre
# 根据依赖传递申明, 可简写为
jlink --module-path C:\mrathena\develop\javafx-jmods-21.0.1 --add-modules java.se,javafx.web,javafx.swing,javafx.fxml --output jre
# 查看该 JRE 中包含的模块
jre\bin\java --list-modules
java.base@21.0.1
java.compiler@21.0.1
java.datatransfer@21.0.1
java.desktop@21.0.1
java.instrument@21.0.1
java.logging@21.0.1
java.management@21.0.1
java.management.rmi@21.0.1
java.naming@21.0.1
java.net.http@21.0.1
java.prefs@21.0.1
java.rmi@21.0.1
java.scripting@21.0.1
java.se@21.0.1
java.security.jgss@21.0.1
java.security.sasl@21.0.1
java.sql@21.0.1
java.sql.rowset@21.0.1
java.transaction.xa@21.0.1
java.xml@21.0.1
java.xml.crypto@21.0.1
javafx.base@21.0.1
javafx.controls@21.0.1
javafx.fxml@21.0.1
javafx.graphics@21.0.1
javafx.media@21.0.1
javafx.swing@21.0.1
javafx.web@21.0.1
jdk.jsobject@21.0.1
jdk.unsupported@21.0.1
jdk.unsupported.desktop@21.0.1
jdk.xml.dom@21.0.1
通过这种方式生成的 JRE, 已经包含了 JavaFX 相关依赖模块, 可以用来直接运行 JavaFX 应用程序, 可以分发给其他用户, 而其他用户不需要再下载 JDK / JRE / JavaFX
# 运行, 不再需要 --module-path 指向 JavaFX 依赖
jre\bin\java --module-path mods --module com.coder.demo/com.coder.demo.HelloApplication
# 打包 JRE, 根据依赖传递申明, com.coder.demo 依赖的 javafx.controls 和 javafx.fxml 模块也会被一并打包
jlink --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --add-modules com.coder.demo --output jre
通过这种方式生成的 JRE, 已经包含了主类运行所需要的所有模块, 运行主类不再需要 --module-path
# 运行, 不再需要 --module-path
jre\bin\java --module com.coder.demo/com.coder.demo.HelloApplication
# 打包 JRE 并生成主类启动器, 在 JRE 的 bin 下生成一个 run 指令, 用于启动指定的主类
jlink --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --add-modules com.coder.demo --output jre --launcher run=com.coder.demo/com.coder.demo.HelloApplication
通过这种方式生成的 JRE, 已经包含了主类运行所需要的所有模块, 同时包含了运行主类的快捷方式, 运行主类不再需要 --module
# 运行, 不再需要指定主类
jre\bin\run
JRE 中集成了我们的自定义模块功能, 将该 JRE 发给别人, 即可直接运行程序
使用 jlink 工具打包自定义模块和依赖模块成为自定义运行时, 要求所有依赖必须是 Named Module (具名模块), 即必须有 module-info.class, 而 Unamed Module (匿名模块) 或者 Automatic Module (自动模块, 在 MANIFEST.MF 文件中通过 Automatic-Module-Name 指定模块名称) 都不能被打包
所以要想使用 jlink 工具, 就需要将工程里面的非模块化的依赖, 比如 OkHttp3 使用同类模块化工具替换掉, 比如 Java 11 引入的 Http Client
这里使用自制的一个自动模块 toolkit (Jar 中没有 module-info.class, 通过 MANIFEST.MF 文件中的 Automatic-Module-Name 指定了模块名称为 toolkit), 里面有一个 Toolkit 工具类, 只有一个返回 String 的静态方法, 在项目中执行该工具代码, 以此来测试编译运行与打包
<?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>toolkit</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-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>toolkit</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
# 编译, 需要将新增的依赖加入到模块路径, 就是 Maven 本地仓库中的依赖目录
dir /s /b src\*.java > sources.txt & javac --module-path C:\coder\develop\javafx-jmods-21.0.1;C:\coder\resource\develop\maven\repository\com\coder\toolkit\1.0.0 -d mods/javafx @sources.txt & del sources.txt
# 运行, 需要将新增的依赖加入到模块路径, 就是 Maven 本地仓库中的依赖目录
java --module-path C:\coder\develop\javafx-sdk-21.0.1\lib;C:\coder\resource\develop\maven\repository\com\coder\toolkit\1.0.0;mods --module com.coder.demo/com.coder.demo.HelloApplication
# jlink, 需要将新增的依赖加入到模块路径, 就是 Maven 本地仓库中的依赖目录
jlink --module-path C:\coder\develop\javafx-jmods-21.0.1;C:\coder\resource\develop\maven\repository\com\coder\toolkit\1.0.0;mods --add-modules com.coder.demo --output jre --launcher run=com.coder.demo/com.coder.demo.HelloApplication
编译和运行都可以正常执行, 但是连接会报错
错误: 自动模块不能用于来自 file:///C:/coder/resource/develop/maven/repository/com/coder/toolkit/1.0.0/toolkit-1.0.0.jar 的 jlink: toolkit
运行 javafx-maven-plugin 提供的 javafx:jlink 命令, 可以按照插件配置生成运行时, 包含所有项目功能, 且内置主类启动程序, 能自动配置正确的模块目录和模块名称等, 比手动执行 jlink 方便很多. 但和 jlink 有同样的限制
Java 14 提供的打包工具, 可以将程序打包成 exe
单独看 jpackage 可能不太好理解, 但是如果是顺着上文下来的, 那么自然而然就会 jpackage 打包了, 一脉相承
jpackage --help
查看选项列表说明, 官方已经给出了针对多种情况的简单命令行案例, 而且把各种参数已经做好了分类该路径与 --input 指定的路径 一定不能相同, 会生成超深文件夹, 无法直接删除(可递归从里向外删除)
我猜的
使用 jpackage
需要安装 WIX v3 或更高版本, 官网, GitHub, 安装完貌似不需要配置环境变量即可直接使用
jpackage --name demo --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --module com.coder.demo/com.coder.demo.HelloApplication
生成一个 demo-1.0.exe 的安装程序, 安装完后自带 JRE, 默认安装路径在 C:\Program Files
下, 双击 demo.exe 可运行, 可通过其他参数配置安装路径, 快捷方式等
在 控制面板-卸载程序 中可以看到安装的该程序, 可正常卸载
# 当前目录生成 demo 文件夹, 内含自动生成的 JRE
jpackage --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --module com.coder.demo/com.coder.demo.HelloApplication --type app-image --name demo
# 使用自定义的 JRE, 输出到当前目录, 参数根据 jre 不同有所区别, 比如 --module-path 和 --module 等可能不需要写
jpackage --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --module com.coder.demo/com.coder.demo.HelloApplicatio --runtime-image jre --type app-image --name demo
# 自动生成 JRE, 自定义图标, 输出到桌面
jpackage --module-path C:\mrathena\develop\javafx-jmods-21.0.1;mods --module com.coder.demo/com.coder.demo.HelloApplication --type app-image --name demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop
双击 demo.exe 即可直接运行
jpackage 如果配置自动生成运行时, 则内部会调用 jlink, 拥有相同的限制
# jpackage, 需要将新增的依赖加入到模块路径, 就是 Maven 本地仓库中的依赖目录
jpackage --module-path C:\coder\develop\javafx-sdk-21.0.1\lib;C:\coder\resource\develop\maven\repository\com\coder\toolkit\1.0.0;mods --module com.coder.demo/com.coder.demo.HelloApplication --type app-image --name demo --icon C:\coder\resource\图片\头像.狐狸.ico --dest C:\Users\coder\Desktop
C:\coder\develop\workspace\idea\mrathena\demo>jpackage --module-path C:\coder\develop\javafx-sdk-21.0.1\lib;C:\coder\resource\develop\maven\repository\com\coder\toolkit\1.0.0;mods --module com.coder.demo/com.coder.demo.HelloApplication --type app-image --name demo --icon C:\coder\resource\图片\头像.狐狸.ico --dest C:\Users\coder\Desktop
jlink 失败,出现 错误: 自动模块不能用于来自 file:///C:/coder/resource/develop/maven/repository/com/coder/toolkit/1.0.0/toolkit-1.0.0.jar 的 jlink: toolkit
有时候, 项目依赖的非具名模块无法被替换, 导致工程无法使用 jlink, 亦无法直接使用 jpackage. 但可以通过 jpackage 的 --main-jar 参数来指定可运行 jar 的方式替代, 这就需要先将项目打包成为一个可直接运行的 Jar
还是以上面 jlink 限制中举的例子为例来尝试打包
添加一个新的入口主类, 内部调用 HelloApplication.main
package com.coder.demo;
public class Application {
public static void main(String[] args) {
HelloApplication.main(args);
}
}
修改 pom.xml 文件, 添加 maven-jar-plugin 和 maven-dependency-plugin 以及对应配置, 注意 maven-jar-plugin 的 mainClass 指向新添加的入口主类, 这里不要用 模块\主类 的写法, 而是直接写主类即可
<finalName>demo</finalName>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>com.coder.demo.Application</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/libs
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
然后运行 Maven 的 clean 和 package 命令, 在 target 中会生成 demo.jar 和 libs 依赖文件夹, 通过 java -jar 运行 demo.jar 验证其可执行
在 target 中新建一个文件夹 test, 将 demo.jar 和 libs 剪切到 test 中, 执行下面的 jpackage 命令, 在桌面生成 exe
jpackage --type app-image --name demo --icon C:\coder\resource\图片\头像.狐狸.ico --input C:\coder\develop\workspace\idea\mrathena\demo\target\test --main-jar demo.jar --dest C:\Users\coder\Desktop
# --input 参数指定打包的资源为刚刚新建的 test 文件夹的绝对路径
# --main-jar 指定可执行 jar 文件, 即 demo.jar
# --main-class 可通过该参数强行改变可执行 jar 启动时的运行主类
有一点非常重要, 就是默认输出目录就是当前目录, 该目录一定不能和参数 --input 指定的打包目录相同(该目录下的所有文件都会被打包), 建议显式指定 --desc 参数并指向不同位置. 不然生成出的内容会不断被尝试打包, 最终因为文件夹深度太深而报错或路径超过字符数限制而报错, 关键还无法直接删除, 需要写代码递归从最深处往外删, 我不小心生成了一个 2000 多层的文件夹目录, 最终靠写递归代码才删掉
生成物目录结构如下, 点击 demo.exe 可正常运行
另外, 其他的可执行 Jar 也可以通过这种方式打包成 exe, 比如用 java.swing 写的工具的可执行 jar
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。