java - 如何解决使用GluonHQ客户端、Native Image和GraalVM编译的JavaFX项目中的fxml加载异常?

标签 java linux maven javafx graalvm

这是对 this 的后续问题问题。

我正在尝试将 JavaFX 项目编译成 native 镜像,这样它就可以在 native 运行,而无需用户安装 Java。 JavaFX 和反射的问题已通过 GluonHQ 客户端插件解决,因此现在编译成功。

我已经设法使用 Gluon 客户端 maven 插件编译一个简单的 JavaFX 项目(IntelliJ 在创建 JavaFX 项目时生成的示例)。但是,当在命令行运行 native 图像时,它给出了 JavaFX fxml 加载异常:

Exception in thread "main" java.lang.RuntimeException: Exception in Application start method
        at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
        at java.lang.Thread.run(Thread.java:834)
        at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:518)
        at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:192)
Caused by: javafx.fxml.LoadException: 
sample.fxml:8

        at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2629)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2607)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2470)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3241)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3198)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3167)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3140)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:3117)
        at javafx.fxml.FXMLLoader.load(FXMLLoader.java:3110)
        at sample.Main.start(Main.java:13)
        at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:846)
        at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
        at java.security.AccessController.doPrivileged(AccessController.java:101)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
        at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VA_LIST:Ljava_lang_Runnable_2_0002erun_00028_00029V(JNIJavaCallWrappers.java:0)
        at com.sun.glass.ui.gtk.GtkApplication._runLoop(GtkApplication.java)
        at com.sun.glass.ui.gtk.GtkApplication.lambda$runLoop$11(GtkApplication.java:277)
        ... 3 more
Caused by: com.sun.javafx.fxml.PropertyNotFoundException: Property "alignment" does not exist or is read-only.
        at javafx.fxml.FXMLLoader$Element.processValue(FXMLLoader.java:355)
        at javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:332)
        at javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
        at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
        at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2842)
        at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2561)
        ... 20 more

只有通过更改 sample.fxml 才能使 native 图像工作:

<?import javafx.scene.layout.GridPane?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
</GridPane>

对此(删除对齐、hgap 和 vgap 属性):

<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<GridPane fx:controller="sample.Controller"
          xmlns:fx="http://javafx.com/fxml">
</GridPane>

然后重新编译。编译后的二进制文件然后按预期运行。

反射已在 POM.xml 中为 Gluon 插件配置如下:

<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>client-maven-plugin</artifactId>
    <version>0.1.30</version>
    <configuration>
        <mainClass>sample.NewMain</mainClass>
        <reflectionList>
            <list>sample.Main</list>
            <list>sample.NewMain</list>
            <list>sample.Controller</list>
            <list>javafx.fxml.FXMLLoader</list>
        </reflectionList>
    </configuration>
</plugin>

这些 FXML 加载异常是相同的,一旦一个更大的项目 JavaFX 项目被编译并配置了反射。异常总是说 Caused by: com.sun.javafx.fxml.PropertyNotFoundException: Property [X] does not exist or is read-only. 这两个项目在 JVM 上运行良好,没有抛出异常。我的 IDE 没有检测到代码错误。

最佳答案

我会进一步扩展@mipa 的回答。

您可能知道,FXML 完全是关于反射的:我们有一个 (f)xml 文件和一个解析器 ( FXMLLoader ),它可以找到类 ( GridPane ) 和属性名称 ( alignment )在解析该文件时被解析为方法名称( setAlignment(Pos) getAlignment() )。

默认情况下,客户端 plugin为您提供reflectionConfig.json包含您可能在 FXML 文件中使用的 大多数 JavaFX 类和方法的文件。

如您所见here ,此文件是在您运行 mvn client:compile 时生成的(或 mvn client:link ),并且可以在 target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json 下找到(带有您的目标架构和操作系统名称)。

现在,它包含大约 290 个类(Java 和 JavaFX),带有字段和方法。

如果你检查它,你会看到,对于给定的 GridPane类:

