Windows Java JavaFX IntelliJ IDEA 开发环境搭建 创建工程 编译运行 打包分发 自定义运行时

博文目录

目录

文章目录

  • 本文说明
  • JavaFX 简单说明
    • JavaFX 版本说明
    • JavaFX 与 JDK 的关系
    • JavaFX 与 JDK Modular (JDK 9 模块化系统)
    • JavaFX 模块说明 (JavaFX 20)
    • JavaFX Scene Builder
    • 创建 JavaFX 应用程序的两种选择
  • 环境搭建
    • 版本选择
    • IntelliJ Idea 安装配置
    • Modular JavaFX Project (非 Maven / Gradle)
      • JavaFX SDK
      • 新建工程
      • 添加 JavaFX SDK 依赖
      • 关联 JavaFX SDK 源码
      • 创建第一个 JavaFX 窗体
        • HelloWorld.java
        • 错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
        • 效果展示
    • Modular JavaFX Maven Project
      • 新建工程
      • 自带初始模板代码如下
        • pom.xml
        • module-info.java
        • HelloApplication.java
        • hello-view.fxml
        • HelloController.java
      • 运行 HelloApplication
  • Scene Builder 19.0.0 安装与配置
  • JavaFX 开发需要掌握的知识点
    • Java 模块化技术基础
    • JavaFX 基础编程模型
    • JavaFX MVC 架构
    • JavafX 事件处理机制
    • JavaFX 多窗体编程与数据传输
    • JavaFX 数据绑定机制
    • Java MVVM
    • JavaFX 多线程
    • JavaFX 集成第三方框架, 如 RxJava
    • JavaFX Chart 图表
  • 编译与运行
    • Modular JavaFX Project (非 Maven / Gradle)
      • 准备
      • 使用命令行工具编译
      • 使用命令行工具运行
    • Modular JavaFX Maven Project
      • 使用命令行工具编译与运行
      • 创建自定义 JRE
  • 自定义 JRE
    • jlink 参数列表
      • `重要参数说明`
    • 生成最小 JRE
    • 生成包含 java.se 模块的 JRE
    • 生成包含完整 JavaFX 模块的 JRE
      • 使用该 JRE 运行 JavaFX 程序, 以前面章节的 Modular Java Project 工程为例
    • 将自定义模块打包到 JRE 中, 并生成该模块中主类的启动程序命令, 以前面章节的 Modular Java Project 工程为例
  • 打包与分发
    • jpackage
      • wix v3
    • Modular JavaFX Project (非 Maven / Gradle)
      • 准备
      • 打包为安装程序
      • 打包为运行程序
        • 补充说明
    • Modular JavaFX Maven Project
      • 准备
      • 打包为运行程序
      • 打包为安装程序
    • Non-Modular Java Project
      • 通过 IDEA 将工程打包为可运行 Jar
      • 将可运行 Jar 打包为运行程序
      • 将可运行 Jar 打包为安装程序
  • 相关命令整理
    • jdeps
    • jlink
    • jpackage
      • Modular JavaFX Project (非 Maven / Gradle)
          • 打包为安装程序
          • 打包为运行程序
      • Modular JavaFX Maven Project
          • 打包为安装程序
          • 打包为运行程序
      • Non-Modular Java Project
          • 打包为安装程序
          • 打包为运行程序

本文说明

官网 Getting Started with JavaFX

本文基于 OpenJFX 官网文档编写, 建议先通读一遍上述引用的官网文档, 内容不算多, 然后与本文相互印证

官网文档是基于 cmd 创建的工程, 编译和运行也是在 cmd 中, 并没有使用集成开发环境

JavaFX 简单说明

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 平台其他的更好的客户端库

JavaFX 版本说明

官网 版本说明

