繁体   English   中英

Java 11 与 JavaFX 构建 JAR 与 Z8ED1A771BC236C2DDD787AD93C69Z9

[英]Java 11 with JavaFX building JAR with gradle

我们正在使用 gradle 使用采用 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()
}

After running gradle clean jar we tried to execute the jar with the following command java -jar ApplicationTest-1.0.8.jar the result:

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


FAT JAR 与依赖项和模块信息。java

然后我们尝试在 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"

这是我们添加到 package 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" --module-path。 -jar ApplicationTest-1.0.8.jar

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

2019 年 9 月 20 日 8:32:55 VORM。 com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: Could not load stylesheet: com/sun/javafx/scene/control/skin/modena/modena.css Sep. 20, 2019 8:32:55 VORM. com.sun.javafx.css.StyleManager loadStylesheetUnPrivileged INFO: Could not load stylesheet: com/sun/javafx/scene/control/skin/modena/modena.css

使用 JAR 文件时没有布局

顺便说一句:使用命令gradle clean run时,IntelliJ 始终工作

使用 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文件时,应用程序将使用布局启动。

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

使用以下依赖项完美地工作

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 之外启动的应用程序。

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

编辑 1) FAT Jar 与第二主 class

就像在这篇文章中一样,我们添加了第二个主要 java class 而不扩展Application并将其放入 gradle 文件

package de.test;

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

}

另一个 class 看起来像这样:

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返回

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

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

编辑 2) FAT Jar 与第二主 class

基于这篇文章,我们再次添加了 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)

根据我到目前为止的(痛苦的)经验,获得具有大量外部依赖项的大型 JavaFX 11+ 项目的唯一方法是保持类路径上的所有内容(也是 JavaFX 依赖项)并使用它主 class 的包装器,就像您在上一个示例中所做的那样。 (不过,您当前的 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工具找出您需要的模块。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM