解决JavaJFX 打包可执行文件问题

一、问题

  • 提示:没有主清单属性
PS C:\Users\liurongming\Desktop\pj\demofx\target> java -jar .\demofx-1.0-SNAPSHOT.jar
.\demofx-1.0-SNAPSHOT.jar中没有主清单属性
PS C:\Users\liurongming\Desktop\pj\demofx\target> 

使用JD-GUI进行反编译排查一下:
工具地址:http://java-decompiler.github.io/
在META-INF/MANIFEST.MF的确没有加入启动类,那么肯定启动不了。


回到工程查看一下,发现:被<executions>排除掉了。

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>com.wewetea.open.demofx/com.wewetea.open.demofx.HelloApplication</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

因此,把排除的部分去掉,再试一下:

  <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <!--<executions>
                    <execution>
                        &lt;!&ndash; Default configuration for running with: mvn clean javafx:run &ndash;&gt;
                        <id>default-cli</id>-->
                        <configuration>
                            <mainClass>com.wewetea.open.demofx/com.wewetea.open.demofx.HelloApplication</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    <!--</execution>
                </executions>-->
            </plugin>

事实上,这样还是不行动的,经过漫长的排查,终于发现就是打包插件问题,

 <groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>

因此,决定换一个打包方式,直接用maven自己的打包方式,然后自己指定启动类:

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <!--maven-jar-plugin的作用是配置mainClass和指定classpath。-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.1</version>
                <configuration>
                    <archive>
                        <manifest>
                            <!--是否在manifest文件中添加classpath。默认为false。-->
                            <addClasspath>true</addClasspath>
                            <!--指定类路径前缀,也就是依赖的jar包所在的文件夹-->
                            <classpathPrefix>lib/</classpathPrefix>
                            <!--指定启动类-->
                            <mainClass>com.wewetea.open.demofx.HelloApplication</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
             <!--maven-dependency-plugin的作用是抽离依赖包,整体打包到 [目录]/lib -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.7.0</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>
                                ${project.build.directory}/lib
                            </outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
           <!-- <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        &lt;!&ndash; Default configuration for running with: mvn clean javafx:run &ndash;&gt;
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>com.wewetea.open.demofx/com.wewetea.open.demofx.HelloApplication</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>-->
        </plugins>
    </build>

主要编码问题:

   <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <junit.version>5.10.2</junit.version>
    </properties>

这样,问题就能解决了。
完整的配置如下:

<?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.wewetea.open</groupId>
    <artifactId>demofx</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>demofx</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <junit.version>5.10.2</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>17.0.15</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>17.0.15</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <executions>
                    <execution>
                        <!-- Default configuration for running with: mvn clean javafx:run -->
                        <id>default-cli</id>
                        <configuration>
                            <mainClass>com.wewetea.open.demofx/com.wewetea.open.demofx.HelloApplication</mainClass>
                            <launcher>app</launcher>
                            <jlinkZipName>app</jlinkZipName>
                            <jlinkImageName>app</jlinkImageName>
                            <noManPages>true</noManPages>
                            <stripDebug>true</stripDebug>
                            <noHeaderFiles>true</noHeaderFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

如何采用JLink 的方式,这里也都可以设置为false或者不设置

  <!--是否在manifest文件中添加classpath。默认为false。-->
  <addClasspath>true</addClasspath>

以上完整以后,执行java -jar xxx.jar会提示,错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序。

PS C:\Users\liurongming\Desktop\pj\demofx\target> java -jar .\demofx-1.0-SNAPSHOT.jar
错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序
PS C:\Users\liurongming\Desktop\pj\demofx\target>

这里其实已经可以了,这是因为另外一个问题,此时再反编译查看:

Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.1
Build-Jdk-Spec: 17
Class-Path: lib/javafx-controls-17.0.6.jar lib/javafx-controls-17.0.6-wi
 n.jar lib/javafx-graphics-17.0.6.jar lib/javafx-graphics-17.0.6-win.jar
  lib/javafx-base-17.0.6.jar lib/javafx-base-17.0.6-win.jar lib/javafx-f
 xml-17.0.6.jar lib/javafx-fxml-17.0.6-win.jar
Main-Class: com.wewetea.open.demofx.HelloApplication

确实已经,添加进来了。但是还出现问题,这是因为:“从JDK 11开始,JavaFX被拆分为一个独立的库”,因此需要链接一下启动就可以了。

java -jar --module-path C:\Users\liurongming\javafx-sdk-17.0.15\lib --add-modules javafx.controls,javafx.fxml  demofx-1.0-SNAPSHOT.jar


事实上,我们不需要采用绝对路径来启动了,因为我们已经把依赖都抽离了过来。因此,只要指定当前包--module-path ./lib就可以了,运行速度会更快。

java -jar --module-path ./lib --add-modules javafx.controls,javafx.fxml  demofx-1.0-SNAPSHOT.jar