截止到 20230423, JavaFX 的版本如上图所示. 有几个点需要注意

  • JavaFX 11 和 17 是长期支持版本, 会有专人做更新和修复工作, 除此之外的版本都是非长期支持版本
  • JavaFX 11 虽然是长期支持版本, 但是其将在 202309 到期, 而且有更好的 17 可选, 所以 11 不推荐使用
  • JavaFX 20 是当前的最新发布版本, 但不是长期支持版本. 最新发布版本会有最多最全的新特性, 有需要可以使用该版本
  • JavaFX 21 是早期访问版本, 相当于测试版本, 一般不建议使用, 看中某些新特性尝鲜可以试试
  • 灰色的 JavaFX 12 / 13 / 14 / 15 / 16 / 18 已经不再更新, 不建议使用. 19 虽然还在更新, 但是估计用不了多久也会停止, 同样不建议使用

综上所述, 比较推荐 JavaFX 17 / 20, 17 有长期支持, 20 有最新最全的特性

JavaFX 与 JDK 的关系

JavaFX 建立在 JDK 之上,是一个独立的组件。

从 JDK 11 开始, JavaFX 与 JDK 分开发布, JavaFX 不再集成于 JDK 中

JavaFXJDK
2017 or later
1911 or later
1811 or later
1711 or later
1611 or later
1511 or later
1411 or later
1311 or later
1211 or later
11OpenJDK 10 / JDK 11 or later
8集成于 JDK 8

JavaFX 与 JDK Modular (JDK 9 模块化系统)

在 Java 8 之后,JavaFX 从 JDK 中分离出来,然后在 Java 9 时,Java 引入了 Java 模块化系统。从那之后,JavaFX 要求使用 Java 模块化系统来运行 JavaFX。因此,当直接使用 Java 8 以上的环境运行非模块化的 JavaFX 项目时就会出现如下报错。

错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序

解决方法见下文 错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序 部分

JavaFX 模块说明 (JavaFX 20)

官网 JavaFX 20 API 文档

  • javafx.base: 定义 JavaFX UI 工具包的基本 API,包括绑定、属性、集合和事件的 API。
  • javafx.controls: 定义可用于 JavaFX UI 工具包的 UI 控件、图表和外观。
  • javafx.fxml: 为 JavaFX UI 工具包定义 FXML API。
  • javafx.graphics: 为 JavaFX UI 工具包定义核心场景图 API(例如布局容器、应用程序生命周期、形状、转换、画布、输入、绘画、图像处理和效果),以​​及动画、CSS、并发、几何、打印的 API , 和开窗。
  • javafx.media: 定义用于播放媒体和音频内容的 API,作为 JavaFX UI 工具包的一部分,包括MediaView和 MediaPlayer.
  • javafx.swing: 为 JavaFX UI 工具包中包含的 JavaFX/Swing 互操作支持定义 API,包括SwingNode(用于将 Swing 嵌入 JavaFX 应用程序)和 JFXPanel(用于将 JavaFX 嵌入 Swing 应用程序)。
  • javafx.web: 为 JavaFX UI 工具包中包含的 WebView 功能定义 API。

JavaFX Scene Builder

官网 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 应用程序的两种选择

  • Modular JavaFX Project (非 Maven / Gradle): 需要下载并引入 JavaFX SDK 提供的依赖 jar, 在 lib 目录下
  • Modular JavaFX Maven / Gradle Project: 使用构建工具可以从中央仓库下载指定版本的 JavaFX 相关依赖, 和使用 JavaFX SDK 本质是一样的, 但是无需下载 JavaFX SDK

建议选择第二种. 第一种是非常原始的方式, 可用于学习, 不适用于大工程开发

环境搭建

官网 JavaFX 和 IntelliJ IDEA

版本选择

  • IntelliJ IDEA 使用的是 2021.2.3
  • 本次学习使用最新版 JavaFX 20, 需要使用 JDK 17

IntelliJ Idea 安装配置

Modular JavaFX Project (非 Maven / Gradle)

以这种方式创建的工程默认是非模块化工程, 如果需要改成模块化工程, 需自行添加并编写 module-info.java, module-info.java 应放在源码根目录下

JavaFX SDK

官网 JavaFX SDK 下载

  • 先选择合适的 SDK 版本, 建议从 11 / 17 / 20 / Early Access 这 4 个版本中选择
  • 然后下载并解压该 SDK, 如 C:\mrathena\develop\javafx-sdk-20.0.1

新建工程



初始工程结构如下

