这是对 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.json
在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
,这次应该可以了。
如果您再次检查 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/