我是JavaFX的初学者.我有一个作为STOMP WebSocket客户端运行的JavaFX&Spring Boot应用程序.我确实将其构建为JAR文件,并且它使用Java命令运行得很好.我的问题是如何将它包装在Windows/Mac/Linux安装程序(包括JRE)中,以便可以轻松地将其安装到其他计算机上. 我的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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.my-group</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0</version>
    <name>my-app</name>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>5.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>19.0.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>19.0.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

我的主要班级

package com.mygroup.myapp;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import java.awt.*;

@SpringBootApplication
public class MyApplication extends Application {

    private ConfigurableApplicationContext applicationContext;
    private Parent rootNode;

    public static void main(String[] args) {
        if (!SystemTray.isSupported()) {
            System.setProperty("java.awt.headless", "false");
        }
        Application.launch(args);
    }

    @Override
    public void init() throws Exception {
        applicationContext = SpringApplication.run(MyApplication.class);
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/login.fxml"));
        fxmlLoader.setControllerFactory(applicationContext::getBean);
        rootNode = fxmlLoader.load();
    }

    @Override
    public void start(Stage primaryStage) {
        Platform.setImplicitExit(false);

        primaryStage.setResizable(false);
        primaryStage.setScene(new Scene(rootNode));
        primaryStage.show();
    }

    @Override
    public void stop() {
        applicationContext.close();
        Platform.exit();
    }
}

我try 了this tutorial次使用JPackage,但安装后它不能运行.

推荐答案

背景

这个例子是only,使用Maven为一个JavaFX SpringBoot应用程序创建Windows Installer,jpackage running on Windows,在IntelliJ Idea的帮助下,尽管这些步骤对任何Java应用程序都有效,并且可以在没有 idea 的情况下执行(给定一些适应和修改).

这是notaguide on how to integrate SpringBoot with JavaFX,而是如何将这样的应用程序打包并部署到Windows机器上.

这里有很多步骤,原因是:

  1. 这本指南需要足够的细节,以至于几乎没有知识的人可以遵循它,而且仍然有合理的成功机会.
  2. 将应用程序打包到本地安装程序是一个很大的话题,而且有一些复杂的细节.
  3. 在StackOverflow上如何做到这一点有很多问题,而且有迹象表明,许多人未能实现他们创建易于安装和共享的打包应用程序的目标.
  4. 许多失败的人似乎在网络或YouTube频道上找到了替代的低质量资源,这导致了他们的失败.

只要您遵循本指南,您就应该能够在合理的时间内创建安装程序,并在出现错误时有机会对构建和安装过程进行故障排除.

Steps

将您的应用程序打包到一个

  1. Install Java

  2. Configure your JAVA_HOME和JDK 21+.

  3. Install the JavaFX SDK21.0 + / p >

  4. Install Wix 3.x.

    • Wix 4.x不适用于JDK 21 jPackage.
  5. 在IDEA 2023.3.2或更高版本中,创建一个new JavaFX project.

    • 将该项目命名为wininstalled.
    • Select Maven作为构建系统.
    • 不要从新建项目向导中 Select 任何其他选项.
  6. 删除module-info.java文件.

    • SpringBoot 3.2.x在Java平台模块系统上运行不佳.
    • 您将拥有一个非模块化项目,但将把JDK和JavaFX模块作为模块引用,而不是作为类路径上的JAR引用.所有与Spring相关的东西和其他应用程序依赖项都将作为类路径上的JAR引用,而不是作为模块引用.
  7. 在生成的项目的maven包装器中升级maven版本.

    • 编辑<your project home>\.mvn\wrapper\maven-wrapper.properties

    • 确保DistributionUrl中列出的版本至少为3.9.6.

      distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
      
    • 这是因为这里使用的akman jpackage plugin至少需要该版本的maven才能运行.

  8. 用下面的文件替换生成的pom.xml文件.

    • 保留pom.xml中的所有内容不变.
    • 您可以稍后调整它以自定义适合您的应用程序,但请确保您获得示例HelloWorld应用程序并运行first.
  9. Reimport the Maven project into Idea.

    • 由于项目Maven升级,导入可能不会立即生效,在这种情况下,请关闭项目,重新启动IDE并再次打开项目,然后重新同步Maven项目,希望之后可以将其与IDE同步.
  10. 转到HelloApplication,并try 运行它.

    • 应用程序将由于缺少JavaFX组件而失败.
  11. 编辑您的执行try 生成的运行配置,以便您可以set the VM arguments for it.

    • 设置这些值(根据需要针对其他或删除的JavaFX模块进行调整):

      --add-modules javafx.controls,javafx.fxml --module-path <your JavaFX SDK path>/lib
      
    • Make sure you set VM arguments NOT program arguments.

    • 请实际单击该链接并研究如何设置VM参数的图像,这样您就不会在这里犯错误.

  12. 再次运行应用程序,它应该在IDE中运行.

  13. 复制应用程序的图标文件from icon archive( Select All Download formats | Download ICO)到

    /src/main/resources/com/example/wininstalled/coffee.ico
    
  14. 转到IDE中的Maven面板,您的项目名称应该是pom.xml(wininstalled)中配置的名称,在Lifecycle下面,双击clean,然后单击install.

  15. 您的应用程序将被构建和打包.