添加 JavaFX SDK 依赖

打开工程结构设置

确保当前使用的是 JDK 17


在 Libraries 里添加 JavaFX SDK 的 lib 目录, 其下所有 jar 文件都会被自动依赖到工程中


这里的 src.zip 就是 JavaFX SDK 的源码


关联 JavaFX SDK 源码

随便打开一个 JavaFX 的 class 文件, 如 javafx.application.Application, Idea 上方会提示 Choose sources ... 即可选择合适的源码关联, JavaFX SDK 的源码就是文件夹下的 src.zip



创建第一个 JavaFX 窗体

HelloWorld.java
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();
    }
}

错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序

当使用 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'
    
效果展示

Modular JavaFX Maven Project

如果使用 Maven 开发 JavaFX 应用程序,则不必下载 JavaFX SDK。只需在 pom.xml 中指定想要的模块和版本,构建系统就会下载所需的模块,包括所属平台的本机库。本机库就是不同系统下的具体实现, 如下图中带 win 的依赖


  • D:\resource\develop\maven\repository\org\openjfx\javafx-controls\20.0.1\javafx-controls-20.0.1.jar
  • D:\resource\develop\maven\repository\org\openjfx\javafx-controls\20.0.1\javafx-controls-20.0.1-win.jar

新建工程

通过 Idea JavaFX Project 创建的就是 Maven 工程, 或者也可以创建 JavaFX Archetype 的 Maven 工程(需要自行导入该骨架), 不如 Idea JavaFX Project 来的方便


不知道这些依赖是干什么的先不选, 初始工程结构如下

默认就是 模块化结构的工程, pom.xml 中 javafx 的依赖版本默认是 17.0.0.1, 改成 20.0.1 即可

自带初始模板代码如下

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mrathena</groupId>
    <artifactId>javafx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>java.javafx.starter</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>20.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>20.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.9.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.7</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplication</mainClass>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
module-info.java
module com.mrathena.javafx {
    requires javafx.controls;
    requires javafx.fxml;

    opens com.mrathena.javafx to javafx.fxml;
    exports com.mrathena.javafx;
}
HelloApplication.java
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();
    }
}
hello-view.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>

<?import javafx.scene.control.Button?>
<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>

HelloController.java
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!");
    }
}

运行 HelloApplication

Scene Builder 19.0.0 安装与配置

官网 Scene Builder 下载

下载好 SceneBuilder-19.0.0.msi 并运行, 选择合适的安装位置, 安装即可

在 Idea 里右键某个 fxml 文件, 选择使用 SceneBuilder 打开, 即可设置与 SceneBuilder.exe 的关联, 设置过后, 后续即可直接用 SceneBuilder 打开 fxml 文件, 编辑好后 Ctrl+S 保存即可直接作用于 fxml 文件


JavaFX 开发需要掌握的知识点

Java 模块化技术基础

51-Java模块化技术基础
廖雪峰 模块

  • module-info.java
  • –module-path: 依赖模块的查找目录
  • –add-modules: 指定除了默认模块额外要解析的模块
  • jmods: 用于生成 .jmod 格式的模块
  • jlink: 用于生成自定义 JRE 的工具, 可根据依赖自行精简 JRE, 便于打包和分发

JavaFX 基础编程模型

54-把握JavaFX编程模型

  • JavaFX 应用程序的主类需要继承自 javafx.application.Application 类, 然后重写其 start 方法, 此方法是所有 JavaFX 应用程序的入口点
  • JavaFX 应用程序将 UI 容器定义为 舞台(Stage)场景(Scene), Stage 是顶级容器, 它对应于窗体, 其内容有 Scene 决定. Scene 是所有可视化内容的 容器(Container). JavaFX 应用程序的可视化界面通常由 控件(Control)形状(Shape) 构成, 放到 Scene 中
  • JavaFX 中, Scene 中的内容会以由 图形节点(Node) 构成的分层 场景图(Scene Graph) 来展现. SceneGraph 其实就是一颗多叉树, 各种控件都是树中的节点, 最底层的节点通常是诸如按钮之类的控件, 也可以是 Circle 之类的图形. 拥有子树的节点称为容器, 在 JavaFX 中称为 布局(Layout)
  • Stage 与 Scene 是 1:1 的关系, Scene 与场景图的根节点是 1:1 的关系
  • JavaFX 应用程序的生命周期: initstartrunningstop, init / start / stop 由抽象类 javafx.application.Application 定义, 可以自定义覆盖
  • Stage 的生命周期: close / hide / show 等等, 可以给 Stage 挂接相关事件

