【问题标题】:How to generate javafx jar from gradle including all dependencies如何从 gradle 生成 javafx jar,包括所有依赖项
【发布时间】:2022-01-07 12:36:03
【问题描述】:

被这个问题困扰了好几天。我正在尝试使用 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

我看过像Creating a Java Gradle project and building the .jar file in IntelliJ IDEA - How to? 这样的帖子,他们似乎帮助了别人,但对我没有帮助。这是我的 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"]
        }
    }
}

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

【问题讨论】:

  • 我不知道 gradle,但是这个问题的 maven 等价物是:Maven Shade JavaFX runtime components are missing
  • 使用 Gradle Shadow 插件(我想这就是它的名字)。此外,如果您使用的是 JavaFX 插件,那么您不应该手动将 JavaFX 添加到 dependencies(您可以使用 javafx 扩展来声明您想要的模块和版本;有关详细信息,请参阅文档)。当版本 17.0.1 是最新版本时,为什么要使用 JavaFX 14?请注意,JavaFX 插件的最新版本是 0.0.10。
  • 谢谢。我会接受建议并回复你们
  • 这是一个不同的解决方案,但是,当您使用 gradle 时,我建议您调查 badass runtime pluginbadass jlink plugin,目标是创建适当的运行时映像,而不是像一个不受支持的配置带有依赖项的 jar。

标签: java gradle javafx


【解决方案1】:

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


独立的应用程序

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

JLink & JPackage

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

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

jpackage 工具本质上采用自定义运行时映像并为其生成特定于平台的可执行文件(例如 Windows 上的 .exe 文件)。然后将应用程序打包到特定于平台的安装文件中(例如,Windows 上的.msi 文件)。这个工具有一个user guide

注意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 文件的示例应用程序。注意我使用 Kotlin DSL for Gradle 而不是 Groovy DSL,但你可以使用任何一个。

使用:

  • Gradle 7.3
  • Java 17.0.1(不包括 JavaFX)

settings.gradle.kts

rootProject.name = "sample"

build.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")
    }
}

Main.java

package sample;

import javafx.application.Application;

public class Main {

    public static void main(String[] args) {
        Application.launch(App.class, args);
    }
}

App.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();
    }
}

运行gradle shadowJar 将在build/libs/sample-1.0-all.jar 处为您提供一个fat/uber JAR 文件。

一些注意事项:

  • 我将shadowJar 插件配置为排除module-info.class 文件,因为fat/uber JAR 文件不适用于模块路径。

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

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

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

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

【讨论】:

  • 使用 shadow 插件创建一个 fat/uber jar 似乎可行,但它不会运行。显然它无法识别我的 fxml 文件。我收到错误:NullPointerException:需要位置。在我尝试使用代码添加 fxml 文件的行上: Parent root = FXMLLoader.load(getClass().getResource("../fxml/Login.fxml"));
  • 资源 API 不支持使用 ..。对于Class#getResource(String),参数必须是绝对路径(植根于类路径/模块路径的根)或类位置的相对路径。尽管在打包代码之前使用.. 可能会起作用,但这只是实现的一个意外。请参阅How do I determine the correct path for FXML files, CSS files, Images, and other resources needed by my JavaFX Application? 了解更多详情。
  • 非常感谢
猜你喜欢
  • 2015-05-27
  • 2015-05-13
  • 1970-01-01
  • 1970-01-01
  • 2014-10-13
  • 2016-01-28
  • 1970-01-01
  • 2015-09-14
  • 1970-01-01
相关资源
最近更新 更多