至此,说明我们的环境和程序没有问题了,那么接下来就是把它制作成为安装包。类似windows的.msi和exe等。

二、制作安装包

  • 方法一、【绿色运行版】采用jlink + launch4j的方式。
    jlink 是JDK自带的,不需要下载,
    下载launch4j地址:https://nchc.dl.sourceforge.net/project/launch4j/launch4j-3/3.50/launch4j-3.50-win32.exe?viasf=1
    ①先用jlink制作一个可以直接启动的环境:
jlink --module-path ./lib --add-modules javafx.controls,javafx.fxml --output runtime-images

这是个非常厉害的工具,它可以把程序要依赖的包和JDK,都抽离成只要当前程序需要的包,类似jre运行包,但体积会更小,因为只针对当前程序。
这样做的目的,是让程序可以直接java -jar的方式去运行:

.\runtime-images\bin\java -jar .\demofx-1.0-SNAPSHOT.jar

当然,必须是使用runtime-images镜像环境启动java -jar才有效。
②用launch4j制作exe运行包,直接启动不用安装。
注意JREpath 直接填写:runtime-images/,否则是采用系统的JRE环境,这样制作出来的程序无法运行。

指定自己的JRE:



然后:



测试之后,就能出现如下界面:

把这个两个包,放在一个文件夹,就可以双击运行了。



做成zip文件,就相当于是一个绿色运行程序了。
  • 方法二、【安装版本】采用jlink + jpackage的方式。
    ①用jlink做镜像,此方法的第一步和前面是一样的,先做好自己的运行依赖镜像。
    此处,省略
    ②用jpackage制作安装包。
    查看一下jpackage -h帮助手册,里面其实都有示例:
Sample usages:
--------------
    Generate an application package suitable for the host system:
        For a modular application:
            jpackage -n name -p modulePath -m moduleName/className
        For a non-modular application:
            jpackage -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        From a pre-built application image:
            jpackage -n name --app-image appImageDir
    Generate an application image:
        For a modular application:
            jpackage --type app-image -n name -p modulePath \
                -m moduleName/className
        For a non-modular application:
            jpackage --type app-image -i inputDir -n name \
                --main-class className --main-jar myJar.jar
        To provide your own options to jlink, run jlink separately:
            jlink --output appRuntimeImage -p modulePath \
                --add-modules moduleName \
                --no-header-files [<additional jlink options>...]
            jpackage --type app-image -n name \
                -m moduleName/className --runtime-image appRuntimeImage
    Generate a Java runtime package:
        jpackage -n name --runtime-image <runtime-image>
Generic Options:
  @<filename>
          Read options and/or mode from a file
          This option can be used multiple times.
  --type -t <type>
          The type of package to create
          Valid values are: {"app-image", "exe", "msi"}
          If this option is not specified a platform dependent
          default type will be created.
  --app-version <version>
          Version of the application and/or package
  --copyright <copyright string>
          Copyright for the application
  --description <description string>
          Description of the application
  --help -h
          Print the usage text with a list and description of each valid
          option for the current platform to the output stream, and exit
  --icon <file path>
          Path of the icon of the application package
          (absolute path or relative to the current directory)
  --name -n <name>
          Name of the application and/or package
  --dest -d <destination path>
          Path where generated output file is placed
          (absolute path or relative to the current directory)
          Defaults to the current working directory.
  --temp <directory path>
          Path of a new or empty directory used to create temporary files
          (absolute path or relative to the current directory)
          If specified, the temp dir will not be removed upon the task
          completion and must be removed manually.
          If not specified, a temporary directory will be created and
          removed upon the task completion.
  --vendor <vendor string>
          Vendor of the application
  --verbose
          Enables verbose output
  --version
          Print the product version to the output stream and exit.

Options for creating the runtime image:
  --add-modules <module name>[,<module name>...]
          A comma (",") separated list of modules to add
          This module list, along with the main module (if specified)
          will be passed to jlink as the --add-module argument.
          If not specified, either just the main module (if --module is
          specified), or the default set of modules (if --main-jar is
          specified) are used.
          This option can be used multiple times.
  --module-path -p <module path>...
          A ; separated list of paths
          Each path is either a directory of modules or the path to a
          modular jar.
          (Each path is absolute or relative to the current directory.)
          This option can be used multiple times.
  --jlink-options <jlink options>
          A space separated list of options to pass to jlink
          If not specified, defaults to "--strip-native-commands
          --strip-debug --no-man-pages --no-header-files".
          This option can be used multiple times.
  --runtime-image <directory path>
          Path of the predefined runtime image that will be copied into
          the application image
          (absolute path or relative to the current directory)
          If --runtime-image is not specified, jpackage will run jlink to
          create the runtime image using options:
          --strip-debug, --no-header-files, --no-man-pages, and
          --strip-native-commands.

Options for creating the application image:
  --input -i <directory path>
          Path of the input directory that contains the files to be packaged
          (absolute path or relative to the current directory)
          All files in the input directory will be packaged into the
          application image.

