2023 IDEA 开发桌面图形界面程序 JavaFX 并导出 Jar 支持多系统 home 编辑时间 2023/08/16 ![JavaFX](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc3e9ada74050014006af3) <br> ## 前言 首先申明,我也是最近才研究,很多方法只是我跑通了分享出来,并不一定是最优解,如果以后有新的理解再回来更新。 先解释下 `JavaFX` 分为2个分支 1. 早期的是 `Oracle JDK 8` 自带的 `JavaFX` (恶心的地方是,OpenJDK 8无此功能 需要额外引入依赖,Oracle JDK8 已停止开源) 2. 后期的是 `OpenJDK 11` 及以上,引入开源的 `openjfx` (推荐) 本文基于的是后者,具体环境如下 系统: `Linux Deepin 20.9` / `Windows 11` JDK: `AdoptOpenJDK 17.0.7+7` IDE: `IntelliJ IDEA 2023.1.2 (Ultimate Edition)` (补上JDK下载地址: [https://adoptium.net/zh-CN/temurin/releases/](https://adoptium.net/zh-CN/temurin/releases/) ) Maven依赖: ```xml <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>17.0.6</version> </dependency> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>17.0.6</version> </dependency> ``` <br> **本文最终目标如下:** 简单实现一个 `JavaFX` 桌面图形界面程序 原生支持 `Windows` `Mac` `Linux` 三系统 导出为可执行程序 `jar` 文件 并附上完整JDK依赖文件夹 编写 `shell/bat` 脚本 即:无需安装JDK和配置系统环境变量,双击start.sh即可运行 <br> ## 折腾 <br> **新建项目** 左边栏选择 `JavaFX` JDK选择 `AdoptOpenJDK 17.0.7+7` 其余默认即可 ![New Project](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc4314da74050014006af4) <br> **配置并运行项目** 如果IDEA自带的Maven速度很慢,建议配置为自己本地的Maven,并配置阿里云镜像 (具体方法略过) 我个人还会删除一些不需要的依赖和文件 等待依赖加载完成并简单配置过后 执行 `src/main/java/HelloApplication` 下的 `main` 方法 ![Run Project](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc44a3da74050014006af5) 如果一切正常就会看到这个 `Hello World` 小程序执行出来的窗口 ![Window](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc44e0da74050014006af6) <br> **简单调整FXML组件样式** (这一步可跳过 不影响后续导出操作) 这里只演示基本的FXML组件,未实现具体功能 (那是另外的价钱 Doge) 完整代码开源地址: [https://gitee.com/zzzmhcn/JavaFX-Demo](https://gitee.com/zzzmhcn/JavaFX-Demo) <br> 修改3个文件: `hello-view.fxml` ```fxml <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <BorderPane xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.zzzmh.javafxdemo.HelloController"> <top> <ToolBar> <MenuBar> <Menu text="文件"> <MenuItem text="设置"/> <MenuItem text="退出"/> </Menu> <Menu text="编辑"> <MenuItem text="查找"/> <MenuItem text="替换"/> </Menu> <Menu text="关于"> <MenuItem text="帮助"/> <MenuItem text="关于"/> </Menu> </MenuBar> </ToolBar> </top> <left> <TreeView prefWidth="200"> <TreeItem expanded="true" value="菜单选项"> <children> <TreeItem expanded="true" value="用户配置"> <children> <TreeItem value="创建用户"/> <TreeItem value="用户列表"/> <TreeItem value="权限配置"/> </children> </TreeItem> <TreeItem expanded="true" value="统计报表"> <children> <TreeItem value="数据管理"/> <TreeItem value="导出报表"/> </children> </TreeItem> <TreeItem expanded="true" value="系统管理"> <children> <TreeItem value="查看日志"/> <TreeItem value="运行状态"/> </children> </TreeItem> </children> </TreeItem> </TreeView> </left> <center> <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER"> <Tab text="用户管理"> <TableView> <columns> <TableColumn text="ID"/> <TableColumn text="用户名"/> <TableColumn text="邮箱"/> <TableColumn text="权限"/> <TableColumn text="状态"/> <TableColumn text="创建时间"/> </columns> </TableView> </Tab> <Tab text="查看日志"> <TextArea/> </Tab> </TabPane> </center> </BorderPane> ``` `HelloController.java` ```java package com.zzzmh.javafxdemo; // 控制层 由于只写界面 懒得写控制层 这里清空代码 public class HelloController {} ``` `Application.java` ```java public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml")); // 只改了这个窗口大小 800x600 Scene scene = new Scene(fxmlLoader.load(), 800, 600); stage.setTitle("后台管理"); stage.setScene(scene); stage.show(); } ``` <br> 顺便插播一下图标如何配置 (这里的图标是指 标题栏+任务栏 不含桌面图标) 关键代码就一行 ```java stage.getIcons().add(new Image(this.getClass().getResourceAsStream("logo.png"))); ``` ![icon](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc6eccda74050014006b30) <br> 样式写完发现已经累了,功能实现就懒得写了,反之不影响正片,重点是导出jar ![累了](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dd9b09da74050014006c6d) <br> 执行看下大致效果 ![System](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc4d64da74050014006afc) <br> <br> **重点来了!** **正片开始!** **导出jar文件** 关键是第一步 新建一个Java文件,main方法里调用 `Application.main(args)` `Launcher.java` ```java public class Launcher { public static void main(String[] args) { HelloApplication.main(args); } } ``` `File` -> `Project Strucuture` ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc767ada74050014006b33) `Artifacts` -> `加号` -> `From modules with dependencies...` ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc772eda74050014006b35) `Main Class:` -> `Launcher` ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc7897da74050014006b37) 最后在菜单里 `Bulid` -> `Bulid Artifacts` -> `Rebulid` 操作完成后,在项目目录下就有一个out文件夹 切换到终端 cd到out目录下 先 `java -version` 看下是否是 `openjdk 17` (如果不是openjdk17 则需要你自行配置一下系统环境变量 略过) ```shell $ java -version openjdk version "17.0.7" 2023-04-18 OpenJDK Runtime Environment Temurin-17.0.7+7 (build 17.0.7+7) ``` 然后直接执行 `java -jar` ```shell java -jar JavaFX_Demo_jar/JavaFX-Demo.jar ``` **如果程序窗口正常启动说明第一部分大功告成** ![window](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dc7974da74050014006b38) <br> 如果你只是自己随便用用,到这一步就差不多了,环境变量保持在 `openjdk 17`,把jar文件复制出来,需要用的时候 `java -jar xxx.jar` 如果是像我一样还需要考虑发给别人用,或者自己有多系统,没jdk环境或者不能一直持续用jdk17等复杂情况 需要尽量免依赖直接执行的,就需要下列步骤 <br> **注意:文末已更新最新版的打包方式,下文中的Linux/Windows打包方式不是最优版!最优方案翻到末尾更新!** <br><br> **Linux系统** ![linux](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dd9b92da74050014006c6e) 新建文件夹 `JavaFXDemo` 分别新建4个目录 `jar` (放xxx.jar文件) `icon` (放icon文件 linux建议用svg/png格式) `jdk` (放jdk) `log` (暂时空着 后续会放log) `JavaFXDemo`根目录新建一个shell文件,内容如下 `linux_start.sh` ```shell #!/bin/bash cd /home/zzzmh/Desktop/JavaFXDemo nohup jdk/bin/java -jar jar/JavaFX-Demo.jar > log/java.log 2>&1 & ``` <br> 最终大致效果如下 ```treeview JavaFXDemo/ |-- jar/ | |-- JavaFX-Demo.jar |-- icon/ | |-- logo.png |-- jdk/ | |-- 省略... |-- log/ | |-- java.log `-- linux_start.sh ``` <br> ```shell # 给shell文件赋权 sudo chmod 755 linux_start.sh # 执行看下效果 bash linux_start.sh ``` <br> 到这一步只要执行shell脚本,就可以实现,无需改变系统环境变量,就可用指定的自带的jdk来执行jar文件 最后一步是桌面新建一个快捷方式,指向这个shell脚本 <br> 桌面快捷方式(linux deepin为例) 在桌面新建一个文件(路径替换你自己的) `javafx-demo.desktop` ```desktop [Desktop Entry] Name=JavaFX-Demo Comment=JavaFX Exec=/home/zzzmh/Desktop/JavaFXDemo/linux_start.sh %u Icon=/home/zzzmh/Desktop/JavaFXDemo/icon/logo.png Terminal=false Categories=net Type=Application ``` 桌面就出现了快捷方式图标,双击即可运行jar(shell指定jdk) ![desktop](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dd9838da74050014006c6c) PS:Java桌面图形项目启动需要2秒左右是正常现象,我暂时也没找到解决方法。 <br><br> **Windows系统** ![windows11](https://leanote.zzzmh.cn/api/file/getImage?fileId=64ddd9bcda74050014006cd0) 这里遇到点波折,我记得以前 `OracleJDK1.8` 自带的 `JavaFX` 是可以一次打包,多系统运行的(也可能是记错了) 但是这次用 `AdoptOpenJDK 17` 居然不行 `Linux` 下打包的比 `Windows` 下小,且拿到Win下运行会报错,看字面意思是缺少 `dll` 所以最后妥协,选择在 `Windows` 下重新编译一次Jar文件 过程就略过了,反正也是 `Idea` 拉代码,等 `Maven` 补依赖,添加 `Artifacts` ,最后 `build` 得到一份 `Windows` 版本的Jar,估计是自带的 `Win` 系统需要的dll 还是按照Linux那个目录去新建和摆文件 ```treeview JavaFXDemo/ |-- jar/ | |-- JavaFX-Demo.jar |-- icon/ | |-- logo.png |-- jdk/ | |-- 省略... |-- log/ | |-- java.log `-- windows_start.bat ``` 有几个不一样 jar必须是刚才在Windows下打包出来的jar icon必须是ico格式的,需要自己找地方png转ico jdk必须是Windows版本的 `AdoptOpenJDK 17` `windows_start.bat` ```bat @echo off start jdk\bin\javaw -jar jar\JavaFX-Demo.jar ``` 仅测试双击执行Bat正常启动程序 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64ddd367da74050014006cc2) 最后补上桌面快捷方式的方法 对 `windows_start.bat` 文件右击 -> 发送桌面快捷发送 桌面得到这样一个快捷方式 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64ddd3efda74050014006cc3) 右击重命名可以修改名字 右击属性-> 更换图标 -> 选择 `JavaFX-Demo\icon` 目录下的 `logo.ico` 即可更换图标 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64ddd46ada74050014006cc4) 最终效果如下 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64ddd479da74050014006cc5) ## END 本文代码的开源地址 [https://gitee.com/zzzmhcn/JavaFX-Demo](https://gitee.com/zzzmhcn/JavaFX-Demo) 暂时先折腾到这里,还留下几个尾巴 1. IDEA有一个 `Artifacts` 是专门导出 `JavaFX` 文件的,目测比我导出jar更科学,但目前测试下,不支持 `AdoptOpenJDK` 以后我再用其他 `OpenJDK` 再试试 2. 目前只测试了 `Linux` 下打包的jar `Windows` 系统不能正常启动,反过来还没测过,目前希望得到一种最优解,即一个jar包3系统都能正常执行 3. jdk包都有290MB+的体积,导致了程序打包后文件过大,目测jre只有30~40MB,之后会再测测看jre作为运行环境是否更科学 总之这次只是探路性质的尝试,后续还是先花时间把JavaFX学好,等再有时间深入研究导出jar的事,折腾出更完美的方案,再回来新开博客推翻这一篇的内容。 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64c9c860da74050014005b69) <br><br> ## 更新 2023.08.17 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dde717da74050014006ce5) 搞了个半吊子我还是浑身难受 google半天,顺便用翻译插件生啃了一会英文文档 [https://openjfx.io/openjfx-docs/#modular](https://openjfx.io/openjfx-docs/#modular) 发现原来官方早就提供了一种最简单的方式 `maven` -> `Plugins` -> `javafx` `javafx:run` 相当于是 JavaFX Maven 插件创建自定义运行 `javafx:jlink` 相当于是 编译一个含jre运行环境的最小的包并导出压缩包 所以前文中的配置 `Artifacts` 的方案可以废弃了 写完代码以后,直接运行 `maven` 中的 `clean` `javafx:jlink` ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dde825da74050014006ce9) 正常情况下,会在项目下多出一个 `target` 文件夹 里面有4个文件夹 1个压缩包 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dde876da74050014006cea) 4个文件夹就是打包后的完整程序 已含所有依赖 `app.zip` 压缩包就是上述4个文件夹压缩后的最终程序 我这里测试下来压缩包大约只有29MB 把压缩包放到合适位置解压,得到4个文件夹 进入 `app` 下的 `bin` 目录 用终端执行 ```shell ./app ``` 就执行成功了 ![](https://leanote.zzzmh.cn/api/file/getImage?fileId=64dde92cda74050014006ceb) 经测试,同样无法直接跨系统,也就是说, `win/mac/linux` 需要分别在对应系统下用maven打包,才可以获得对应系统的程序包,这个问题目前我还没找到解决方案 先折腾到这里 有后续再回来更新 送人玫瑰,手留余香 赞赏 Wechat Pay Alipay 2023 IDEA 开发JavaFX图形界面入门 并导出Jar 支持多系统 桌游说明书备忘录