
如果你写过几年 Java平时主要跑后端服务突然要做一个带界面的内网小工具第一反应大概率是“忍一忍用 Swing”。结果拖完控件一看那画风真的有点倒胃口。后来我改用了 JavaFX配合 IDEA、JDK17 和 OpenJFX做过批量文件处理、内部数据看板、简易可视化导入导出工具甚至帮同事封装过一个带界面的日志分析器。这套流程最大的价值在于从建项目到打出 exe最快一次不到四十分钟。这篇文章我就把这套完整流程拆开讲包括环境配置、应用开发、依赖处理以及最后怎么用 JDK 自带的 jpackage把 JavaFX 应用打包成 Windows 下双击就能跑的 exe。整个过程没有商用框架、不写一行 C、也不依赖外网工具适合给内部小团队做工具分发、个人效率软件或者大学生交课程设计。1. 项目设计与技术选型为什么是 JDK17 OpenJFX1.1 JavaFX 和 Swing到底差在哪很多人对 Java 桌面开发的印象还停留在 Swing 时代那确实很劝退。JavaFX 是 Oracle 推出的现代化 UI 框架它的定位就是让 Java 开发者能写出一套像样的桌面应用而不只是“能弹个窗口”。JavaFX 设计上更接近 WPF 和 QML 的思路界面用 CSS 做样式布局有 BorderPane、GridPane 这类容器控件也丰富得多还内置了动画、图表、并发工具做一个带交互的业务工具完全够用。到了 JDK 11Oracle 把 JavaFX 从 JDK 中剥离出来改成由 OpenJFX 开源项目单独维护版本号跟 JDK 保持同步。也就是说现在想要在 JDK17 上用 JavaFX你得额外引入 OpenJFX 的依赖。这一步看着多了一个环节实际上反而是好事。一来 JavaFX 不再受 JDK 完整发布节奏的拖累一年能出多个小版本修复速度更快二来通过 Maven 或 Gradle 引入依赖版本干净可控不会出现别人电脑上预装了一个老 JDK 导致 JavaFX 版本被捆绑的尴尬。1.2 JDK17 在这个组合里的地位JDK17 是 2021 年发布的 LTS 长期支持版本技术圈对这个版本认可度很高。原因有几个它引入了 sealed class、扩展了 switch 表达式、支持了 pattern matching for instanceof这些语法让代码写起来更紧凑Record 类在 16 转正JavaFX 项目的实体类写起来舒服很多更重要的是长期支持意味着你的工具以后要给别人用对方不需要纠结频繁升级 JDK 的问题。还有人会问都 2024 年了JDK21 都出来了为什么还要用 17我的理由是17 的生态最稳。各种 IDE 插件、构建工具、第三方库对 17 的支持最成熟遇到问题搜解决方案也最容易。JavaFX 17 是 OpenJFX 的 LTS 版本两者配合不用考虑版本兼容问题。除非你项目有特殊需求否则 17 就是桌面小工具最省心的选择。1.3 打包 exe 的三条路线为什么选了 jpackage做桌面应用的人都知道写界面只完成了一半另一半是怎么把程序发给别人。Java 项目的 exe 打包方案常见的就三种jpackage、Launch4j、GraalVM Native Image。三者也有人对比过热词我直接说一下实际体验。Launch4j 是给 jar 包套了一层 Windows exe 外壳真正跑起来的时候仍然依赖目标机器上有没有 JRE如果对方电脑没装 Java程序直接启动不了。这不是真正的“免环境”分发。GraalVM Native Image 能把 Java 应用编译成原生可执行文件启动速度确实快体积也小但 JavaFX 对它支持不算全一些控件库、反射场景、动态代理很容易踩坑。而且 Native Image 编译时间长、内存要求高做一个内部小工具很不划算。jpackage 是 JDK 自带的打包工具JDK14 引入JDK16 开始转正专门用于构建自包含应用。它能把 JRE、JavaFX 模块、你的应用代码一起打包生成 exe 安装包目标机器不需要安装任何 Java 环境。同时 jpackage 还支持生成免安装的 app-image 目录拷给谁都能直接跑。对我这种追求“快速交付”的人来说这条路线最省事。1.4 为什么不建议手动下 jar 包网上很多老教程是让你去 OpenJFX 官网下载 SDK然后把 lib 目录里的 jar 包手动加进 IDEA 的 Project Structure。这个做法能跑但问题在后头JavaFX 的 native 库是按平台拆分的Windows 下的 jar 包和 Linux 下的不一样依赖传递关系还牵扯 javafx-base、javafx-graphics你漏一个就起动不了。而且将来升级版本手动删包换包非常容易出错。所以我强烈建议用 Maven 管理依赖。只需要在 pom.xml 里声明一个 javafx-controlsMaven 会自动把 javafx-graphics、javafx-base 一起拉下来版本统一、干净明了后面打包时也能用 Maven 插件统一处理依赖目录。2. 环境准备与项目初始化从 JDK17 到 IDEA 第一家窗口2.1 JDK17 安装与环境变量配置开始之前先确认你机器上有 JDK17。下载地址不写了直接去 Oracle 官网或 AdoptiumEclipse Temurin下载 Windows x64 的 zip 或安装版都行。我个人习惯用 zip 解压版因为不污染注册表换版本也方便。安装完后做两件事一是设置系统环境变量JAVA_HOME指向你的 JDK 解压目录比如D:\soft\jdk-17二是在Path变量里增加%JAVA_HOME%\bin。注意啊Path里要放在比较靠前的位置因为很多电脑上可能还有 Python、MySQL 自带的其他 Java变量顺序会影响最终生效版本。验证是否成功打开命令提示符输入java -version javac -version如果显示的是 17 开头就说明环境变量配好了。如果你机器上装过别的 JDK 或 JRE这里经常会显示老版本那就是Path里其他 Java 路径排在前面了把老的去掉或把%JAVA_HOME%\bin移到最前就行。2.2 IDEA 里创建 JavaFX 项目的两种方式IDEA 社区版和 Ultimate 版都能做 JavaFX 开发用社区版就行。新建项目时IDEA 默认模板里其实没有 JavaFX 选项所以有两种方式第一种用 Maven 骨架。直接用命令行生成mvn archetype:generate -DarchetypeGroupIdorg.openjfx -DarchetypeArtifactIdjavafx-archetype-fxml -DarchetypeVersion0.0.6 -DgroupIdcom.example -DartifactIdrenamer -Dversion1.0.0生成完用 IDEA 打开它会自动识别为 Maven 项目并下载依赖。第二种直接创建一个普通 Java Maven 项目然后自己在 pom.xml 里加入 JavaFX 依赖。这种方式更灵活不用担心模板里的 FXML 文件要不要删。我下面的示例就按这个方式走因为我觉得做内部工具时用纯 Java 代码写界面比 FXML 文件更直观少一层文件跳转。2.3 pom.xml 的核心配置创建好 Maven 项目后pom.xml 里加入 JavaFX 依赖。这里有一个容易被忽略的点你只需要引入javafx-controls它会自动带上javafx-graphics和javafx-base。properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target javafx.version17.0.2/javafx.version /properties dependencies dependency groupIdorg.openjfx/groupId artifactIdjavafx-controls/artifactId version${javafx.version}/version /dependency /dependencies如果后面你想用 FXML 分离界面再补一个javafx-fxml依赖。我这次的项目不引入 FXML所以控制界面、控制逻辑都集中在 Java 类里对一个 500 行不到的小工具来说反而更好维护。2.4 第一次运行No JavaFX runtime 怎么处理写到这很多入门的人会卡在第一步写了个Application子类直接在 IDEA 里点运行结果报错“JavaFX runtime components are missing, and are required to run this application”。这个报错的本质是JavaFX 要求自己必须作为模块加载不能老实地躺在 classpath 里当普通 jar。IDEA 直接运行 JavaFX 应用时需要让它把 JavaFX 作为模块暴露出去。在 IDEA 的 Run Configuration 里找到 VM options 一栏加上--module-pathtarget\classes --add-modulesjavafx.controls如果你用的是javafx-archetype-fxml模板其实不用手动配模板里已经带了javafx-maven-plugin你直接运行mvn javafx:run就行。不过我这里更推荐一种一劳永逸的配置在 pom.xml 里加上javafx-maven-plugin之后所有运行都用 Maven 来执行不依赖 IDEA 的运行配置。build plugins plugin groupIdorg.openjfx/groupId artifactIdjavafx-maven-plugin/artifactId version0.0.8/version configuration mainClasscom.example.renamer.Main/mainClass /configuration /plugin /plugins /build然后命令行执行mvn javafx:run这是我在开发阶段最常用的方式因为它的启动参数是 Maven 插件按 JavaFX 标准自动生成的环境不会有偏差。3. 应用开发实战做一个批量重命名工具3.1 工具需求与界面规划我选了一个最能体现桌面应用实用性的场景批量重命名文件。很多人电脑里有大量照片、下载文件、导出报表文件名乱七八糟手动改到崩溃。这种工具做起来逻辑不复杂但界面非常直观适合作为完整例子。界面结构我用 JavaFX 的 BorderPane 布局顶部放工具栏ToolBar中间是文件列表TableView底部放状态栏。工具栏从左到右依次是“选择文件夹”按钮、“前缀输入框”、“执行重命名”按钮。“选择文件夹”事件里用 DirectoryChooser 弹系统目录选择框选中后递归读取目录下所有文件并展示在 TableView 中。核心 Code 大致长这样public class Main extends Application { private final ObservableListFileItem fileItems FXCollections.observableArrayList(); private TableViewFileItem tableView; private TextField prefixField; private File currentDir; Override public void start(Stage stage) { BorderPane root new BorderPane(); ToolBar toolbar new ToolBar(); Button chooseBtn new Button(选择文件夹); prefixField new TextField(renamed_); Button renameBtn new Button(执行重命名); toolbar.getItems().addAll(chooseBtn, new Label(前缀:), prefixField, renameBtn); tableView new TableView(); TableColumnFileItem, String nameCol new TableColumn(当前文件名); nameCol.setCellValueFactory(cellData - cellData.getValue().nameProperty()); nameCol.setPrefWidth(400); TableColumnFileItem, String newNameCol new TableColumn(新文件名); newNameCol.setCellValueFactory(cellData - cellData.getValue().newNameProperty()); newNameCol.setPrefWidth(400); tableView.getColumns().add(nameCol); tableView.getColumns().add(newNameCol); tableView.setItems(fileItems); root.setTop(toolbar); root.setCenter(tableView); chooseBtn.setOnAction(e - chooseDirectory(stage)); renameBtn.setOnAction(e - doRename()); Scene scene new Scene(root, 850, 550); stage.setTitle(批量重命名工具); stage.setScene(scene); stage.show(); } private void chooseDirectory(Stage stage) { DirectoryChooser chooser new DirectoryChooser(); File dir chooser.showDialog(stage); if (dir ! null) { currentDir dir; loadFiles(dir); prefixField.requestFocus(); } } private void loadFiles(File dir) { fileItems.clear(); File[] files dir.listFiles(); if (files null) return; for (File file : files) { if (file.isFile()) { fileItems.add(new FileItem(file.getName(), )); } } tableView.refresh(); } }FileItem 是一个简单的记录类用 JavaFX 的 SimpleStringProperty 包装文件名和新名称这样表格列可以实时刷新。3.2 重命名逻辑与防覆盖处理重命名功能不复杂但要注意两点一是文件扩展名不能被拼音掉二是新文件名不能和目录里已有文件冲突。我给重命名规则设计成“前缀 三位序号 原扩展名”比如renamed_001.jpg、renamed_002.jpg。private void doRename() { if (currentDir null || fileItems.isEmpty()) { showAlert(请先选择文件夹); return; } String prefix prefixField.getText().trim(); if (prefix.isEmpty()) { showAlert(请输入前缀); return; } SetString existingNames new HashSet(); for (File f : currentDir.listFiles()) { existingNames.add(f.getName().toLowerCase()); } int index 1; for (FileItem item : fileItems) { File original new File(currentDir, item.getName()); String extension ; String name item.getName(); int dotIndex name.lastIndexOf(.); if (dotIndex 0) { extension name.substring(dotIndex); } String newName prefix String.format(%03d, index) extension; File target new File(currentDir, newName); if (existingNames.contains(newName.toLowerCase())) { newName prefix String.format(%03d_, index) System.currentTimeMillis() % 1000 extension; target new File(currentDir, newName); } if (original.renameTo(target)) { existingNames.add(newName.toLowerCase()); item.setNewName(newName); } index; } loadFiles(currentDir); }这段代码里防冲突处理是实战里必踩的坑。如果你不做冲突检查批量重命名到一半突然有一个文件重名整个流程就中断了。处理逻辑也很直白先收集当前目录所有文件名重命名前检查目标名是否存在如果存在就拼接一个时间戳后缀保证一定能执行成功。3.3 线程问题别卡死界面很多人第一次写 JavaFX 应用喜欢在按钮事件里直接做耗时操作。如果只是遍历几百个文件问题不大但你如果以后把工具扩展成处理大文件复制、压缩就必须考虑线程问题。JavaFX 的界面线程叫 FX Application Thread所有 UI 更新必须在这个线程上执行但耗时任务不能堵在这个线程。简单的方案是用Task配合Platform.runLater。比如在doRename里如果文件量很大可以先在后台线程里执行重命名逻辑再通过 Platform.runLater 把结果塞回 TableView。我这个批量重命名工具因为普通场景就是几十个文件直接同步做完全没问题但我在代码里还是留了 Task 的注释提醒自己以后扩展的时候别踩死界面的坑。如果文件量大推荐的方向是把doRename改成TaskVoid task new Task() { Override protected Void call() { // 重命名逻辑 return null; } }; task.setOnSucceeded(e - loadFiles(currentDir)); new Thread(task).start();这能保证界面始终可响应不会出现“未响应”的白屏窗口。3.4 和 IDEA 联调时的体验开发过程中我基本都是直接mvn javafx:run启动。这里提一个 IDEA 的小技巧配置好 Maven 后可以在 IDEA 右侧 Maven 面板里找到javafx:run这个 goal双击直接运行。第一次跑起来会下载一堆依赖需要等一会儿之后就很快了。窗口起来以后选一个测试目录点“执行重命名”看表格里的新文件名字段是否跟着刷新。我在开发时总会准备一个放满测试文件的文件夹里面故意放几个名字相同的文件用来验证防冲突逻辑。这一步跑通后面打包成 exe 后就不用再频繁反复测试了。4. 打包成 exejpackage 完整实战4.1 jpackage 的前置条件打包之前先确认命令行能直接调用jpackage。JDK17 自带这个工具路径在%JAVA_HOME%\bin\jpackage.exe。你可以运行jpackage --version如果提示找不到命令说明%JAVA_HOME%\bin不在 Path 里或者你当前用的是 JRE 而不是 JDK。这里有个常见的误区只安装了 JRE 的机器上是没有 jpackage 的必须完整安装 JDK。还有一个容易忽略的条件jpackage 打包 exe 时内部依赖了 WiX Toolset。JDK16 以后 jpackage 已经内置了轻量级的 WiX 组件不需要你再手动装 WiX这点比早期版本省事很多。如果你用更老的 JDK14/15 去打包则需要额外下载 WiX 3.0 以上版本并配置环境变量非常麻烦。所以用 JDK17 是绕开这个坑最直接的办法。4.2 先构建普通 jarjpackage 打包时需要一个可运行的 jar作为应用的入口。先用 Maven 构建mvn clean package构建完到target/目录下看会有一个renamer-1.0.jar。但你直接双击这个 jar 是跑不起来的因为它没有 Main-Class 属性而且即使有JavaFX 也不在 classpath 里。为了让 jpackage 能正确找到入口需要在 pom.xml 里配置 manifest。我用 maven-jar-plugin 指定 Main-Classplugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.3.0/version configuration archive manifest mainClasscom.example.renamer.Main/mainClass /manifest /archive /configuration /plugin同时还需要把 JavaFX 相关的依赖 jar 复制到一个统一目录打包时让 jpackage 当作模块路径里的内容加载。最简单的方式是mvn dependency:copy-dependencies -DoutputDirectorytarget/lib这条命令会把 javafx-controls、javafx-graphics、javafx-base 等所有依赖 jar 复制到target/lib。注意 JavaFX 的 jar 包自带 module-info所以它们可以出现在模块路径上这是后面能打包成功的关键。4.3 非模块化项目的 jpackage 命令很多 JavaFX 教程要求你必须写module-info.java走模块化路线。真实项目里除非你从零开始完全控制依赖否则第三方库动不动就破模块化规则坑很多。所以我这次的批量重命名工具走的是“非模块化应用 jpackage 模块路径”这条路代码简单打包也不复杂。完整的打包命令如下Windows cmd 环境set JAVA_HOMED:\soft\jdk-17 set PATH%JAVA_HOME%\bin;%PATH% mkdir dist jpackage --type exe ^ --name Renamer ^ --input target ^ --main-jar renamer-1.0.jar ^ --main-class com.example.renamer.Main ^ --module-path %JAVA_HOME%\jmods;target\lib ^ --add-modules javafx.controls ^ --icon src\main\resources\icon.ico ^ --dest dist ^ --app-version 1.0.0 ^ --vendor Demo ^ --win-menu ^ --win-shortcut ^ --win-dir-chooser这里几个参数是重点--input target表示把 target 目录下的所有内容作为应用输入会自动带上renamer-1.0.jar和lib/目录。--main-jar renamer-1.0.jar指定入口 jar。--module-path这里有两段第一段是 JDK 自带的 jmods第二段是前面复制出来的 JavaFX 依赖 jar。jpackage 会根据这两个路径组装一个最小运行时。--add-modules javafx.controls告诉 jlink 把 JavaFX 的 controls 模块加进运行时否则就算依赖放在模块路径里也不会被链接进去。--win-menu和--win-shortcut让安装包自动创建开始菜单和桌面快捷方式。--win-dir-chooser安装过程中允许用户选择安装目录这个对于分发给同事特别有用不然默认只能装到 Program Files权限问题很烦。如果打包过程中提示 javafx 相关模块找不到多半是target/lib目录里 javafx 的 jar 不在重新执行一遍mvn dependency:copy-dependencies即可。4.4 生成 app-image 和 exe 安装包上面的命令会生成一个 exe 安装程序目标文件在dist/Renamer-1.0.exe。双击它会走 Windows 标准的安装流程安装完成后从快捷方式启动程序就能正常运行。由于 jpackage 把精简的 JRE 和 JavaFX 运行时一起打包进安装目录目标机器上不需要预装任何 JDK 或 JRE。如果你不想走安装流程比如自己拷给同事直接免安装运行可以用--type app-image替代--type exejpackage --type app-image ^ --name Renamer ^ --input target ^ --main-jar renamer-1.0.jar ^ --main-class com.example.renamer.Main ^ --module-path %JAVA_HOME%\jmods;target\lib ^ --add-modules javafx.controls ^ --dest dist生成的是一个dist/Renamer/文件夹里面有几个关键子目录Renamer.exe真正的启动器app/存放应用 jar 和依赖 jarruntime/精简后的 JDK 运行时你把整个 Renamer 文件夹拷到任意 Windows 机器上双击里面的Renamer.exe就能运行。这在团队内部使用的时候非常方便不用分发安装包也不用担心卸载残留。4.5 打包完成后验证与体积优化我第一次打包完exe 安装包大概 55MB安装完占磁盘 130MB 左右。这个体积在现在动辄几百 MB 的 Electron 应用面前已经算轻量了。如果还想更小可以考虑在 jpackage 时用--strip-native-commands或者手动精简 jmods 目录里的模块。但说实话内部工具 50MB 完全可以接受不用过度优化。验证时我一般先在打包机器上跑一遍再去一台“干净”的 Windows 虚拟机里装一遍测试。重点看两点第一是否提示缺 DLL 或 Java 环境第二双击快捷方式后 JavaFX 窗口能否正常弹出。只要这两点没问题就可以放心发给别人了。5. 常见报错与排查技巧实录5.1 “JavaFX runtime components are missing” 的两种情况这个报错是 JavaFX 新手最容易遇到的背后含义是 JavaFX 没有以模块方式加载。在 IDEA 里运行报错按 2.4 节说的用javafx-maven-plugin的mvn javafx:run启动就行。但在打包后的 exe 里报同样错误就需要查 jpackage 命令里的--add-modules参数确认是否加了javafx.controls同时确认target/lib里有没有 javafx 相关 jar。我之前遇到过一种情况用 jpackage 打包时忘了--module-path里带第二段target/lib结果依赖 jar 虽然被复制出来了jpackage 却根本没把它当模块路径看运行时自然找不到 JavaFX。所以检查时优先看命令里的模块路径是不是两条都写了。5.2 jpackage 提示 WiX 相关错误怎么办前面说了 JDK16 以后 jpackage 内置了 WiX但有些环境下还是会报找不到 WiX 工具的错误。常见原因是jpackage.exe所在 JDK 版本低于 16或者JAVA_HOME指向了 JRE 而不是 JDK。你可以在命令行里执行%JAVA_HOME%\bin\jpackage --version如果打印出来的版本是 15 或更低直接换 JDK17。另外还有一种情况是系统里装了老版本 JDK但命令行用的是新版本 JDK 的 jpackage这个版本不一致的现象很隐蔽建议在打包脚本开头显式指定set JAVA_HOMED:\soft\jdk-17和set PATH%JAVA_HOME%\bin;%PATH%。5.3 打包后的 exe 双击没反应或闪退这个问题通常在开发机器上不容易出现因为它大概率跟目标机器缺少字体、显卡驱动或系统版本有关。如果你自己机器上双击闪退先临时在 jpackage 命令里加一个--win-console参数它会额外弹出一个命令行窗口把 Java 的异常堆栈打出来。jpackage --type exe --win-console ...这种控制台模式非常有用一旦崩了就能看到具体异常信息。实际中我遇到过的问题是 JavaFX 的硬件加速在部分老显卡上启动失败解决方法是给启动器加系统属性禁用硬件加速。在 jpackage 打包时可以通过--java-options传入 JVM 参数--java-options -Dprism.ordersw这个参数强制 JavaFX 使用软件渲染虽然性能稍有损失但兼容性最好。如果你做的工具只是表格、表单这类 UI完全感觉不到差别。5.4 “cannot determine path to tools.jar library for 17” 怎么处理热词里有人搜过这个报错我也踩过。这个错误一般不是 JavaFX 本身的问题而是某些老构建插件、老版 Maven 插件在 JDK9 之后仍然尝试寻找tools.jar。JDK9 开始JDK 目录里已经没有tools.jar了相关 API 都移入了jdk.compiler模块。解决办法是找到那个引用了 tools.jar 的配置。最常见的是 Maven 的maven-compiler-plugin版本过老升级到 3.8.1 以上或者检查 pom.xml 里有没有compilerArguments这种老式参数把它去掉。IDEA 里如果还有这种问题检查 Project Structure 里的 SDK 配置确保选的是 JDK17 而不是一个被坑爬过的 JRE。如果你看到这个报错时已经用了较新的 Maven 插件还可以试试加一个依赖dependency groupIdjdk.tools/groupId artifactIdjdk.tools/artifactId version1.8/version scopesystem/scope systemPath${java.home}/../lib/tools.jar/systemPath /dependency但注意这只是临时绕过不建议长期依赖因为 tools.jar 在新 JDK 上不存在万一别人换更新的 JDK构建环境会更乱。5.5 杀毒软件误报与图标问题jpackage 生成的 exe 没有数字签名所以 360、火绒、Windows Defender 有时候会弹风险提示。这个问题没有百分之百必然的解法内部工具一般就是团队内约定加白名单或者用企业证书对 exe 做签名。产品级使用者还是建议购买代码签名证书否则用户信任成本很高。另一个坑是图标格式。jpackage 的--icon参数要求.ico文件不能拿一张 PNG 改名硬充。而且 ico 里最好包含 16、32、48、256 多个尺寸否则部分 Windows 界面下快捷方式图标会很模糊。制作多尺寸 ico 最简单的方式是找一个在线转换工具或者用 Python 的 Pillow 库生成多尺寸 ico 文件from PIL import Image img Image.open(icon.png) img.save(icon.ico, sizes[(16,16),(32,32),(48,48),(256,256)])把这张 ico 放到src/main/resources/下打包命令里用相对路径或绝对路径引用它就行。最后分享一个小习惯这套流程跑通之后我把 jpackage 的完整命令整理成了一个build.bat扔在项目根目录。脚本里用变量管理版本号、应用名、图标路径每次更新版本只需要改一行变量。比如这次改了代码执行build.bat等几十秒dist目录下就是一个全新的 exe。我把这个脚本也分享到这里你根据自己的项目名调整即可echo off setlocal set APP_NAMERenamer set APP_VERSION1.0.0 set MAIN_JARrenamer-1.0.jar set MAIN_CLASScom.example.renamer.Main set JAVA_HOMED:\soft\jdk-17 set PATH%JAVA_HOME%\bin;%PATH% call mvn clean package || goto :error call mvn dependency:copy-dependencies -DoutputDirectorytarget/lib || goto :error mkdir dist 2nul jpackage --type exe ^ --name %APP_NAME% ^ --input target ^ --main-jar %MAIN_JAR% ^ --main-class %MAIN_CLASS% ^ --module-path %JAVA_HOME%\jmods;target\lib ^ --add-modules javafx.controls ^ --icon src\main\resources\icon.ico ^ --dest dist ^ --app-version %APP_VERSION% ^ --win-menu ^ --win-shortcut ^ --win-dir-chooser || goto :error echo. echo 打包成功: dist\%APP_NAME%-%APP_VERSION%.exe exit /b 0 :error echo. echo 打包失败请检查上一步输出。 exit /b 1说句实在话只要环境搭好、命令顺手JavaFX 和 JDK17 这套组合做内部桌面工具非常靠谱。界面不丑、开发不慢、发布不折腾遇到问题也能靠 Java 生态堆出解决方案。如果你正在几个技术栈之间犹豫不妨拿这个批量重命名工具当练兵项目从 0 到 exe 走一遍感受会比看十篇文章都直观。