,
  {
    "name" : "javafx.scene.layout.GridPane",
    "methods":[
      {"name":"<init>","parameterTypes":[] },
      {"name":"setRowIndex","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
      {"name":"getRowIndex","parameterTypes":["javafx.scene.Node"] },
      {"name":"setColumnIndex","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
      {"name":"getColumnIndex","parameterTypes":["javafx.scene.Node"] },
      {"name":"setColumnSpan","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
      {"name":"getColumnSpan","parameterTypes":["javafx.scene.Node"] },
      {"name":"setRowSpan","parameterTypes":["javafx.scene.Node","java.lang.Integer"] },
      {"name":"getRowSpan","parameterTypes":["javafx.scene.Node"] },
      {"name":"getRowConstraints","parameterTypes":[] },
      {"name":"getColumnConstraints","parameterTypes":[] },
      {"name":"setHgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] },
      {"name":"getHgrow","parameterTypes":["javafx.scene.Node"] },
      {"name":"setVgrow","parameterTypes":["javafx.scene.Node","javafx.scene.layout.Priority"] },
      {"name":"getVgrow","parameterTypes":["javafx.scene.Node"] },
      {"name":"setMargin","parameterTypes":["javafx.scene.Node","javafx.geometry.Insets"] },
      {"name":"getMargin","parameterTypes":["javafx.scene.Node"] }
    ]
  }
,

如您所见,它包含 GridPane 中的构造函数和所有静态方法。 ,所以这适用于客户端插件:

<GridPane>
    <Label text="a label" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
</GridPane>

然而,alignment方法不包括在内,这就是您的 fxml 失败的原因。

有两种可能的解决方案:

<强>1。配置文件

Config files 之后部分,您可以将自己的文件添加到项目中并添加缺少的方法:

  • 创建文件 reflectionconfig.jsonsrc/main/resources/META-INF/substrate/config/

  • 添加缺少的方法:

[
  {
    "name" : "javafx.scene.layout.GridPane",
    "methods":[
      {"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },
      {"name":"getAlignment","parameterTypes":[] },
      {"name":"setHgap","parameterTypes":["double"] },
      {"name":"getHgap","parameterTypes":[] },
      {"name":"setVgap","parameterTypes":["double"] },
      {"name":"getVgap","parameterTypes":[] }
    ]
  }
]
  • 再次运行mvn client:build client:run ,这次应该可以了。

如果您再次检查 target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json文件,你会看到你的json文件的内容已经包含在末尾,现在使用的都是GridPane方法可用于反射。

<强>2。反射列表

或者,您可以简单地将整个类添加到反射列表中:

<reflectionList>
    <list>javafx.scene.layout.GridPane</list>
</reflectionList>

运行后,检查 json 文件,您将看到:

  {
    "name" : "javafx.scene.layout.GridPane",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true
  }

与选项 1 的不同之处在于,您现在告诉 GraalVM 将该类的所有声明的和公共(public)的构造函数、字段和方法添加到它的反射列表中,无论它们是否被使用,这可能会对编译时间和内存占用产生(小)影响。理想情况下,上述选项 1 优于选项 2。

提供所需的类/方法是最好的,但正如@mipa 指出的那样,这将需要一些工具来发现哪些是那些。同时,您将不得不运行一些迭代来查明您的 FXML 文件中使用的所有类/方法是否包含在默认的 json 文件中,并将缺少的添加到您的反射文件(或只是类名到反射列表)。

关于java - 如何解决使用GluonHQ客户端、Native Image和GraalVM编译的JavaFX项目中的fxml加载异常?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63527596/

相关文章:

java - SNMP4J - 无法使其运行 SHA/AES 256 的 SNMP V3

Linux VXLAN 驱动程序和网络命名空间

linux - 在 Fedora Linux 中双击可执行 .sh 文件后终端窗口关闭

maven - 在Gradle上声明存储库时脚本编译错误

java - Maven 没有获取依赖关系

java - 将 JAR 中的文件包含在依赖项目中

java - Spring Webflux 不尊重数据库名称 MongoDB 的配置

java - 多次运行Java程序时如何防止打开新的MATLAB实例

java - Spring Oauth2 单点登录 : Use Authority granted by AuthorizationServer

linux - 本地安装的 FTP 文件夹用于 SVN 存储库