JavaFX MVC 架构

55-JavaFX应用的MVC架构

  • MainClass: 程序的入口点, 通常包容管理多窗体, 实现各控制器之间相互通讯的相关代码
  • Model: 就是封装了数据的 JavaBean
  • View: 使用 FXML 编写, 包容可视化的 UI 控件, 使用户使用程序的媒介, 关联着一个控制器
  • Controller: 包容程序的应用逻辑, 负责响应用户操作, 负责在 View 和 Model 之间实现数据同步

JavafX 事件处理机制

56-把握JavaFX事件处理机制原理

  • 桌面应用都是 事件驱动 的, 事件(Event) 表示程序所感兴趣的某件事情发生了, 比如鼠标移动事件, 按键按下事件等
  • JavaFX 中, 事件是 javafx.event.Event 类或其任何子类的实例, JavaFX 提供了多种事件, 比如 DragEvent(拖动事件), KeyEvent, MouseEvent 等, JavaFX 提供的内置 UI 控件和图形对象, 都可以触发特定事件
  • JavaFX 中可以通过 Java 代码绑定事件, 也可以通过 FXML 绑定事件, 通过 FXML 声明的事件的方法要加 @FXML 注解
  • 事件派发链: 事件在控件树中的传播过程. 事件派发链是一个双向链表, 有时间目标对象负责创建, 事件发生时, 事件对象会在链中传播. 事件响应方法分为 EventFilter 和 EventHandler 两类, 先执行 EventFilter 事件链
    • EventFilter: 下沉: 从根节点向下来到事件发生节点
    • EventHandler: 冒泡: 从事件发生节点向上去往根节点
    • 举例: HBox 里放了个 Label: 两者都分别挂载了两个方向的 MOUSE_PRESSED 事件, 如果在标签上点击左键, 则会按照先 EventFilter 再 EventHandler 的顺序来调用响应方法, Filter 的先执行 hBox 的再执行 label 的, Handler 的先执行 label 的再执行 hBox 的
      • hBox.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {})
      • hBox.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {})
      • label.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {})
      • label.addEventHandler(MouseEvent.MOUSE_PRESSED, e -> {})

JavaFX 多窗体编程与数据传输

57-JavaFX多窗体编程

  • 每个 Stage 对应一个窗体
  • 模态窗体: 具有父子关系且子窗体出现时, 父窗体不能操作
  • 对象互相传递消息: 就是对象互相持有引用, 调用对方的方法来传递数据, 在 JavaFX 中窗体间传递信息靠的就是各 Controller 相互引用, 调用回调函数

JavaFX 数据绑定机制

58-JavaFX数据绑定机制及应用

  • JavaFX Bean Property: 提供了很多 SimpleIntegerProperty 之类的工具类, 可以绑定值改变事件, 代码操作值, 绑定的 UI 自动跟新
  • JavaFX 数据绑定编程模式
    • 绑定数据源: 具备改变通知能力的数据对象与数据集合, 包括 JavaFX Bean Property / ObservableValue / ObservableList<T>
    • JavaFX 应用程序的 UI 界面: 包容 TableView / Label 等支持数据绑定的控件
    • JavaFX 数据绑定机制: Binding 对象
    • 举例: Circle 始终居于窗体中央, Circle 自带圆心横纵坐标的 Property 属性, Scene 自带宽度高度的 Property 属性
      • circle.centerXProperty().bind(Bindings.divide(scene.widthProperty(), 2));
      • circle.centerYProperty().bind(Bindings.divide(scene.heightProperty(), 2));
  • 优点: 业务逻辑与 UI 分离, 绑定好关系之后, 属性值变化, UI 会自动更新, 无需代码修改 UI
  • 双向绑定:
    • 举例: 两个 TextField 保持内容一致, 再任何一方修改, 另一方保持内容相同
      • repeater.textProperty().bindBidirectional(speaker.textProperty());

