如何从 gradle 生成包含所有依赖项的 javafx jar
How to generate javafx jar from gradle including all dependencies
几天来一直被这个问题困扰。我正在尝试使用 gradle 构建命令导出一个 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"]
}
}
}
我也尝试过使用构建工件方法,但似乎没有任何效果。
前言: 虽然可以创建包含 JavaFX 的 fat/uber JAR 文件,但这不是首选方法.这导致 JavaFX 从 class 路径加载,这不是受支持的配置。但是,如果您真的想创建 fat/uber JAR 文件,请跳至“创建 Fat/Uber JAR”部分。
自包含应用程序
目前部署 JavaFX 应用程序的首选方法是创建自定义 运行 时间映像并将其打包到特定平台 installer/executable.
JLink 和 JPackage
jlink
工具用于创建自定义 运行 时间图像。这实际上只是一种奇特的说法,“一个只包含您需要的模块的自定义 JRE”。结果是一个独立的应用程序,虽然在我看来不是一个非常用户友好的应用程序。
请注意 jlink
工具仅适用于明确命名的模块(即存在 module-info.class
文件)。
jpackage
tool essentially takes a custom run-time image and generates a platform-specific executable for it (e.g. a .exe
file on Windows). Then the application is packaged into a platform-specific installation file (e.g. a .msi
file on Windows). This tool has a user guide.
请注意 jpackage
工具支持创建非模块化应用程序。您甚至可以配置它,以便所有模块最终都在自定义 运行-time 图像中,而您的非模块化应用程序和任何其他非模块化依赖项都放在 class-path.
本机代码
JavaFX 框架需要特定于平台的本机代码才能工作。这意味着您需要确保本机代码包含在自定义 运行 时间图像中。有两种方法可以做到这一点:
使用来自 Maven Central 的 JavaFX JAR 文件,而不是 JavaFX SDK。
- 发布到 Maven Central 的 JAR 文件嵌入了本机代码。但是,JavaFX 必须将本机代码提取到您计算机上的某个位置才能使用它。
(首选)使用JavaFX JMOD文件,可从gluonhq.com.
下载
- 您可以将
jlink
/ jpackage
指向 JMOD 文件 而不是 常规 JAR 文件。
- 这导致本机代码以与 JRE 本身所需的所有本机代码相同的方式被包含。现在不需要提取本机代码,这在我看来是更好的选择。
Gradle
我推荐使用 jlink
/ jpackage
来自 Gradle 的两个插件。
- The Badass JLink Plugin(对于模块化应用程序)
- The Badass Runtime Plugin(对于非模块化应用程序)
正在创建“Fat/Uber JAR”
当使用 Gradle 时,我推荐使用 Gradle Shadow Plugin 来创建所谓的 fat/uber JAR 文件。它为您做了很多配置,例如排除签名文件。它从已经存在的 jar
任务中提取适当的默认值。它还添加了用于构建 fat/uber JAR 文件的 shadowJar
任务。
例子
这是一个创建 fat/uber JAR 文件的示例应用程序。请注意,我将 Kotlin DSL 用于 Gradle 而不是 Groovy DSL,但您可以使用任何一个。
已用:
- Gradle 7.3
- Java 17.0.1(Java不含外汇)
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 放在 class-路径上。
- 好处:这避免了在 JAR 的根目录中有多个
module-info.class
文件,每个模块依赖一个文件。
sample.Main
class 是必要的,因为 JavaFX 在技术上不支持从 class 路径加载。如果main class assignable to javafx.application.Application
,而在boot layer中没有找到javafx.graphics
模块,那么应用程序将无法启动。单独的主要 class 破解。
在 JavaFX 16+ 上会发出警告,因为 JavaFX 不支持从 class 路径加载。
此 JAR 文件仅适用于您 运行 Gradle 所在的平台,因为仅包含该平台的本机代码。
- 如果您想要一个跨平台的 JAR,那么您需要为每个平台手动添加 JavaFX 依赖项,以便每个平台的本机代码嵌入到 JAR 文件中。
几天来一直被这个问题困扰。我正在尝试使用 gradle 构建命令导出一个 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"]
}
}
}
我也尝试过使用构建工件方法,但似乎没有任何效果。
前言: 虽然可以创建包含 JavaFX 的 fat/uber JAR 文件,但这不是首选方法.这导致 JavaFX 从 class 路径加载,这不是受支持的配置。但是,如果您真的想创建 fat/uber JAR 文件,请跳至“创建 Fat/Uber JAR”部分。
自包含应用程序
目前部署 JavaFX 应用程序的首选方法是创建自定义 运行 时间映像并将其打包到特定平台 installer/executable.
JLink 和 JPackage
jlink
工具用于创建自定义 运行 时间图像。这实际上只是一种奇特的说法,“一个只包含您需要的模块的自定义 JRE”。结果是一个独立的应用程序,虽然在我看来不是一个非常用户友好的应用程序。
请注意 jlink
工具仅适用于明确命名的模块(即存在 module-info.class
文件)。
jpackage
tool essentially takes a custom run-time image and generates a platform-specific executable for it (e.g. a .exe
file on Windows). Then the application is packaged into a platform-specific installation file (e.g. a .msi
file on Windows). This tool has a user guide.
请注意 jpackage
工具支持创建非模块化应用程序。您甚至可以配置它,以便所有模块最终都在自定义 运行-time 图像中,而您的非模块化应用程序和任何其他非模块化依赖项都放在 class-path.
本机代码
JavaFX 框架需要特定于平台的本机代码才能工作。这意味着您需要确保本机代码包含在自定义 运行 时间图像中。有两种方法可以做到这一点:
使用来自 Maven Central 的 JavaFX JAR 文件,而不是 JavaFX SDK。
- 发布到 Maven Central 的 JAR 文件嵌入了本机代码。但是,JavaFX 必须将本机代码提取到您计算机上的某个位置才能使用它。
(首选)使用JavaFX JMOD文件,可从gluonhq.com.
下载- 您可以将
jlink
/jpackage
指向 JMOD 文件 而不是 常规 JAR 文件。 - 这导致本机代码以与 JRE 本身所需的所有本机代码相同的方式被包含。现在不需要提取本机代码,这在我看来是更好的选择。
- 您可以将
Gradle
我推荐使用 jlink
/ jpackage
来自 Gradle 的两个插件。
- The Badass JLink Plugin(对于模块化应用程序)
- The Badass Runtime Plugin(对于非模块化应用程序)
正在创建“Fat/Uber JAR”
当使用 Gradle 时,我推荐使用 Gradle Shadow Plugin 来创建所谓的 fat/uber JAR 文件。它为您做了很多配置,例如排除签名文件。它从已经存在的 jar
任务中提取适当的默认值。它还添加了用于构建 fat/uber JAR 文件的 shadowJar
任务。
例子
这是一个创建 fat/uber JAR 文件的示例应用程序。请注意,我将 Kotlin DSL 用于 Gradle 而不是 Groovy DSL,但您可以使用任何一个。
已用:
- Gradle 7.3
- Java 17.0.1(Java不含外汇)
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
.
一些注意事项:
我将
shadowJar
插件配置为排除module-info.class
文件,因为 fat/uber JAR 文件不能很好地与模块路径一起工作。- 一个 JAR 只能包含一个模块。
- 运行 带有
-jar
的应用程序将 JAR 放在 class-路径上。 - 好处:这避免了在 JAR 的根目录中有多个
module-info.class
文件,每个模块依赖一个文件。
sample.Main
class 是必要的,因为 JavaFX 在技术上不支持从 class 路径加载。如果main class assignable tojavafx.application.Application
,而在boot layer中没有找到javafx.graphics
模块,那么应用程序将无法启动。单独的主要 class 破解。在 JavaFX 16+ 上会发出警告,因为 JavaFX 不支持从 class 路径加载。
此 JAR 文件仅适用于您 运行 Gradle 所在的平台,因为仅包含该平台的本机代码。
- 如果您想要一个跨平台的 JAR,那么您需要为每个平台手动添加 JavaFX 依赖项,以便每个平台的本机代码嵌入到 JAR 文件中。