无需安装即可为不同平台创建独立的 Java 可执行文件

Create standalone Java executable for different platforms without installation

我已经使用 jlink 创建了一个 Java 应用程序运行时映像。我希望能够将软件作为可执行文件发送到不同的平台。 (最好是在一个平台上构建,比如交叉编译。)

理想情况下,它应该是用户可以双击启动的单个应用程序文件,而无需安装任何东西。

如何实现?

您所描述的是所谓的本机可执行文件。有些程序会将您的 Java 应用程序包装到一个可执行文件中,但是因为 Java 在 Java 虚拟机 (JVM) 上运行它的代码,您的用户需要预先安装它让您的程序开箱即用。您可以使用 C++ 或 C#(C# 在预装在所有 Windows 机器上的 .NET 运行时上运行)为您的应用程序编写安装程序,安装 JVM 并可能安装您的应用程序,然后编译该代码到本机可执行文件。这样,最终用户就不需要四处寻找 Java 下载。我相信这就是 Minecraft 采用的方法。

以下是我之前谈到的可以将 Java 可执行文件包装成本机可执行文件的程序: Launch4J(Windows):http://launch4j.sourceforge.net/ Oracle 文档 (MacOS):https://docs.oracle.com/javase/7/docs/technotes/guides/jweb/packagingAppsForMac.html 话语(Linux):https://discourse.appimage.org/t/tutorial-to-create-appimage-out-of-java-application/461

还有一个有趣的 link 我发现详细说明了从 Java 代码构建本机可执行文件而无需安装程序: https://quarkus.io/guides/building-native-image#producing-a-native-executable

双击 运行 多个平台上的可执行文件,需要事先在操作系统中注册文件类型,或现有文件类型才能知道如何处理代码。

jlink 将 "required modules and their transitive dependencies" 静态链接到输出。

这个问题没有跨平台解决方案。

不可能(或者换句话说,不可行)将所有平台都包含在一个文件中,因为每种可执行文件类型(COFF、ELF...)都有不同的结构。您可以尝试使用通用批处理文件来启动正确的可执行文件,但在 Windows 上,这将需要文本文件类型编码;从而使剩余的二进制代码中毒。

使用 jlink 新的 jmod 文件格式将允许您将本机代码存储在 Java 容器中,从而允许进入嵌入式本机 JRE 代码的入口点在单个可执行映像中 用于单个预定义平台。

此问题的另一方面是安全隐患。由于嵌入式 JRE 不受安全更新的约束,黑客可能会选择嵌入以前已知的有缺陷的 JRE,从而将更正的漏洞暴露给不知情的消费者。

防病毒程序的预期响应是将所有未更新的嵌入式 JRE 标记为病毒。

另外,看看SubstrateVM。这不是真的 Java,但是,它可能在某些情况下对您有所帮助,例如简单的命令行应用程序。

Substrate VM is a framework that allows ahead-of-time (AOT) compilation of Java applications under closed-world assumption into executable images or shared objects (ELF-64 or 64-bit Mach-O).

是的,从 Java 8 开始,有两种方法可以做到这一点,使用 javapackager 工具,或 JavaFX Ant Tasks(实际上并不特定于 JavaFX,并提供了 Java 8 JDK).

下面是一个将应用程序打包为 Jar 的示例,设置 main-class 和 classpath 属性,并将所有依赖项复制到一个文件夹中,使用 Maven。

<plugins>
        <plugin>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <mainClass>${project.groupId}.${project.artifactId}.DemoCLI</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
</plugins>

这是用于打包独立应用程序的 Ant 构建文件(Windows 上的 exe、OS X 上的 .app 和 .dmg、Linux 上的 .deb 和 .rpm ).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>

<!--
 Uses the JavaFX Ant Tasks to build native application bundles
 (specific to the platform which it is built on).

 See https://docs.oracle.com/javase/8/docs/technotes/guides/deploy/javafx_ant_tasks.html

 These tasks are distributed with version 8 of the Oracle JDK,
 Amazon Corretto JDK, RedHat JDK, probably others, though they
 do not seem to be the OpenJDK on Debian Linux

 -->
<project
    name="fxwebclient" default="default" basedir="."
    xmlns:fx="javafx:com.sun.javafx.tools.ant">

    <!-- In Java 8, java.home is typically the JRE of a JDK -->
    <property name="jdk.lib.dir" value="${java.home}/../lib" />

    <!-- Where to build our app bundles -->
    <property name="build.dist.dir" value="${basedir}/target/dist" />

   <echo>Using Java from ${java.home}</echo>

    <target name="default" depends="clean">

        <!-- get the ant-jfx.jar from the JDK -->
        <taskdef resource="com/sun/javafx/tools/ant/antlib.xml"
            uri="javafx:com.sun.javafx.tools.ant"
            classpath="${jdk.lib.dir}/ant-javafx.jar" />

        <!-- Define our application entry point -->
        <fx:application id="demo" name="demo"
            mainClass="yourpackage.DemoCLI" />

        <!-- Our jar and copied dependency jars (see pom.xml) -->
       <fx:resources id="appRes">
            <fx:fileset dir="${basedir}/target" includes="*.jar"/>
            <fx:fileset dir="${basedir}/target/lib" includes="*.jar" />
       </fx:resources>

        <!-- Create app bundles [platform specific] -->
        <fx:deploy nativeBundles="all"
            outdir="${build.dist.dir}" outfile="DemoWebClient">

            <fx:application refid="demo" />
            <fx:resources refid="appRes" />

        </fx:deploy>

    </target>

    <!-- clean up -->
    <target name="clean">
        <delete dir="${build.dist.dir}" includeEmptyDirs="true" />
        <delete file="${basedir}/target/${ant.project.name}.jar" />
    </target>

</project>