Java MVVM

59-JavaFX实现MVVM架构

  • View: 还是 FXML 定义的 UI
  • ViewModel: 和 MVC 中的 Model 一样, 但是使用 JavaFX Bean Property / ObservableValue / ObservableList<T> 具备值变化通知能力的工具来做成员属性, 其与 UI 上的某些控件的值相对应
  • Controller: 实现 javafx.fxml.Initializable 接口, 在其提供的 initialize 方法中做如下事情
    • 控件的事件挂载
    • ViewModel 和 UI 控件做双向数据绑定, 这样 Controller 直接操作 ViewModel 的属性即可同步到 UI, 在 UI 相关控件上修改值也能同步到 ViewModel 的相关字段中
    • 注意: UI 变化完全由数据绑定后自行更新, Controller 不做任何 UI 的控制
  • 举例: 登录功能, UI 有 username 和 password 两个 TextField, login 和 cancel 两个 Button
    • LoginApplication: 程序主类入口
    • LoginViewModel: 持有 usernameProperty 和 passwordProperty 两个 StringProperty, 还有相关的 getter / setter / protertyGetter
    • LoginController: 实现 login 和 cancel 两个方法, 在 initialize 方法中做 login 按钮和 login 方法的绑定, cancel 按钮和 cancel 方法的绑定, ViewModel 中 usernameProperty 和 username 文本框的双向绑定, ViewModel 中 passwordProperty 和 password 文本框的双向绑定
      • login: 通过获取并验证 ViewModel 中 usernameProperty 和 passwordProperty 的值做合适的逻辑处理
      • cancel: 清空 ViewModel 中 usernameProperty 和 passwordProperty 的值
  • 也可以把 login 中的业务逻辑放到其他方法或类中, 这样 单元测试 时可以只测业务不看 UI

JavaFX 多线程

60-JavaFX多线程及典型示例展示

  • UI 操作有专门的 JavaFX Application Thread 线程负责, 自行创建的线程不允许操作 UI
  • 主线程中不要直接调用耗时很久的方法, 会导致窗体冻结. 要用一个线程去这些任务, 如果任务完成后需要更新 UI, 则可以使用 Platform.runLater(() - > {}) 的方式, 这里 Lambda 表达式执行的操作会被推送到 JavaFX Application Thread 线程中执行, 所以可以访问 UI 控件
  • JavaFX 的 Worker 和 Task: 提供了后台线程运行并更新 UI 的相关功能封装, 支持中途取消

JavaFX 集成第三方框架, 如 RxJava

  • RxJava: 一款响应式的框架
  • 需要注意一点, 需要更新 UI 的地方用 Platform.runLater

JavaFX Chart 图表

  • JavaFX 内置了 图表 相关控件, 支持数据绑定, 可做一些数据可视化相关功能

编译与运行

通过 Idea 可以便捷编译与运行, 直接点击 运行 按钮即可, 下面讲述通过 命令行 运行, 便于理解编译和运行的过程

Modular JavaFX Project (非 Maven / Gradle)

该工程非常简单, 如上图, 其中 .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 <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 %PATH_TO_FX% -d mods/javafx @sources.txt

# 最后删掉生成的 sources.txt 文件
del sources.txt

注意: 编译生成的 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 <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

Modular JavaFX Maven Project

该工程 pom.xml 中引入了 javafx-maven-plugin, 提供了 javafx:runjavafx:jlink 两个 maven 命令, 可以在 Idea 的 Maven 面板中的 Plugins 中找到 javafx, 运行该命令, 也可以使用命令行工具运行该命令

使用命令行工具编译与运行

cd C:\mrathena\develop\workspace\idea\mrathena\code.study\java.javafx.starter
mvn clean javafx:run