包装做了什么

  • Maven将编译并测试您的应用程序.
  • 然后它会将您的代码和资源打包到一个JAR文件中.
  • 接下来,它将把非模块化依赖项复制到target/jpackage-input目录.
  • 然后,它将使用Akman插件配置调用jpackage.
  • jpackage将使用默认的一组模块和Akman配置中列出的模块(包括来自JDK的模块和来自JavaFX的模块)创建Java运行时的jlink映像,这些模块将被烘焙到映像中
    • 您甚至在映像中的任何位置都找不到模块jmod或JAR文件,但JRE和JavaFX在Windows Intel平台上运行的所有代码和本地库都在那里.
    • 这些模块化依赖项将从模块路径运行.
  • jpackage将从target/jpackage-input目录中复制非模块化依赖项,并配置它创建的应用程序启动可执行文件,以便与类路径上的非模块化jar一起运行.
  • jpackage将调用Wix来为应用程序创建安装程序.
    • 在此示例中,我们 Select 了EXE而不是MSI作为安装程序类型.
  • 安装程序是<your project>/target/jpackage/<your project name>.exe文件.

安装应用程序

  • 要运行安装程序,请在Idea中 Select Terminal选项卡

    • 如果您愿意,也可以使用命令提示符,或者在资源管理器中双击安装程序exe.
  • 执行安装程序

     .\target\jpackage\jpackage\wininstalled-24.01.1013.1128.exe
    
    • 安装程序名称后的数字将随每个版本而更改,它们与jpackage配置分配给Windows的版本号相匹配,以用于应用程序更新,并在用户查询操作系统中已安装应用程序的版本信息时显示.
  • 安装程序将执行并将应用程序安装到您的计算机上.

  • 应用程序安装在以下位置:

     \Users\<your username>\AppData\Local\<your app name>\<your app name>.exe
    
  • 带有您配置的咖啡图标的应用程序的快捷方式将添加到您的桌面.

  • 该应用程序也可以从Windows开始菜单,

  • 可以通过Windows控制面板中的Windows添加或删除程序项删除该应用程序.

  • 你可以双击你的应用程序图标来运行你的应用程序.

  • 您可以通过执行已安装的.exe文件(不是安装程序exe,而是程序exe)从命令行运行应用程序.

更新应用程序

  • 如果运行新的构建,Maven将清理现有的jPackage构建目录并重新构建它们的内容.
  • 每个版本都有时间戳,并为应用程序生成唯一的Windows应用程序版本ID.时间戳将晚于前一个版本,因此版本将更高.
  • 之所以使用时间戳版本控制,是因为Windows会在您try 安装应用程序时判断应用程序版本.如果已经安装了相同版本的相同应用程序,则新的安装try 将失败. for each 构建应用新的时间戳版本是避免由于try 重新安装相同的应用程序版本而导致更新失败的一种便捷方法.时间戳还有助于了解您在计算机上安装了哪个版本以及它是在何时创建的.
  • 当您运行新创建的安装程序时,Windows将检测到已经安装了版本号较低的应用程序,然后将其删除,并将其替换为您的新应用程序版本.
  • 现在,双击桌面上相同的已安装图标将运行该应用程序的新版本.

故障排除

当事情出错的时候,这是很有可能的...例如,也许你点击已安装的应用程序的图标,没有任何react ,然后你认为你被卡住了,但你并没有...

  • 确保您可以在IDE中运行该应用程序.
  • 确保maven的包目标可以构建JAR.
  • 确保您可以使用已安装的JDK从命令行运行该JAR,并将JavaFX SDK lib目录添加到模块路径.
  • 如果Maven出现问题,则为run Maven in debug mode(它将意外地输出太多信息,但您可能会发现一些有用的信息,例如插件用于调用jpackage的生成的jpackage命令,或者可能是wix软件生成的一些错误消息).
  • 知道插件用来调用jPackage的命令后,如果需要帮助调试,您可以从命令行手动运行和测试相同的命令.
  • 在pom.xml中更改akman jpackage插件的包配置以启用win控制台.
  • 使用Windows的添加删除程序功能可以删除可能已安装的应用程序的任何旧副本.
  • 确保打包程序运行,并在桌面上创建一个图标.
  • 在控制台中,切换到已安装应用程序的目录并从命令行运行<user dir>/AppData/Local/<your app name>/<your app name>.exe文件(这是已安装的应用程序,而不是安装程序应用程序).如果使用堆栈跟踪失败,您现在将在控制台中看到堆栈跟踪,说明出了什么问题.