Options for creating the application launcher(s):
  --add-launcher <launcher name>=<file path>
          Name of launcher, and a path to a Properties file that contains
          a list of key, value pairs
          (absolute path or relative to the current directory)
          The keys "module", "main-jar", "main-class",
          "arguments", "java-options", "app-version", "icon", and
          "win-console" can be used.
          These options are added to, or used to overwrite, the original
          command line options to build an additional alternative launcher.
          The main application launcher will be built from the command line
          options. Additional alternative launchers can be built using
          this option, and this option can be used multiple times to
          build multiple additional launchers.
  --arguments <main class arguments>
          Command line arguments to pass to the main class if no command
          line arguments are given to the launcher
          This option can be used multiple times.
  --java-options <java options>
          Options to pass to the Java runtime
          This option can be used multiple times.
  --main-class <class name>
          Qualified name of the application main class to execute
          This option can only be used if --main-jar is specified.
  --main-jar <main jar file>
          The main JAR of the application; containing the main class
          (specified as a path relative to the input path)
          Either --module or --main-jar option can be specified but not
          both.
  --module -m <module name>[/<main class>]
          The main module (and optionally main class) of the application
          This module must be located on the module path.
          When this option is specified, the main module will be linked
          in the Java runtime image.  Either --module or --main-jar
          option can be specified but not both.

用来创建应用程序启动程序的与平台相关的选项:
  --win-console
          为应用程序创建控制台启动程序,应当为
          需要控制台交互的应用程序指定

Options for creating the application package:
  --about-url <url>
          URL of the application's home page
  --app-image <directory path>
          Location of the predefined application image that is used
          to build an installable package
          (absolute path or relative to the current directory)
  --file-associations <file path>
          Path to a Properties file that contains list of key, value pairs
          (absolute path or relative to the current directory)
          The keys "extension", "mime-type", "icon", and "description"
          can be used to describe the association.
          This option can be used multiple times.
  --install-dir <directory path>
          默认安装位置下面的相对子路径
  --license-file <file path>
          Path to the license file
          (absolute path or relative to the current directory)
  --resource-dir <directory path>
          Path to override jpackage resources
          Icons, template files, and other resources of jpackage can be
          over-ridden by adding replacement resources to this directory.
          (absolute path or relative to the current directory)
  --runtime-image <directory path>
          Path of the predefined runtime image to install
          (absolute path or relative to the current directory)
          Option is required when creating a runtime package.

Platform dependent options for creating the application package:
  --win-dir-chooser
          Adds a dialog to enable the user to choose a directory in which
          the product is installed.
  --win-help-url <url>
          URL where user can obtain further information or technical support
  --win-menu
          Request to add a Start menu shortcut for this application
  --win-menu-group <menu group name>
          Start Menu group this application is placed in
  --win-per-user-install
          Request to perform an install on a per-user basis
  --win-shortcut
          Request to add desktop shortcut for this application
  --win-shortcut-prompt
          Adds a dialog to enable the user to choose if shortcuts
          will be created by installer.
  --win-update-url <url>
          URL of available application update information
  --win-upgrade-uuid <id string>
          UUID associated with upgrades for this package

PS C:\Users\liurongming\Desktop\pj\demofx\target>

因为之前,已经用Jlink制作好了镜像环境包,使得运行我们的jar程序,只要直接java -jar就可以了启动,因此再使用jpackage打包就非常简单了。这里只要关心几个关键项目就额可以了。
参考这两条命令:

  jpackage --type app-image -n name \
                -m moduleName/className --runtime-image appRuntimeImage
    Generate a Java runtime package:
        jpackage --n name --runtime-image <runtime-image>

简化一下,就直接

jpackage --type msi --name xxxname --runtime-image <xxxruntime-image>

这里的 --type可以选择以下类型。

--type -t <type>
          The type of package to create
          Valid values are: {"app-image", "exe", "msi"}
          If this option is not specified a platform dependent
          default type will be created.

更简单的,不指定的话,默认是和自己平台相关的。

jpackage --n name --runtime-image <runtime-image>

PS:下载一下WiX 3.0工具

PS C:\Users\liurongming\Desktop\pj\demofx\target> jpackage --type msi --name mydemo --runtime-image runtime-images
[01:06:46.451] 找不到 WiX 工具 (light.exe, candle.exe)
[01:06:46.451] 从 https://wixtoolset.org 下载 WiX 3.0 或更高版本,然后将其添加到 PATH。
错误:类型 [msi] 无效或不受支持
PS C:\Users\liurongming\Desktop\pj\demofx\target> 

选择这里下载即可:

https://sourceforge.net/projects/wix/files/latest/download

这里也不做演示了。

  • ③方式三、使用NSIS做个windows开发的就知道这个工具
https://nsis.sourceforge.io/Download

也比较简单,这里就不介绍了。

posted @ 2025-05-20 01:24  刘文江  阅读(173)  评论(0)    收藏  举报  来源