博文目录
官网 Getting Started with JavaFX
本文基于 OpenJFX 官网文档编写, 建议先通读一遍上述引用的官网文档, 内容不算多, 然后与本文相互印证
官网文档是基于 cmd 创建的工程, 编译和运行也是在 cmd 中, 并没有使用集成开发环境
JavaFX 官网
JavaFX 是一个开源的下一代客户端应用程序平台,适用于基于 Java 构建的桌面、移动和嵌入式系统。它是许多个人和公司的协作成果,目标是为开发富客户端应用程序生成一个现代、高效且功能齐全的工具包。
JavaFX 主要致力于富客户端开发,以弥补 swing 的缺陷,主要提供图形库与 media 库,支持 audio,video,graphics,animation,3D 等,同时采用现代化的 css 方式支持界面设计。同时又采用 XUI 方式以 XML 方式设计 UI 界面,达到显示与逻辑的分离。与 android 这方面确实有点相似性。
Java 8 新特性探究(十三)JavaFX 8 新特性以及开发 2048 游戏
跟 java 在服务器端和 web 端成绩相比,桌面一直是 java 的软肋,于是 Sun 公司在 2008 年推出 JavaFX,弥补桌面软件的缺陷,请看下图 JavaFX 一路走过来的改进
从上图看出,一开始推出时候,开发者需使用一种名为 JavaFX Script 的静态的、声明式的编程语言来开发 JavaFX 应用程序。因为 JavaFX Script 将会被编译为 Java bytecode,程序员可以使用 Java 代码代替。 JavaFX 2.0 之后的版本摒弃了 JavaFX Script 语言,而作为一个 Java API 来使用。因此使用 JavaFX 平台实现的应用程序将直接通过标准 Java 代码来实现。 JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView 可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。从 JDK 7u6 开始,JavaFX 就与 JDK 捆绑在一起了,JavaFX 团队称,下一个版本将是 8.0,目前所有的工作都已经围绕 8.0 库进行。这是因为 JavaFX 将捆绑在 Java 8 中,因此该团队决定跳过几个版本号,迎头赶上 Java 8。
目前我还不知道 Java 平台其他的更好的客户端库
官网 版本说明
截止到 20230423, JavaFX 的版本如上图所示. 有几个点需要注意
综上所述, 比较推荐 JavaFX 17 / 20, 17 有长期支持, 20 有最新最全的特性
JavaFX 建立在 JDK 之上,是一个独立的组件。
从 JDK 11 开始, JavaFX 与 JDK 分开发布, JavaFX 不再集成于 JDK 中
JavaFX | JDK |
---|---|
20 | 17 or later |
19 | 11 or later |
18 | 11 or later |
17 | 11 or later |
16 | 11 or later |
15 | 11 or later |
14 | 11 or later |
13 | 11 or later |
12 | 11 or later |
11 | OpenJDK 10 / JDK 11 or later |
… | |
8 | 集成于 JDK 8 |
… |
在 Java 8 之后,JavaFX 从 JDK 中分离出来,然后在 Java 9 时,Java 引入了 Java 模块化系统。从那之后,JavaFX 要求使用 Java 模块化系统来运行 JavaFX。因此,当直接使用 Java 8 以上的环境运行非模块化的 JavaFX 项目时就会出现如下报错。
错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
解决方法见下文 错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
部分
官网 JavaFX 20 API 文档
官网 Scene Builder 下载
Scene Builder 是针对 JavaFX FXML UI 的拖拽式页面设计编码工具, 免费且开源
当前版本是 Scene Builder 19.0.0, 运行需要 JDK 11 or later
如果使用 JDK 8 的 JavaFX, 可以下载 Scene Builder 8.5.0
建议选择第二种. 第一种是非常原始的方式, 可用于学习, 不适用于大工程开发
官网 JavaFX 和 IntelliJ IDEA
略
以这种方式创建的工程默认是非模块化工程, 如果需要改成模块化工程, 需自行添加并编写 module-info.java, module-info.java 应放在源码根目录下
官网 JavaFX SDK 下载
C:\mrathena\develop\javafx-sdk-20.0.1
初始工程结构如下
打开工程结构设置
确保当前使用的是 JDK 17
在 Libraries 里添加 JavaFX SDK 的 lib 目录, 其下所有 jar 文件都会被自动依赖到工程中
这里的 src.zip 就是 JavaFX SDK 的源码
随便打开一个 JavaFX 的 class 文件, 如 javafx.application.Application
, Idea 上方会提示 Choose sources ...
即可选择合适的源码关联, JavaFX SDK 的源码就是文件夹下的 src.zip
package com.mrathena;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class HelloWorld extends Application {
@Override
public void start(Stage stage) throws Exception {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
Scene scene = new Scene(new StackPane(l), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
当使用 Java 8 以上的环境运行非模块化的 JavaFX 项目时就会出现如下报错
错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
解决方法也有多种
使用 JDK 8, 不建议, 不能从根源解决问题
改造本工程为模块化工程, 推荐使用该方法
. 添加 module-info.java 模块申明文件, 内容如下
module com.mrathena { // 定义模块名称, 通常是类似包名的结构, 这里定义包名为 com.mrathena
requires javafx.controls; // 引入 javafx.controls 模块
exports com.mrathena; // 暴露本模块中的指定包(不包含子包) // Caused by: java.lang.IllegalAccessException: class com.sun.javafx.application.LauncherImpl (in module javafx.graphics) cannot access class com.mrathena.HelloWorld (in module com.mrathena) because module com.mrathena does not export com.mrathena to module javafx.graphics
}
在运行配置里添加 vm 参数 --module-path 下载的JavaFx的SDK的lib文件夹的完整路径 --add-modules javafx.controls,javafx.fxml
, 如果路径里有空格, 则路径要加上双引号 ""
, 因为 JDK 不包含 JavaFX SDK, 所以可以人工关联上, 相当于给 JDK 补充了 JavaFX 的模块
为了方便, 可以给 JavaFX 运行模板配置该 vm 参数, 后续运行其他主类也会自动加上该参数
使用引导类, 在另一个类的 main 方法中调用主类的 main 方法, 会自动生成一个匿名的模块系统, 桌面程序可以运行, 但有警告提示
4月 23, 2023 10:19:53 下午 com.sun.javafx.application.PlatformImpl startup
警告: Unsupported JavaFX configuration: classes were loaded from 'unnamed module @491dbe27'
如果使用 Maven 开发 JavaFX 应用程序,则不必下载 JavaFX SDK。只需在 pom.xml 中指定想要的模块和版本,构建系统就会下载所需的模块,包括所属平台的本机库。本机库就是不同系统下的具体实现, 如下图中带 win 的依赖
通过 Idea JavaFX Project 创建的就是 Maven 工程, 或者也可以创建 JavaFX Archetype 的 Maven 工程(需要自行导入该骨架), 不如 Idea JavaFX Project 来的方便
不知道这些依赖是干什么的先不选, 初始工程结构如下
默认就是 模块化结构的工程, pom.xml 中 javafx 的依赖版本默认是 17.0.0.1, 改成 20.0.1 即可
<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.0modelVersion>
<groupId>com.mrathenagroupId>
<artifactId>javafxartifactId>
<version>1.0-SNAPSHOTversion>
<name>java.javafx.startername>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>org.openjfxgroupId>
<artifactId>javafx-controlsartifactId>
<version>20.0.1version>
dependency>
<dependency>
<groupId>org.openjfxgroupId>
<artifactId>javafx-fxmlartifactId>
<version>20.0.1version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.9.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-engineartifactId>
<version>5.9.2version>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>17source>
<target>17target>
configuration>
plugin>
<plugin>
<groupId>org.openjfxgroupId>
<artifactId>javafx-maven-pluginartifactId>
<version>0.0.7version>
<executions>
<execution>
<id>default-cliid>
<configuration>
<mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplicationmainClass>
configuration>
execution>
executions>
plugin>
plugins>
build>
project>
module com.mrathena.javafx {
requires javafx.controls;
requires javafx.fxml;
opens com.mrathena.javafx to javafx.fxml;
exports com.mrathena.javafx;
}
package com.mrathena.javafx;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}
<VBox alignment="CENTER" spacing="20.0" xmlns:fx="http://javafx.com/fxml"
fx:controller="com.mrathena.javafx.HelloController">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
padding>
<Label fx:id="welcomeText"/>
<Button text="Hello!" onAction="#onHelloButtonClick"/>
VBox>
package com.mrathena.javafx;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
public class HelloController {
@FXML
private Label welcomeText;
@FXML
protected void onHelloButtonClick() {
welcomeText.setText("Welcome to JavaFX Application!");
}
}
官网 Scene Builder 下载
下载好 SceneBuilder-19.0.0.msi 并运行, 选择合适的安装位置, 安装即可
在 Idea 里右键某个 fxml 文件, 选择使用 SceneBuilder 打开, 即可设置与 SceneBuilder.exe 的关联, 设置过后, 后续即可直接用 SceneBuilder 打开 fxml 文件, 编辑好后 Ctrl+S 保存即可直接作用于 fxml 文件
51-Java模块化技术基础
廖雪峰 模块
54-把握JavaFX编程模型
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 定义, 可以自定义覆盖55-JavaFX应用的MVC架构
56-把握JavaFX事件处理机制原理
事件驱动
的, 事件(Event)
表示程序所感兴趣的某件事情发生了, 比如鼠标移动事件, 按键按下事件等57-JavaFX多窗体编程
58-JavaFX数据绑定机制及应用
JavaFX Bean Property
/ ObservableValue
/ ObservableList
59-JavaFX实现MVVM架构
JavaFX Bean Property
/ ObservableValue
/ ObservableList
具备值变化通知能力的工具来做成员属性, 其与 UI 上的某些控件的值相对应javafx.fxml.Initializable
接口, 在其提供的 initialize 方法中做如下事情
60-JavaFX多线程及典型示例展示
JavaFX Application Thread
线程负责, 自行创建的线程不允许操作 UIPlatform.runLater(() - > {})
的方式, 这里 Lambda 表达式执行的操作会被推送到 JavaFX Application Thread 线程中执行, 所以可以访问 UI 控件通过 Idea 可以便捷编译与运行, 直接点击 运行 按钮即可, 下面讲述通过 命令行 运行, 便于理解编译和运行的过程
该工程非常简单, 如上图, 其中 .idea 和 javafx.iml 是 idea 的配置文件, out 是该工程的编译文件, 除此之外, 就是一个源码文件夹 src 了. 采用命令行的方式编译只需要 src 即可
该工程对 JavaFX 的依赖来源于下载的 JavaFX SDK, 编译和运行时需要指定模块依赖
# 设置环境变量
set PATH_TO_FX="C:\mrathena\develop\javafx-sdk-20.0.1\lib"
# 切换工作路径设置到本工程内
cd C:\mrathena\develop\workspace\idea\mrathena\code.study\javafx
# 编译
dir /s /b src\*.java > sources.txt & javac --module-path %PATH_TO_FX% -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
注意: 编译生成的 mods/javafx 只是一个存放编译好的模块的文件夹而已, 可以是任意位置, 在 javafx 文件夹下的模块的名称由其中的 module-info.class 定义, 和文件夹名称没有关系. 本工程中我们定义的模块名称是 com.mrathena, 而不是 javafx
如果下载了 JavaFX JMods, 也可以用如下命令编译
cd C:\mrathena\develop\workspace\idea\mrathena\code.study\javafx
set PATH_TO_FX_MODS="C:\mrathena\develop\javafx-jmods-20.0.1"
dir /s /b src\*.java > sources.txt & javac --module-path %PATH_TO_FX_MODS% -d mods/javafx @sources.txt & del sources.txt
# java: java 运行工具
# 用法: java [options] <主类> [args...]: 执行类
# 用法: java [options] -jar [args...]: 执行 jar 文件
# 用法: java [options] -m <模块>[/<主类>] [args...] / java [options] --module <模块>[/<主类>] [args...]: 执行模块中的主类
# 用法: java [options] <源文件> [args]: 执行单个源文件程序. 现在能直接执行源文件了?
# --module-path <模块路径>...: 用 ; 分隔的目录列表, 每个目录都是一个包含模块的目录。除了依赖的 JavaFX SDK 的 lib 外, 还依赖上刚刚编译出的放到 mods 文件夹下的模块
# java -m <模块>[/<主类>] [args...] / java [options] --module <模块>[/<主类>] [args...]: 执行 com.mrathena 模块下的 com.mrathena.HelloWorld 主类
java --module-path %PATH_TO_FX%;mods -m com.mrathena/com.mrathena.HelloWorld
该工程 pom.xml 中引入了 javafx-maven-plugin, 提供了 javafx:run
和 javafx:jlink
两个 maven 命令, 可以在 Idea 的 Maven 面板中的 Plugins 中找到 javafx, 运行该命令, 也可以使用命令行工具运行该命令
cd C:\mrathena\develop\workspace\idea\mrathena\code.study\java.javafx.starter
mvn clean javafx:run
mvn clean javafx:jlink
默认会在 target 目录下生成一个 image 文件夹, 该文件夹就是生成的 JRE 了, 同样包含了自定义的 com.mrathena.javafx 模块, 可通过如下命令运行自定义模块 com.mrathena.javafx 中的主类 com.mrathena.javafx.HelloApplication
target\image\bin\java -m com.mrathena.javafx/com.mrathena.javafx.HelloApplication
也可以配置 javafx-maven-plugin, 做一些定制, 如下方的 configuration
部分, 指定了生成的 JRE 的文件夹为 jre, 指定了自定义模块的主类, 同时在 bin 目录额外生成了一个用于运行主类的 launcher 文件, 执行该文件即可便捷地运行主类
<plugin>
<groupId>org.openjfxgroupId>
<artifactId>javafx-maven-pluginartifactId>
<version>0.0.7version>
<configuration>
<jlinkImageName>jrejlinkImageName>
<launcher>launcherlauncher>
<mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplicationmainClass>
configuration>
<executions>
<execution>
<id>default-cliid>
<configuration>
<mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplicationmainClass>
configuration>
execution>
executions>
plugin>
target\jre\bin\launcher
使用 jlink 可以定制化 JRE, 可以做到如下的一些事情
jlink --help
用法: jlink <选项> --module-path <模块路径> --add-modules <模块>[,<模块>...]
可能的选项包括:
--add-modules [,...] 除了初始模块之外要解析的根模块。 还可以为 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 Disable the plugin mentioned
--endian 所生成 jimage 的字节顺序 (默认值: native)
-h, --help, -? 输出此帮助消息
--ignore-signing-information 在映像中链接已签名模块化 JAR 的情况下隐藏致命错误。已签名模块化 JAR 的签名相关文件将不会复制到运行时映像。
--launcher <名称>=<模块>[/<主类>] 为模块和主类添加给定名称的启动程序命令 (如果指定)
--limit-modules <模块>[,<模块>...] 限制可观察模块的领域
--list-plugins List available plugins
-p, --module-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
[email protected]
看了一下还有 40MB 大小
java.se 模块是一个聚合模块, 该模块没有任何代码, 只有一个 module-info.class 文件, 用于声明一些依赖, 产生聚合的作用, 达到引用该模块就相当于引用很多其他模块的效果
不建议直接引用 java.se 模块,因为它就相当于 Java 9 以前版本的 rt.jar 的内容
# 检查 java.se 中的模块
java --describe-module java.se
[email protected]
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.base mandated
requires java.xml.crypto transitive
requires java.scripting transitive
requires java.logging transitive
requires java.compiler transitive
requires java.net.http transitive
requires java.rmi transitive
requires java.management.rmi transitive
requires java.sql.rowset transitive
# 生成包含 java.se 所依赖的全部模块的 JRE
jlink --add-modules java.se --output jre
# 查看该 JRE 中包含的模块
jre\bin\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]
看了一下还有 82MB 大小
官网 JavaFX SDK / JMods 下载
生成包含 JavaFX 模块的 JRE 需要 JavaFX JMods, 下载后解压到合适的地方
# 设置环境变量(当然也可以不设置, 在使用该环境变量的地方用具体路径替代)
set PATH_TO_FX="C:\mrathena\develop\javafx-sdk-20.0.1\lib"
set PATH_TO_FX_MODS="C:\mrathena\develop\javafx-jmods-20.0.1"
# 检查 JavaFX 模块的声明
java --module-path %PATH_TO_FX% --describe-module javafx.base
java --module-path %PATH_TO_FX% --describe-module javafx.controls
# ...
# 生成包含 java.se 和 JavaFX 全部模块的 JRE
jlink --module-path %PATH_TO_FX_MODS% --add-modules java.se,javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web --output jre
# 根据依赖传递申明, 可简写为
jlink --module-path %PATH_TO_FX_MODS% --add-modules java.se,javafx.web,javafx.swing,javafx.fxml --output jre
# 查看该 JRE 中包含的模块
[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]
通过这种方式生成的 JRE, 已经包含了 JavaFX 相关依赖模块, 可以用来直接运行 JavaFX 应用程序, 可以分发给其他用户, 而其他用户不需要再下载 JavaFX SDK / JMods
# 编译
dir /s /b src\*.java > sources.txt & javac --module-path %PATH_TO_FX% -d mods/javafx @sources.txt & del sources.txt
# 运行, 不再需要 --module-path 指向 PATH_TO_FX
jre\bin\java --module-path mods --module com.mrathena/com.mrathena.HelloWorld
jre\bin\java --module-path mods --m com.mrathena/com.mrathena.HelloWorld
会将该自定义模块依赖的其他模块一同打包到 JRE 中
# 编译
dir /s /b src\*.java > sources.txt & javac --module-path %PATH_TO_FX% -d mods/javafx @sources.txt & del sources.txt
# 打包 JRE
jlink --module-path %PATH_TO_FX_MODS%;mods --add-modules com.mrathena --output jre
# 运行, 不再需要 --module-path
jre\bin\java -m com.mrathena/com.mrathena.HelloWorld
# 打包 JRE 并生成主类启动器, 在 JRE 的 bin 下生成一个 run 指令, 用于启动指定的主类
jlink --module-path %PATH_TO_FX_MODS%;mods --add-modules com.mrathena --output jre --launcher run=com.mrathena/com.mrathena.HelloWorld
# 运行, 不再需要指定主类
jre\bin\run
JRE 中集成了我们的自定义模块功能, 将该 JRE 发给别人, 即可直接运行功能
使用命令行工具运行
章节单独看 jpackage 可能不太好理解, 但是如果是顺着上文下来的, 那么自然而然就会 jpackage 打包了, 一脉相承
jpackage --help
查看选项列表说明, 官方已经给出了针对多种情况的简单命令行案例, 而且把各种参数已经做好了分类该路径与 --input 指定的路径 一定不能相同, 会生成超深文件夹, 无法直接删除(可递归从里向外删除)
我猜的
使用 jpackage
需要安装 WIX v3 或更高版本, 官网, GitHub, 安装完貌似不需要配置环境变量即可直接使用
# 设置环境变量
set PATH_TO_FX_MODS="C:\mrathena\develop\javafx-jmods-20.0.1"
# 切换工作路径设置到本工程内
cd C:\mrathena\develop\workspace\idea\mrathena\code.study\javafx
# 编译
dir /s /b src\*.java > sources.txt & javac --module-path %PATH_TO_FX% -d mods/javafx @sources.txt & del sources.txt
jpackage --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
生成一个 javafx.demo-1.0.exe 的安装程序, 安装后默认在 C:\Program Files
下, 双击 javafx.demo.exe 可运行
在 控制面板-卸载程序 中可以看到安装的该程序, 可正常卸载
# 自动生成 JRE, 输出到当前目录
jpackage --type app-image --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
# 使用自定义的 JRE, 输出到当前目录
jpackage --type app-image --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld --runtime-image jre
# 自动生成 JRE, 自定义图标, 输出到桌面
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
测试发现, 自动生成的 JRE 比我自定义的 JRE 体积更小, 0.0
双击 javafx.demo.exe 即可直接运行
可以使用 IntelliJ IDEA 编译的结果来生成可执行程序, IDEA 默认将 class 文件生成在 out\production\项目名 目录下, 修改打包命令如下
# 修改 --module-path 补充的 mods 路径为 out\production 即可直接使用 IDEA 的编译结果
jpackage --type app-image --name javafx.demo --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;out\production --module com.mrathena/com.mrathena.HelloWorld --icon D:\resource\头像.狐狸.ico
# 设置环境变量
set PATH_TO_FX_MODS="C:\mrathena\develop\javafx-jmods-20.0.1"
# 切换工作路径设置到本工程内
cd C:\mrathena\develop\workspace\idea\mrathena\code.study\java.javafx.starter
# 编译, 可以使用 IDEA Maven JavaFX 插件提供的 javafx:run 来编译
mvn clean javafx:run
同理, 生成的编译文件在 target\classes 目录, 将 target 目录补充到 --module-path
# 理论上通过下述命令可以直接打包了, 虽然成功打包了, 但是打包报了下面的错
# java.lang.IllegalArgumentException: "版本 [1.0-SNAPSHOT] 包含无效组件 [0-SNAPSHOT]"
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication
# 添加 -app-version 参数后可以运行了
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication --app-version 1.0.0
虽然可以成功打包与运行, 但是我还没理解为什么能读到 pom.xml 里面的 version, 然后报这个错
jpackage --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication --app-version 1.0.0
和 打包为运行程序 一样, --type 参数, 使用默认值 exe 就好了
要先把非模块化工程打包成一个可以运行的 Fat Jar, 这里以以前的一个 非模块化的 swing 工程为例, 打出的 jar 是 qq.speed.local.data.manager.jar, 确保通过 java -jar qq.speed.local.data.manager.jar
可直接运行
在 项目结构 里添加 Jar Artifact, 选择正确的 Main Class, 默认勾选 Extract to the target JAR
以便于生成无依赖的 Fat Jar. 其他同样默认即可. 然后在 [Build - Build Artifacts …] 选项里 [build] 该 Jar Artifact, 就会在 out\artifacts 目录下生成可直接运行的 Jar
非模块化工程打包时不需要使用模块化工程专用的那些参数, 确保该 jar 存在于 --input
指定的目录中
# 切换到 jar 所在的目录
# cd C:\mrathena\develop\workspace\idea\mrathena\code.project\qq.speed.local.data.manager\out\artifacts\qq_speed_local_data_manager_jar
# 打包
# 因为该 jar 可以直接运行, 即 jar 知道主类是哪个, 所以打包时可以不指定 --main-class, 当然指定也可以, 甚至可以强行指定为其他可运行主类
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar --main-class com.mrathena.QQSpeedLocalDataManager
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar --main-class com.mrathena.tool.QQGroupKit
有一点非常重要, 就是默认输出目录就是当前目录, 该目录一定不能和参数 --input 指定的打包目录相同(该目录下的所有文件都会被打包), 建议显式指定 --desc 参数并指向不同位置. 不然生成出的内容会不断被尝试打包, 最终因为文件夹深度太深而报错或路径超过字符数限制而报错, 关键还无法直接删除, 需要写代码递归从最深处往外删, 我不小心生成了一个 2000 多层的文件夹目录, 最终靠写递归代码才删掉
后来发现 [Build - Build Artifacts ... - 选择 Jar Artifact - clean] 可以非常方便的删除 out\artifacts 下的所有文件
双击 qq.speed.local.data.manager.exe 即可直接运行
就是 jar 包没有打在 exe 中, 处于暴露状态, 使用 exe4j 可以把 jar 一起打到 exe 中
jpackage --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar
和 打包为运行程序 一样, --type 参数, 使用默认值 exe 就好了
# 查看一个jar的依赖. 包含模块, 每个类的import等
jdeps qq.speed.local.data.manager.jar
# 生成只包含 java.base 模块的 JRE
jlink --add-modules java.base --output jre
# 生成包含 java.se 所依赖的全部模块的 JRE
jlink --add-modules java.se --output jre
# 生成包含 java.se 和 JavaFX 全部模块的 JRE
jlink --module-path %PATH_TO_FX_MODS% --add-modules java.se,javafx.base,javafx.controls,javafx.fxml,javafx.graphics,javafx.media,javafx.swing,javafx.web --output jre
# 根据依赖传递申明, 可简写为
jlink --module-path %PATH_TO_FX_MODS% --add-modules java.se,javafx.web,javafx.swing,javafx.fxml --output jre
# 检查 java.se 中的模块
java --describe-module java.se
# 查看自定义 JRE 中包含的模块
jre\bin\java --list-modules
# 打包 自定义模块 com.mrathena 到 JRE
jlink --module-path %PATH_TO_FX_MODS%;mods --add-modules com.mrathena --output jre
# 运行, 不再需要 --module-path
jre\bin\java -m com.mrathena/com.mrathena.HelloWorld
# 打包 JRE 并生成主类启动器, 在 JRE 的 bin 下生成一个 run 指令, 用于启动指定的主类
jlink --module-path %PATH_TO_FX_MODS%;mods --add-modules com.mrathena --output jre --launcher run=com.mrathena/com.mrathena.HelloWorld
# 运行, 不再需要指定主类
jre\bin\run
jpackage --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
# 自动生成 JRE, 输出到当前目录
jpackage --type app-image --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
# 使用自定义的 JRE, 输出到当前目录
jpackage --type app-image --name javafx.demo --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld --runtime-image jre
# 自动生成 JRE, 自定义图标, 输出到桌面
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;mods --module com.mrathena/com.mrathena.HelloWorld
jpackage --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication --app-version 1.0.0
# 理论上通过下述命令可以直接打包了, 虽然成功打包了, 但是打包报了下面的错
# java.lang.IllegalArgumentException: "版本 [1.0-SNAPSHOT] 包含无效组件 [0-SNAPSHOT]"
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication
# 添加 -app-version 参数后可以运行了
jpackage --type app-image --name javafx.demo --icon D:\resource\头像.狐狸.ico --dest C:\Users\mrathena\Desktop --module-path %PATH_TO_FX_MODS%;target --module com.mrathena.javafx/com.mrathena.javafx.HelloApplication --app-version 1.0.0
jpackage --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar
# 因为该 jar 可以直接运行, 即 jar 知道主类是哪个, 所以打包时可以不指定 --main-class, 当然指定也可以, 甚至可以强行指定为其他可运行主类
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar --main-class com.mrathena.QQSpeedLocalDataManager
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input . --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar --main-class com.mrathena.tool.QQGroupKit
# 创建自定义 JRE
jlink --add-modules java.se --output runtime
# 使用自定义 JRE 打包, out文件夹里只有这个jar包
jpackage --type app-image --icon D:\resource\头像.狐狸.ico --input out --dest C:\Users\mrathena\Desktop --name qq.speed.local.data.manager --main-jar qq.speed.local.data.manager.jar --runtime-image runtime