【问题标题】:Java 11 with JavaFX building JAR with gradle带有 JavaFX 的 Java 11 使用 gradle 构建 JAR
【发布时间】:2020-01-21 08:25:51
【问题描述】:

我们正在使用 gradle 使用 Adopt JDK 11 和 IntelliJ 开发 JavaFX 11 应用程序。最后,我们需要一个适用于 windows 的 EXE 文件(该应用程序仅适用于 windows)。在第一次尝试中,我们还在 gradle 中使用了 launch4J,由于以下问题,我们也可以使用 BAT 文件,我们可以将其迁移到 exe 文件。

所以我们的主要目标和问题是,我们如何创建可执行的 jar 文件。

我们采取了几种不同的方法,但我们完全迷失了。

FAT JAR

我们为测试目的制作了一个独立的应用程序:

package de.test;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        VBox vBox = new VBox();

        Button button = new Button("Klick");

        TextField textfield = new TextField();
        TextArea area = new TextArea();
        area.setMinHeight(300);

        button.setOnAction(event -> area.setText(area.getText() + " --  Klick Version 1.0.8"));
        vBox.getChildren().addAll(button, textfield,area);

        primaryStage.setScene(new Scene(vBox));
        primaryStage.setTitle("Test");
        primaryStage.show();
    }
}

FAT JAR

我们的第一个尝试是使用 gradle 的 javafx 插件创建一个带有或不带有 modules-info.java 文件的 FAT-JAR。这是我们的 gradle 文件

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
}

group 'de.test'
version '1.0.8'

sourceCompatibility = 11

javafx {
    modules = ['javafx.controls']
}

mainClassName = 'de.test.Main'

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

repositories {
    mavenLocal()
    mavenCentral()
}

运行 gradle clean jar 后,我们尝试使用以下命令执行 jar java -jar ApplicationTest-1.0.8.jar 结果:

错误:无法找到或加载主类 de.test.Main 原因: java.lang.NoClassDefFoundError: javafx/application/Application


带有依赖项和 module-info.java 的 FAT JAR

然后我们尝试在 gradle 文件中像这样添加 JavaFX 的依赖项

compile group: 'org.jboss.resteasy', name: 'resteasy-client', version: '4.3.0.Final'
compile "org.openjfx:javafx-graphics:11.0.2:win"
compile "org.openjfx:javafx-base:11.0.2:win"
compile "org.openjfx:javafx-controls:11.0.2:win"
compile "org.openjfx:javafx-fxml:11.0.2:win"
compile "org.openjfx:javafx-graphics:11.0.2:win"

这是我们添加到包 de.test 中的 module-info.java 描述符:

module ApplicationTest.main {

    requires javafx.controls;

    exports de.test;

}

当调用 gradle installDist 我们使用了这个命令

cd build\install\ApplicationTest\lib java --add-modules “javafx.controls”——模块路径。 -jar ApplicationTest-1.0.8.jar

至少应用程序此时已启动,但布局已被以下信息消息破坏:

九月。 2019 年 2 月 20 日 8:32:55 VORM。 com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged 信息:无法加载样式表: com/sun/javafx/scene/control/skin/modena/modena.css 2019 年 9 月 20 日 8:32:55 VORM。 com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged 信息:无法加载样式表: com/sun/javafx/scene/control/skin/modena/modena.css

顺便说一句:使用命令 gradle clean run

时 IntelliJ 一直在工作


JLink 与 module-info.java

下一个方法是使用 jlink 和以下 modules-info.java

module ApplicationTest.main {
    requires javafx.controls;
    requires javafx.graphics;

    exports de.test;

}

我们需要像下面这样扩展 gradle 文件

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.8'
    id 'org.beryx.jlink' version '2.10.4'
}

group 'de.test'
version '1.0.8'

sourceCompatibility = 11

mainClassName = 'de.test.Main'

javafx {
    version = 11
    modules = [ 'javafx.controls']
}

jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
}


repositories {
    mavenLocal()
    mavenCentral()
}

当使用 gradle jlink 并执行 cd build\image\bin\ApplicationTest.bat 文件时,应用程序会启动 WITH 布局。

此时我们哪里有种幸福。现在我们需要为我们的应用程序添加一些依赖项。

使用以下依赖项效果很好