参考文献

为非SpringBoot或非JavaFX应用程序创建安装程序

这里描述的大多数步骤都是打包任何Java程序的通用步骤,并不特定于SpringBoot或JavaFX.因此,您可以通过一些修改将这些步骤应用于其他应用程序类型.

尽管这里的目标是SpringBoot,但大多数信息都是使用Windows安装程序在Windows上部署Java应用程序的通用信息,而不是针对SpringBoot的特定信息.SpringBoot唯一的真正复杂之处在于,它在非模块化应用程序中工作得最好,但除此之外,它在创建安装程序方面没有什么特别之处.

类似地,对于JavaFX,只有在用作模块时才支持它,所以这就是这里所演示的.请注意,非模块化应用程序仍然可以使用JDK和JavaFX模块,因此拥有以非模块化方式使用SpringBoot、但使用定制的模块化Java运行时和JavaFX模块的非模块化应用程序是可以的.

创建跨平台版本

这里描述的步骤是针对Windows-only build.

Steps to create installers for other platforms can be followed and are largely similar to the steps outlined here for Windows. However, they must be run on a machine of the same type as the target platform you are building for, and will create a platform-specific installer for that machine type.

jpackage构建的安装程序不仅在操作系统上不同,而且在体系 struct 类型上也不同(例如,Mac Intel安装程序必须在Mac Intel计算机上创建,而Mac M系列处理器安装程序必须在Mac M系列计算机上创建).

其他平台的步骤将略有不同(例如,Linux版本将使用rpmdeb打包,而不是Wix打包,mac版本将创建Mac类型的包格式,如pkgdmg).对于不同平台的图标类型和格式等一些东西有不同的要求,以及可能需要签署应用程序以进行分发的要求,这可能需要付费的开发者证书.本文没有介绍这些主题,但Oracle的jpackage资源指南中提供了有关这些主题的信息.

