无需安装即可为不同平台创建独立的 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>
我已经使用 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>