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

问题描述

这是对this问题的跟进问题。

我正在尝试将JavaFX项目编译为本机映像,以使其在本机运行而无需用户安装Java。 gluonHQ客户端插件解决了JavaFX和反射问题,因此编译现在很成功。

我已经设法获得一个简单的JavaFX项目(该示例由IntelliJ在创建JavaFX项目时生成),可以使用gluon客户端maven插件进行编译。但是,在命令行上运行本机映像时,它会给出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来使本机映像工作:

<?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>

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

解决方法

我将进一步扩展@mipa的答案。

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

默认情况下,客户端plugin为您提供一个alignment文件,其中包含您可能在FXML文件中使用的最多JavaFX类和方法的。

您可以阅读here,此文件是在运行reflectionConfig.json(或mvn client:compile)时生成的,并且可以在mvn client:link下找到(具有目标体系结构和操作系统名称)。

到目前为止,它包含大约290个类(Java和JavaFX)以及字段和方法。

如果您进行检查,将会看到针对给定的target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json类:

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",{"name":"getColumnIndex",{"name":"setColumnSpan",{"name":"getColumnSpan",{"name":"setRowSpan",{"name":"getRowSpan",{"name":"getRowConstraints",{"name":"getColumnConstraints",{"name":"setHgrow","javafx.scene.layout.Priority"] },{"name":"getHgrow",{"name":"setVgrow",{"name":"getVgrow",{"name":"setMargin","javafx.geometry.Insets"] },{"name":"getMargin","parameterTypes":["javafx.scene.Node"] } ] },中的所有静态方法,因此可与Client插件一起使用:

GridPane

但是,不包括<GridPane> <Label text="a label" GridPane.columnIndex="0" GridPane.rowIndex="0"/> </GridPane> 方法,这就是您的fxml失败的原因。

有两种可能的解决方案:

1。配置文件

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

  • alignment下创建文件reflectionconfig.json

  • 添加缺少的方法:

src/main/resources/META-INF/substrate/config/
  • 再次运行[ { "name" : "javafx.scene.layout.GridPane","methods":[ {"name":"setAlignment","parameterTypes":["javafx.geometry.Pos"] },{"name":"getAlignment",{"name":"setHgap","parameterTypes":["double"] },{"name":"getHgap",{"name":"setVgap",{"name":"getVgap","parameterTypes":[] } ] } ] ,这次应该可以了。

如果再次检查mvn client:build client:run文件,您将看到json文件的内容已包含在末尾,现在所有已使用的target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json方法都可以进行反射。

2。反射列表

或者,您可以简单地将整个类添加到ReflectionList:

GridPane

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

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

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

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

,

您必须将更多的通过FXML加载的类添加到反射列表中。例如。我还必须添加javafx.geometry.HPosjavafx.geometry.VPos。事实是不一致的,这使情况变得复杂。默认情况下已经包含了某些类,而其他类则没有。您将需要在这里进行一些实验。有时,如果已经在其中定义了属性,我什至必须指定类的父级。出于我自己的目的,我编写了一个简化此操作的小工具:XOR

,

我只想在José的答案中添加一个更具体的案例,也许他也可以澄清。

另一个烦人的问题是,有时仅仅将要加载的类放入反射列表中是不够的。例如,如果您想加载一个ProgressBar并将该类放入反射列表中,您仍然会收到以下错误:ProgressBar Property "progress" does not exist or is read-only。原因是“ progress”属性是在ProgressBar的超类中定义的,因此您还必须将ProgressIndicator添加到列表中。

为什么会这样,并且可以通过某种方式避免?

,

这太令人沮丧了,但是最后我没事了,这是一些建议。 如果您有fxml文件,并且该文件已加载到Controller中,则只需在ReflectionList中添加Controller即可。 resourceReflection.json不包含javafx类的所有属性,请检查是否在json中添加了所有属性,如果不只是将fx类添加到ReflectionList中,请添加 javafx.scene.layout.GridPane 将添加GridPane的所有属性。 检查fxml中的导入类,其中某些可能不会添加到json文件中,一旦找到它就添加到ReflectionList中,例如,如果fxml文件中有RadioButton,ComboBox,则不会在生成的json文件中找到它们。 因此1.复制并在META-INF.substrate.config目录中添加生成的resourceReflection.json。 2.将所有您的类添加到reflectionList,无需添加用于加载fxml文件的View。 3.将e.printStackTrace()添加到您的fxml加载器中,以检查异常情况。 4. mvn clean client:build client:run。

快乐编码!好工作胶团队。