dependencies {
    compile "commons-io:commons-io:2.6"
    compile "org.apache.logging.log4j:log4j-api:2.11.2"
    compile "dom4j:dom4j:1.6.1"
    compile "commons-lang:commons-lang:2.6"
    compile "axis:axis:1.4"
    compile "jaxen:jaxen:1.1.6"
    compile "net.java.dev.jna:platform:3.5.2"
    compile "org.apache.poi:poi:4.1.0"
    compile "org.apache.poi:poi-scratchpad:4.1.0"
    compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"
}

但是,一旦我添加了其他依赖项,我就会收到不同类型的错误消息。我单独尝试了每个依赖项以隔离错误消息。

org.jboss.resteasy - resteasy-client - 3.7.0.Final

Cannot derive uses clause from service loader invocation in: javax/ws/rs/client/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/ext/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/ws/rs/sse/FactoryFinder.find().
Cannot derive uses clause from service loader invocation in: javax/xml/bind/ServiceLoaderUtil.firstByServiceLoader().

ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:154: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
    provides javax.ws.rs.ext.Providers with org.jboss.resteasy.plugins.interceptors.CacheControlFeature,
                                                                                   ^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:155: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
                org.jboss.resteasy.plugins.interceptors.encoding.ClientContentEncodingAnnotationFeature,
                                                                ^
ApplicationTest\build\jlinkbase\tmpjars\de.test.merged.module\module-info.java:156: error: the service implementation type must be a subtype of the service interface type, or have a public static no-args method named "provider" returning the service implementation
                org.jboss.resteasy.plugins.interceptors.encoding.MessageSanitizerContainerResponseFilter,
                                                                ^
...     
                                            ^
25 errors

依赖:org.apache.logging.log4j:log4j-core:2.11.2

package org.apache.logging.log4j.spi is not visible
(package org.apache.logging.log4j.spi is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)
package org.apache.logging.log4j.message is not visible
(package org.apache.logging.log4j.message is declared in module org.apache.logging.log4j, but module de.test.merged.module does not read it)

依赖:org.jboss.resteasy:resteasy-multipart-provider:3.7.0.Final

module not found: java.activation

依赖:org.jboss.resteasy:resteasy-jackson2-provider:3.7.0.Final

package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist
package javax.ws.rs.ext does not exist

结论

我们现在没有任何进展。它可能与 modules-info.java 有关,但有关此主题的说明和文档非常复杂且通常非常详细。我们只是想要一个可以在 IntelliJ 之外启动的应用程序。

我们对所有解决方案持开放态度,只要它们有效。我们也不需要最先进的解决方案,但我们最终可以通过某种方式获得 EXE(也可以借助外部工具)。我们的应用程序不必是模块化的,但我们既没有得到也没有解决它。

编辑 1) 第二个主类的 FAT Jar

就像在this aricle 中一样,我们添加了第二个主java 类而不扩展Application 并将其放入gradle 文件中

package de.test;

public class Main {
    public static void main(String[] args) {
        MainFX.main(args);
    }

}

另一个类看起来像这样:

package de.test;

import ...;

public class MainFX extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
    ...
    }
}

gradle 文件如下所示:

plugins {
    id 'java'
    id 'application'
}

group 'de.test'
version '1.0.8'

sourceCompatibility = 11

mainClassName = 'de.test.MainFX'

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

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    compile "commons-io:commons-io:2.6"
    compile "org.apache.logging.log4j:log4j-api:2.11.2"
    compile "dom4j:dom4j:1.6.1"
    compile "commons-lang:commons-lang:2.6"
    compile "axis:axis:1.4"
    compile "jaxen:jaxen:1.1.6"
    compile "net.java.dev.jna:platform:3.5.2"
    compile "org.apache.poi:poi:4.1.0"
    compile "org.apache.poi:poi-scratchpad:4.1.0"
    compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.4"

    compile "org.jboss.resteasy:resteasy-multipart-provider:3.7.0.Final"
    compile "org.jboss.resteasy:resteasy-jackson2-provider:3.7.0.Final"
    compile "org.apache.logging.log4j:log4j-core:2.11.2"
    compile "org.jboss.resteasy:resteasy-client:3.7.0.Final"
    compile "org.openjfx:javafx-graphics:11.0.2:win"
    compile "org.openjfx:javafx-base:11.0.2:win"
    compile "org.openjfx:javafx-controls:11.0.2:win"
    compile "org.openjfx:javafx-fxml:11.0.2:win"
    compile "org.openjfx:javafx-graphics:11.0.2:win"
}

