这是对这个问题的跟进。
我试图将一个JavaFX项目编译成一个本机映像,这样它就可以本机运行,而不需要用户安装Java。JavaFX和reflection的问题已经通过GluonHQ客户端插件解决了,所以编译现在是成功的。
我已经设法让一个简单的 JavaFX 项目(由 IntelliJ 在创建 JavaFX 项目时生成的示例)使用 Gluon 客户端专家插件进行编译。但是,当在命令行中运行本机映像时,它会给出 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
只有通过更改示例才能使本机映像正常工作。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>
为此(删除对齐方式、hspace和vspace属性):
<?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加载异常是相同的。异常总是说由:com.sun.javafx.fxml.PropertyNotFoundException: Property[X]不存在或是只读的。
两个项目在JVM上运行良好,没有引发异常。我的IDE可以检测到代码没有错误。
我只想在José的回答中再添加一个具体的例子,也许他也可以澄清这一点。
另一个恼人的问题是,有时仅仅把你想要加载的类放入反射列表是不够的。例如,如果您希望加载一个ProgressBar并将该类放入反射列表,您仍然会得到以下错误:< code>ProgressBar属性“progress”不存在或为只读。原因是属性“progress”是在ProgressBar的超类中定义的,所以您也必须将ProgressIndicator添加到列表中。
为什么会这样,能以某种方式防止吗?
您必须向reflexion列表中添加更多通过FXML加载的类。例如,我还必须添加< code > Java FX . geometry . hpos 和< code > Java FX . geometry . vpos 。这是复杂的事实,这是不一致的。默认情况下,有些类已经包含在内,有些则没有。你需要做一些实验。有时我甚至不得不指定一个类的父类,如果在那里已经定义了一个属性。为了我自己的目的,我写了一个小工具让这变得更容易:https://github.com/mipastgt/JFXToolsAndDemos#fxml-checker
我将进一步扩展@mipa的答案。
如您所知,FXML 完全是关于反射的:我们有一个 (f) xml 文件和一个解析器 (FXMLLoader
),用于查找类(GridPane
)和属性名称(对齐
),这些名称在解析该文件时解析为方法名称(set对齐(Pos)
和 getAlignment())
。
默认情况下,客户端插件会为您提供一个反射Config.json
文件,其中包含您可能在 FXML 文件中使用的大多数 JavaFX 类和方法。
正如您可以在此处阅读的那样,此文件是在运行 mvn 客户端:编译
(或 mvn 客户端:链接
)时生成的,可以在目标/客户端/$arch$os/gvm/反射配置$arch-$os.json
(带有目标体系结构和操作系统名称)下找到。
与现在一样,它包含大约290个类(Java和JavaFX),以及字段和方法。
如果检查它,您将看到,对于给定的<code>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
中的构造函数和所有静态方法,因此这适用于Client插件:
<GridPane>
<Label text="a label" GridPane.columnIndex="0" GridPane.rowIndex="0"/>
</GridPane>
但是,没有包含< code>alignment方法,这就是fxml失败的原因。
有两种可能的解决方案:
1.配置文件
在配置文件部分之后,您可以将自己的文件添加到项目中并添加缺少的方法:
>
创建文件<code>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":[] }
]
}
]
如果再次检查目标/client/$arch-$os/gvm/reflectionconfig-$arch-$os。json
文件,您将看到json文件的内容已包含在末尾,现在所有使用的<code>GridPane
2.反思清单
或者,您可以简单地将整个类添加到reflectionList:
<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将该类的所有声明和公共构造函数、字段和方法添加到其反射列表中,无论它们是否使用,这可能会对编译时间和内存占用产生(小)影响。理想情况下,上述方案1优于方案2。
只提供所需的类/方法是最好的,但是正如@mipa指出的,这将需要一些工具来发现它们。与此同时,您将不得不运行一些迭代来找出您的FXML文件中使用的所有类/方法是否都包含在默认的json文件中,并将缺少的添加到您的反射文件中(或者只是将类名添加到反射列表中)。
所有使用指南都显示了如何在单个 JAR 文件上运行。但我相信大多数项目通常在类路径上使用多个JAR(例如Gradle默认方式)。如何告诉本机映像将它们全部捆绑到一个可执行文件中?
我正在尝试用GraalVM创建一个本地映像,我的代码是: 然后我把代码本身称为: 当我运行IDE或java-jar时,它可以正常工作,但当我尝试编译为本机映像时,会抛出一个错误。下面是用于编译本机映像的命令行。 错误: TypeError:invokeMember(打印)在JavaObject[com.compiler.commons.log]上。Console@113a2d320(com.com
Spring Boot通过引入Spring Graal Native 0.6.0特性引入了GraalVM本机映像支持。现在,如果我们有一个成功编译的本机Spring启动应用程序,我们如何在命令行上动态更改应用程序的端口(就像我们在JVM模式中习惯的那样)? 这对于使用Docker或云提供商的部署场景至关重要……
main.java controller.java FXMLView.java
我有一个JavaFX项目,希望使用GraalVM Java虚拟机和相关的本机映像工具将其编译成Linux二进制文件。我正在使用GraalVM Java 11版本20.1.0和本机Image Maven插件来实现这一点,该插件是通过Maven添加的。 最初,我收到一个错误,说明我使用本机映像跟踪代理生成用于反射的配置文件,我将其传递到编译器插件中,如下所示: 我还打开了堆栈跟踪异常报告。 现在,当我
但指定的错误中的位置包含它所引用的文件。谁能解释一下这个错误的原因。是代码问题还是插件问题?