创建自定义 JRE

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.openjfx</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>0.0.7</version>
    <configuration>
        <jlinkImageName>jre</jlinkImageName>
        <launcher>launcher</launcher>
        <mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplication</mainClass>
    </configuration>
    <executions>
        <execution>
            <!-- Default configuration for running with: mvn clean javafx:run -->
            <id>default-cli</id>
            <configuration>
                <mainClass>com.mrathena.javafx/com.mrathena.javafx.HelloApplication</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>
target\jre\bin\launcher

自定义 JRE

使用 jlink 可以定制化 JRE, 可以做到如下的一些事情

  • 可以排除一些没有使用到的模块, 缩小 JRE 的体积以便于分发
  • 可以将 JDK 和 JavaFX 的相关模块打包到一起便于 JavaFX 应用程序的运行
  • 可以把我们自己自定义的模块也打包进去, 便于自定义模块的运行

jlink 参数列表

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 中

生成最小 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@17.0.5

看了一下还有 40MB 大小

生成包含 java.se 模块的 JRE

java.se 模块是一个聚合模块, 该模块没有任何代码, 只有一个 module-info.class 文件, 用于声明一些依赖, 产生聚合的作用, 达到引用该模块就相当于引用很多其他模块的效果

不建议直接引用 java.se 模块,因为它就相当于 Java 9 以前版本的 rt.jar 的内容

# 检查 java.se 中的模块
java --describe-module java.se
java.se@17.0.5
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
java.base@17.0.5
java.compiler@17.0.5
java.datatransfer@17.0.5
java.desktop@17.0.5
java.instrument@17.0.5
java.logging@17.0.5
java.management@17.0.5
java.management.rmi@17.0.5
java.naming@17.0.5
java.net.http@17.0.5
java.prefs@17.0.5
java.rmi@17.0.5
java.scripting@17.0.5
java.se@17.0.5
java.security.jgss@17.0.5
java.security.sasl@17.0.5
java.sql@17.0.5
java.sql.rowset@17.0.5
java.transaction.xa@17.0.5
java.xml@17.0.5
java.xml.crypto@17.0.5

看了一下还有 82MB 大小

生成包含完整 JavaFX 模块的 JRE

官网 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 中包含的模块
java.base@17.0.5
java.compiler@17.0.5
java.datatransfer@17.0.5
java.desktop@17.0.5
java.instrument@17.0.5
java.logging@17.0.5
java.management@17.0.5
java.management.rmi@17.0.5
java.naming@17.0.5
java.net.http@17.0.5
java.prefs@17.0.5
java.rmi@17.0.5
java.scripting@17.0.5
java.se@17.0.5
java.security.jgss@17.0.5
java.security.sasl@17.0.5
java.sql@17.0.5
java.sql.rowset@17.0.5
java.transaction.xa@17.0.5
java.xml@17.0.5
java.xml.crypto@17.0.5
javafx.base@20.0.1
javafx.controls@20.0.1
javafx.fxml@20.0.1
javafx.graphics@20.0.1
javafx.media@20.0.1
javafx.swing@20.0.1
javafx.web@20.0.1
jdk.jsobject@17.0.5
jdk.unsupported@17.0.5
jdk.unsupported.desktop@17.0.5
jdk.xml.dom@17.0.5

通过这种方式生成的 JRE, 已经包含了 JavaFX 相关依赖模块, 可以用来直接运行 JavaFX 应用程序, 可以分发给其他用户, 而其他用户不需要再下载 JavaFX SDK / JMods

使用该 JRE 运行 JavaFX 程序, 以前面章节的 Modular Java Project 工程为例

# 编译
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 中, 并生成该模块中主类的启动程序命令, 以前面章节的 Modular Java Project 工程为例

会将该自定义模块依赖的其他模块一同打包到 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 发给别人, 即可直接运行功能

打包与分发

  • Fat Jar: 运行时不再需要 -classpath / –module-path 参数, 将依赖 Jar 中的相关类抽取到目标 Jar 包中
  • jpackage: Java 14 提供的打包分发工具, Windows 平台需要 Wix 3.0 及以上版本的支持
  • 自定义的包含 JDK 和 JavaFX 依赖模块的 JRE 里因为没有 jpackage 工具, 所以不能使用, 需要使用 JDK 的 jpackage 工具, 所以需要 –module-path 来补充 JavaFX 的依赖模块, 可参考 使用命令行工具运行 章节

