如何使用GluonHQ客户端,Native Image和GraalVM解决已编译的JavaFX项目中的fxml加载异常? [英] How to solve fxml loading exceptions in compiled JavaFX project using GluonHQ client, Native Image and GraalVM?

查看:92
本文介绍了如何使用GluonHQ客户端,Native Image和GraalVM解决已编译的JavaFX项目中的fxml加载异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am attempting to compile a JavaFX project into a Native Image so that it will run natively without the user needing Java installed. The problems with JavaFX and reflection have been solved with the GluonHQ client plugin, so that compilation is now a success.

我设法获得了一个简单的JavaFX项目(该示例由IntelliJ在创建JavaFX项目时生成),可以使用Gluon客户端maven插件进行编译.但是,在命令行上运行本机映像时,它会给出JavaFX fxml加载异常:

I have managed to get a simple JavaFX project (the example generated by IntelliJ upon creating a JavaFX project) to compile using the Gluon client maven plugin. However, when running the native image at the command line, it gives a JavaFX fxml loading exception:

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来使本机映像工作:

It to was only possible to get the native image to work by changing the sample.fxml from this:

<?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属性):

to this (removing the alignment, hgap and vgap attributes):

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

,然后重新编译.然后,已编译的二进制文件将按预期运行.

and then recompiling. The compiled binary then runs as expected.

对于POM.xml中的Gluon插件,反射的配置如下:

Reflection has been configured as follows for the Gluon plugin in the POM.xml:

<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加载异常相同.异常总是说由以下原因引起:com.sun.javafx.fxml.PropertyNotFoundException:属性[X]不存在或为只读.两个项目都在JVM上运行良好,没有抛出异常.我的IDE无法使用该代码检测到错误.

These FXML loading exceptions are the same once a larger project JavaFX project is compiled with reflection configured. Exceptions always say Caused by: com.sun.javafx.fxml.PropertyNotFoundException: Property [X] does not exist or is read-only. Both projects run fine on the JVM, with no exceptions being thrown. My IDE can detect no errors with the code.

推荐答案

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

I will expand a little bit more @mipa's answer.

您可能知道,FXML完全是关于反射的:我们有一个(f)xml文件和一个解析器( FXMLLoader ),该解析器用于查找类( GridPane )以及解析为方法名称(

As you may know, FXML is all about reflection: we have an (f)xml file, and a parser (FXMLLoader), that finds classes (GridPane), and properties names (alignment) that are resolved to method names (setAlignment(Pos) and getAlignment()) while parsing that file.

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

By default, the Client plugin provides for you a reflectionConfig.json file with most of the JavaFX classes and methods that you might use in your FXML files.

正如您在此处所阅读的那样,此文件是在您运行 mvn时生成的client:compile (或 mvn client:link ),可以在 target/client/$ arch- $ os/gvm/reflectionconfig- $ arch- $ os下找到.json (带有您的目标体系结构和操作系统名称).

As you can read here, this file is generated when you run mvn client:compile (or mvn client:link), and can be found under target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json (with your target architecture and OS name).

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

As is now, it contains around 290 classes (Java and JavaFX), with fields and methods.

如果您对其进行检查,将会看到给定的 GridPane 类:

If you inspect it, you will see, for that given GridPane class:

,
  {
    "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 中包含构造函数和所有静态方法,因此可与Client插件一起使用:

As you can notice, it contains the constructor and all the static methods in GridPane, so this works with the Client plugin:

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

但是,不包括 alignment 方法,这就是您的fxml失败的原因.

however, the alignment methods are not included, and that's why your fxml fails.

有两种可能的解决方案:

There are two possible solutions:

1.配置文件

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

Following the Config files section, you can add your own file to your project and add the missing methods:

  • src/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 ,这一次它应该可以工作.
    • Run again mvn client:build client:run, this time it should work.
    • 如果再次检查 target/client/$ arch- $ os/gvm/reflectionconfig- $ arch- $ os.json 文件,您将看到json文件的内容已被包含在末尾,现在所有使用的 GridPane 方法都可以进行反射.

      If you inspect again the target/client/$arch-$os/gvm/reflectionconfig-$arch-$os.json file, you will see that the content of your json file has been included at the end, and now all the used GridPane methods are available for reflection.

      2.反射列表

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

      Alternatively, you could simple add the whole class to the reflectionList:

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

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

      After you run it, inspecting the json file you will see:

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

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

      The difference with the option 1 is now that you are telling GraalVM to add all the declared and public constructors, fields and methods of that class to its reflection list, whether they are used or no, which might have a (small) impact in compilation time and memory footprint. Ideally, the option 1 above is better than option 2.

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

      Providing only the required classes/methods will be best, but as @mipa points out, this will require some tooling that could discover which are those. In the meantime, you will have to run some iterations to find out if all the classes/methods used in your FXML files are included or not by the default json file, and add the missing ones to your reflection file (or simply the classes name to the reflection list).

      这篇关于如何使用GluonHQ客户端,Native Image和GraalVM解决已编译的JavaFX项目中的fxml加载异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