如何从 gradle 生成 javafx jar 包括所有依赖项

Pat*_*ala 0 java javafx gradle

被这个问题困扰好几天了。我正在尝试使用 gradle build 命令导出 jar 文件,但它提供了一个小 jar 文件。当我运行 jar 文件时,它给出以下错误,表明 javafx 依赖项未包含在构建中:

Exception in thread "main" java.lang.NoClassDefFoundError: javafx/application/Application
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
    at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
    at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    at newcare.home.Main_1.main(Main_1.java:6)
Caused by: java.lang.ClassNotFoundException: javafx.application.Application
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 10 more
Run Code Online (Sandbox Code Playgroud)

我看过类似这样的帖子在 IntelliJ IDEA 中创建 Java Gradle 项目并构建 .jar 文件 - 如何?他们似乎帮助了别人,但没有帮助我。这是我的 build.gradle:

plugins {
 id 'java'
 id 'application'
 id 'org.openjfx.javafxplugin' version '0.0.8'
}
group 'newcare'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

sourceCompatibility = 1.8

dependencies {
    testImplementation group: 'junit', name: 'junit', version: '4.12'
    implementation group: 'org.jasypt', name: 'jasypt', version: '1.9.2'
    implementation 'com.google.code.gson:gson:2.8.7'
    implementation 'joda-time:joda-time:2.10.13'
    implementation 'org.ocpsoft.prettytime:prettytime:4.0.4.Final'


    // here starts JavaFX
    implementation 'org.openjfx:javafx:14'

    implementation 'org.openjfx:javafx-base:14'
    implementation 'org.openjfx:javafx-graphics:14'
    implementation 'org.openjfx:javafx-controls:14'
    implementation 'org.openjfx:javafx-fxml:14'
    implementation 'org.openjfx:javafx-swing:14'
    implementation 'org.openjfx:javafx-media:14'
    implementation 'org.openjfx:javafx-web:14'
}

javafx{
    modules = ['javafx.controls', 'javafx.fxml', 'javafx.media', 'javafx.graphics']
    version = '11.0.2'
}

mainClassName = 'newcare.home.Main_1'




jar {
    from {
        configurations.runtime.collect {
            it.isDirectory() ? it : zipTree(it)
        }
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    manifest {
        attributes "Main-Class": "$mainClassName"
    }

    exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
}




sourceSets {
    main {
        resources {
            srcDirs = ["src/main/java"]
            includes = ["**/*.fxml","**/*.css","**/*.png","**/*.jpg"]
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我也尝试过使用构建工件方法,但似乎没有任何效果。

Sla*_*law 7

前言:虽然可以创建包含 JavaFX 的 fat/uber JAR 文件,但这不是首选方法。这会导致从类路径加载 JavaFX,而这不是受支持的配置。但是,如果您确实想创建 fat/uber JAR 文件,请跳至“创建 Fat/Uber JAR”部分。


独立的应用程序

如今部署 JavaFX 应用程序的首选方法是创建自定义运行时映像并将其打包到特定于平台的安装程序/可执行文件中。

JLink 和 JPackage

jlink工具用于创建自定义运行时映像。这实际上只是“只包含您需要的模块的自定义 JRE”的一种奇特方式。结果是一个独立的应用程序,尽管在我看来不是一个非常用户友好的应用程序。

请注意,该jlink工具仅适用于明确命名的模块(即存在一个module-info.class文件)。

jpackage工具本质上采用自定义运行时映像并为其生成特定于平台的可执行文件(例如.exeWindows 上的文件)。然后应用程序被打包成特定于平台的安装文件(例如.msiWindows 上的文件)。该工具有一个用户指南

请注意,该jpackage工具支持创建非模块化应用程序。您甚至可以对其进行配置,以便所有模块最终都位于自定义运行时映像中,而您的非模块化应用程序和任何其他非模块化依赖项则放置在类路径上。

本机代码

JavaFX 框架需要特定于平台的本机代码才能工作。这意味着您需要确保自定义运行时映像中包含本机代码。您可以通过两种方式执行此操作:

  1. 使用 Maven Central 中的 JavaFX JAR 文件,而不是 JavaFX SDK。

    • 发布到 Maven Central 的 JAR 文件嵌入本机代码。但是,JavaFX 必须将本机代码提取到计算机上的某个位置才能使用它。
  2. (首选)使用 JavaFX JMOD 文件,可以从gluonhq.com下载该文件。

    • 您可以将jlink/jpackage指向 JMOD 文件而不是常规 JAR 文件。
    • 这导致本机代码以与 JRE 本身所需的所有本机代码相同的方式包含在内。现在不需要提取本机代码,这使得我认为这是更好的选择。

摇篮

我推荐使用Gradle 中的jlink/两个插件jpackage


创建一个“Fat/Uber JAR”

使用 Gradle 时,我推荐使用Gradle Shadow Plugin来创建所谓的 fat/uber JAR 文件。它为您完成大部分配置,例如排除签名文件。它从已经存在的jar任务中提取适当的默认值。它还添加了shadowJar构建 fat/uber JAR 文件的任务。

例子

以下是创建 fat/uber JAR 文件的示例应用程序。请注意,我使用 Gradle 的 Kotlin DSL 而不是 Groovy DSL,但您可以使用任何一个。

用过的:

  • 摇篮7.3
  • Java 17.0.1(不包括 JavaFX)

设置.gradle.kts

rootProject.name = "sample"
Run Code Online (Sandbox Code Playgroud)

构建.gradle.kts

plugins {
    application
    id("org.openjfx.javafxplugin") version "0.0.10"
    id("com.github.johnrengelman.shadow") version "7.1.0"
}

group = "sample"
version = "1.0"

java {
    modularity.inferModulePath.set(false)
}

javafx {
    version = "17.0.1"
    modules("javafx.controls")
}

application {
    mainClass.set("sample.Main")
}

repositories {
    mavenCentral()
}

tasks {
    shadowJar {
        exclude("module-info.class")
    }
}
Run Code Online (Sandbox Code Playgroud)

主程序.java

package sample;

import javafx.application.Application;

public class Main {

    public static void main(String[] args) {
        Application.launch(App.class, args);
    }
}
Run Code Online (Sandbox Code Playgroud)

应用程序.java

package sample;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class App extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        StackPane root = new StackPane(new Label("Hello, World!"));
        primaryStage.setScene(new Scene(root, 500, 300));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }
}
Run Code Online (Sandbox Code Playgroud)

运行gradle shadowJar会给你一个 fat/uber JAR 文件,位于build/libs/sample-1.0-all.jar.

一些注意事项:

  • 我将shadowJar插件配置为排除module-info.class文件,因为 fat/uber JAR 文件不能与模块路径很好地配合。

    • 一个 JAR 只能包含一个模块。
    • 运行应用程序会将-jarJAR 放在类路径上。
    • 好处:这避免了module-info.classJAR 根目录中存在多个文件,每个文件对应一个模块依赖项。
  • 该类sample.Main是必需的,因为 JavaFX 从技术上讲不支持从类路径加载。如果主类可分配给javafx.application.Application,并且javafx.graphics在引导层中找不到该模块,则应用程序将无法启动。单独的主类围绕这个问题进行了破解。

  • 在 JavaFX 16+ 上,将发出警告,因为 JavaFX 不支持从类路径加载。

  • 该 JAR 文件仅适用于您运行 Gradle 的平台,因为仅包含该平台的本机代码。

    • 如果您想要一个跨平台的 JAR,那么您需要手动添加每个平台的 JavaFX 依赖项,以便每个平台的本机代码嵌入到 JAR 文件中。