jpackage

单独看 jpackage 可能不太好理解, 但是如果是顺着上文下来的, 那么自然而然就会 jpackage 打包了, 一脉相承

  • 首先通过 jpackage --help 查看选项列表说明, 官方已经给出了针对多种情况的简单命令行案例, 而且把各种参数已经做好了分类
  • 几个关键名词和参数
    • application package suitable: 就是安装程序包, 双击后是一套安装程序, 安装好后才能在指定位置找到 exe 运行程序
    • application image: 就是运行程序包, 双击直接运行, 无需安装的那种
    • –name: 打包出的 exe 的名称, 生成的存放 exe 的文件夹也是这个名称
    • –type: 打包的类型, Windows 平台支持 {“app-image”, “exe”, “msi”}, 只有 app-image 是打包为可执行程序, 其他都是安装包程序
    • –input: 指定一个资源文件夹, 该文件夹下的所有内容都会被打包
      • 我目前只有在打包非模块化工程时, 通过该参数指定 jar 包所在的路径, 目录下也只有一个可运行的 jar 包, 暂没感觉到其他用处
    • –dest: 生成物的输出路径, 默认是当前所在工作路径, 该路径与 --input 指定的路径 一定不能相同, 会生成超深文件夹, 无法直接删除(可递归从里向外删除)
    • –icon: 指定可执行程序的图标
    • –module-path: 默认包含 JDK JMods 目录, 额外补充依赖模块的查找路径, 可以是模块的目录, 也可以是模块化的 jar 的路径, 用 ; 分割
    • –module: 指定应用程序的主模块(和主类), 必须在模块查找路径所覆盖的范围内, 一旦指定该选项, 主模块将会链接到运行时?? 我觉得应该是自动生成合适的 JRE 的意思
    • –main-jar:
      • 非模块化的 Jar 打包为 exe 时需要该参数, 同时 –input 参数必须存在, 且该 jar 必须在 –input 指定的目录下
      • 模块化的 Jar 也可以用该参数打包 exe, 但是没必要, 直接用编译好的模块 class 打包就好了, 无需转成 Jar 再打包. 我猜的
    • –main-class
      • 如果 jar 是可直接运行的 jar, 则可以不使用该参数指定运行的主类. 也可以使用该参数将运行主类指向其他可运行类
      • 如果 jar 是不可直接运行的, 但是包含可运行主类, 可通过该参数指定
    • 其他 win 参数: … 自行查看 help 的解释

wix v3

使用 jpackage 需要安装 WIX v3 或更高版本, 官网, GitHub, 安装完貌似不需要配置环境变量即可直接使用

Modular JavaFX Project (非 Maven / Gradle)

准备

# 设置环境变量
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

Modular JavaFX Maven Project

准备

# 设置环境变量
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 就好了

Non-Modular Java Project

要先把非模块化工程打包成一个可以运行的 Fat Jar, 这里以以前的一个 非模块化的 swing 工程为例, 打出的 jar 是 qq.speed.local.data.manager.jar, 确保通过 java -jar qq.speed.local.data.manager.jar 可直接运行

通过 IDEA 将工程打包为可运行 Jar

在 项目结构 里添加 Jar Artifact, 选择正确的 Main Class, 默认勾选 Extract to the target JAR 以便于生成无依赖的 Fat Jar. 其他同样默认即可. 然后在 [Build – Build Artifacts …] 选项里 [build] 该 Jar Artifact, 就会在 out\artifacts 目录下生成可直接运行的 Jar

将可运行 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 中

将可运行 Jar 打包为安装程序

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 就好了

相关命令整理

jdeps

# 查看一个jar的依赖. 包含模块, 每个类的import等
jdeps qq.speed.local.data.manager.jar

jlink

# 生成只包含 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

Modular JavaFX Project (非 Maven / Gradle)

打包为安装程序
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

Modular JavaFX Maven Project

打包为安装程序
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

Non-Modular Java Project

打包为安装程序
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

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年12月7日
下一篇 2023年12月7日

相关推荐