结果真奇怪java -jar ApplicationTest.jar返回

错误:无法找到或加载主类 de.test.MainFX 原因: java.lang.ClassNotFoundException: de.test.MainFX

我们仔细检查了 jar 文件,并且类文件位于正确的位置(de\test\Main.class 和 de\test\MainFX.class)

编辑 2) FAT Jar 与第二个主类

基于this article,我们再次添加了 javafx 插件,移除了 javafx 依赖项,并在清单中定义了 Launcher-Main,并将常规 FXML 邮件定义为 mainClassName:

...
mainClassName = 'de.test.MainFX'

javafx {
    version = 11
    modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.web']
}

jar {
    manifest {
        attributes 'Main-Class': 'de.test.MainLauncher'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

结果是

> 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:1016)
>         at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
>         at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
>         at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
>         at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
>         at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
>         at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
>         at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
>         at de.test.MainLauncher.main(MainLauncher.java:7) Caused by: java.lang.ClassNotFoundException: javafx.application.Application
>         at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
>         at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
>         at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
>         ... 10 more

所以我们再次添加 javafx 的依赖项,结果如下:

Graphics Device initialization failed for :  d3d, sw
Error initializing QuantumRenderer: no suitable pipeline found
java.lang.RuntimeException: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer.getInstance(QuantumRenderer.java:280)
        at com.sun.javafx.tk.quantum.QuantumToolkit.init(QuantumToolkit.java:222)
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:260)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.RuntimeException: Error initializing QuantumRenderer: no suitable pipeline found
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.init(QuantumRenderer.java:94)
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:124)
        ... 1 more
Exception in thread "main" java.lang.RuntimeException: No toolkit found
        at com.sun.javafx.tk.Toolkit.getToolkit(Toolkit.java:272)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:267)
        at com.sun.javafx.application.PlatformImpl.startup(PlatformImpl.java:158)
        at com.sun.javafx.application.LauncherImpl.startToolkit(LauncherImpl.java:658)
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:678)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.base/java.lang.Thread.run(Thread.java:834)

【问题讨论】:

标签: java javafx


【解决方案1】:

根据我迄今为止的(痛苦的)经验,让具有大量外部依赖项的大型 JavaFX 11+ 项目正常工作(您无法控制)的唯一方法是将所有内容保留在类路径中(也是 JavaFX 依赖项)并将该包装器用于主类,就像您在上一个示例中所做的那样。 (不过,您当前的 Gradle 文件是错误的。请参阅我上面的评论。)试图摆弄模块系统只会让您无处可去,只会浪费您的时间。

我目前执行以下操作来创建 exe(或 Mac 上的应用)。

  1. 使用上述常规设置。
  2. 通过 Maven/Gradle 将所有依赖项收集到一个 lib 文件夹中。
  3. 使用 jlink 为您的程序创建专用运行时。
  4. 使用 EA 版本的 jpackage 创建 exe/app。

一旦你弄清楚了你需要的所有选项,这种方法就像一个魅力。

使用jlink 构建您自己的运行时很简单。

JAVA_HOME=<the java version you want to use>

$JAVA_HOME/bin/jlink --no-header-files --no-man-pages --compress=2 --strip-debug \
--add-modules <a list of the required or just all modules> \
--output java-runtime

您可以使用jdeps 工具找出您需要的模块。

【讨论】:

  • 痛苦的经历是正确的表达方式。我不明白为什么分发东西如此复杂。我按照您的步骤操作,但我使用“gradle installDist”来收集依赖项,并且我只是复制了整个 JRE 而不是使用 jlink。它几乎可以工作了。一旦它起作用,我将在这里发布我的解决方案。 (而且我根本没有使用任何 module-info.java)
  • 从一个完整的 JRE 开始可能是你能做的最好的开始,但是一旦它以这种方式工作,即使你不关心,使用 jlink 也可以显着减少你的包的大小模块。这是因为 jlink 可以从您的 JRE 中剥离您在捆绑应用程序中不需要的所有内容。我会对结果感兴趣。
猜你喜欢
  • 2019-03-05
  • 2019-03-30
  • 2019-06-15
  • 1970-01-01
  • 1970-01-01
  • 2019-04-08
  • 1970-01-01
  • 2017-03-09
相关资源
最近更新 更多