示例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.example</groupId>
    <artifactId>wininstalled</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>wininstalled</name>
    <description>An installable SpringBoot JavaFX application for Windows</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <javafx.version>21.0.1</javafx.version>
        <jpackageInputDirectory>${project.build.directory}/jpackage-input</jpackageInputDirectory>
        <maven.build.timestamp.format>yy.MM.ddHH.mmss</maven.build.timestamp.format>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javafx.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javafx.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>3.2.1</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <outputDirectory>${jpackageInputDirectory}</outputDirectory>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.6.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                ${jpackageInputDirectory}/lib
                            </outputDirectory>
                            <excludeGroupIds>
                                org.openjfx
                            </excludeGroupIds>
                            <includeScope>
                                runtime
                            </includeScope>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                    <parameters>true</parameters>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.3.1</version>
                <executions>
                    <execution>
                        <id>auto-clean</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>clean</goal>
                        </goals>
                        <configuration>
                            <excludeDefaultDirectories>true</excludeDefaultDirectories>
                            <filesets>
                                <fileset>
                                    <directory>${project.build.directory}/jpackage</directory>
                                </fileset>
                            </filesets>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>com.github.akman</groupId>
                <artifactId>jpackage-maven-plugin</artifactId>
                <version>0.1.5</version>
                <executions>
                    <execution>
                        <phase>verify</phase>
                        <goals>
                            <goal>jpackage</goal>
                        </goals>
                        <configuration>
                            <name>wininstalled</name>
                            <appversion>${maven.build.timestamp}</appversion>
                            <copyright>Unrestricted freeware</copyright>
                            <description>JavaFX windows installed app demo.</description>
                            <vendor>Acme Widgets, Inc.</vendor>
                            <icon>${project.basedir}/src/main/resources/com/example/wininstalled/coffee.ico</icon>
                            <modulepath>
                                <dependencysets>
                                    <dependencyset>
                                        <includeoutput>false</includeoutput>
                                        <excludeautomatic>true</excludeautomatic>
                                        <includes>
                                            <!-- todo would it be better to fetch jmods and use them? -->
                                            <include>glob:**/javafx-*-win.jar</include>
                                        </includes>
                                    </dependencyset>
                                </dependencysets>
                            </modulepath>
                            <addmodules>
                                <!-- we add required modules here,
                                     we need to include base ones from the jdk which are not
                                     part of the minimum service set that jpackage uses by default,
                                     for example jdk.crypto.cryptoki is needed for ssl support and
                                     jdk.crypto.ec if you need to support elliptic curve ciphers in ssl
                                     and java.sql if you (or a library you use) uses jdbc, etc.
                                     you would want different ones for another app,
                                     libraries that are not treated as modular should need to be listed,
                                     transitively included modules don`t need to be listed -->
                                <addmodule>jdk.crypto.cryptoki</addmodule>
                                <addmodule>jdk.crypto.ec</addmodule>
                                <addmodule>java.sql</addmodule>
                                <addmodule>java.naming</addmodule>
                                <addmodule>java.net.http</addmodule>
                                <addmodule>java.instrument</addmodule>
                                <addmodule>javafx.controls</addmodule>
                                <addmodule>javafx.fxml</addmodule>
                                <!-- if you want these other javafx modules then
                                     uncomment them and ensure you
                                     also have maven dependencies for them -->
<!--                                <addmodule>javafx.media</addmodule>-->
<!--                                <addmodule>javafx.swing</addmodule>-->
<!--                                <addmodule>javafx.web</addmodule>-->
                            </addmodules>
                            <!-- our app is non-modular, so we wont have a module entry, we set the mainjar and mainclass instead -->
                            <!--                            <module>com.example.wininstalled/HelloApplication</module>-->
                            <input>${jpackageInputDirectory}</input>
                            <mainjar>wininstalled-1.0-SNAPSHOT.jar</mainjar>
                            <mainclass>com.example.wininstalled.HelloApplication</mainclass>
                            <!--                            <javaoptions>-Dfile.encoding=UTF-8</javaoptions>-->
                            <!--                            <installdir>Utilities/Win Installed FX App</installdir>-->
                            <!--                            <licensefile>${project.basedir}/config/jpackage/LICENSE</licensefile>-->
                            <!--                            <resourcedir>${project.basedir}/config/jpackage/resources</resourcedir>-->
                            <!--                            <windirchooser>false</windirchooser>-->
                            <winmenu>true</winmenu>
                            <!--                            <winmenugroup>Utilities/Win Installed FX App</winmenugroup>-->
                            <winperuserinstall>true</winperuserinstall>
                            <winshortcut>true</winshortcut>
                            <!--                            <winupgradeuuid>${project.build.uuid}</winupgradeuuid>-->

                            <!-- if something goes wrong (and it will..) enable the winconsole and run the app from the command line
                                 then if the app aborts with an exception you can see it
                                 To run from the command line execute
                                    <your user home>\AppData\Local\<your app>\<your app>.exe
                                 -->
                            <!--                            <winconsole>true</winconsole>-->
                            <type>EXE</type>
                            <verbose>true</verbose>
                            <!-- example for setting jvm options if needed -->
                            <javaoptions>--enable-preview</javaoptions>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.ow2.asm</groupId>
                        <artifactId>asm</artifactId>
                        <version>9.5</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.2.1</version>
            </plugin>
        </plugins>
    </build>
</project>

Java相关问答推荐

Javascript更新alert 可扩展内容样式与CSS—按钮更多/更少

Listview—在Android Java中正确链接项目时出错

neo4j java驱动程序是否会在错误发生时自动回滚事务?

Spark上下文在向Spark提交数据集时具有内容,但Spark在实际构建它时发现它为空

是否在允许数组元素为空时阻止 idea 为空性警告?

无法初始化JPA实体管理器工厂:无法确定为Java类型<;类>;推荐的JdbcType

使用Spring和ActiveMQ的侦听器方法引发属性名称不能重复为空警告

对于亚洲/香港,使用ResolverStyle.STRICT的LocalDate.parse返回意外结果

try 判断可选参数是否为空时出现空类型安全警告

我如何解释这个错误?必需类型:供应商R,提供:收集器对象,捕获?,java.util.List java.lang.Object>>

Domino Designer 14中的保存代理添加了重影库

为什么同步数据块无效?

如何使用路径过渡方法使 node 绕圆旋转?

无法将GSON导入到我的JavaFX Maven项目

IntelliJ IDEA依赖项工具窗口丢失

如何调整JButton的大小以适应图标?

基于Java中mm/dd/yy格式的最近日期对数组列表进行排序

在Java中使用StorageReference将数据从Firebase存储添加到数组列表

我无法在我的Spring Boot应用程序中导入CSV依赖项

为什么Spring要更改Java版本配置以及如何正确设置?