GraalVM javafx.fxml.LoadException:解析 onAction='#loginAction' 时出错,事件处理程序不在命名空间中

GraalVM javafx.fxml.LoadException: Error resolving onAction='#loginAction', either the event handler is not in the Namespace

我 运行 正在通过 Gluon 客户端插件 运行 处理 GraalVM(基于 JDK 11 构建的最新 GraalVM)生成的本机映像。

javafx.fxml.LoadException: Error resolving onAction='#loginAction', either the event handler is not in the Namespace or there is an error in the script. fxml/LoginScreen.fxml:17

编译步骤工作正常:

mvn clean client:build

我在名为“projectname/target/client/x86_64-linux/binaryname”的文件夹中看到二进制文件

当我通过“./binaryname”运行 可执行文件时产生上述错误

它在第 17 行抱怨的 FXML 代码行是:

<Button fx:id="_loginButton" layoutX="516.0" layoutY="174.0" mnemonicParsing="false" onAction="#loginAction" prefHeight="28.0" prefWidth="94.0" text="Login" />

后台代码逻辑如下,并用@FXML标注:

@FXML
void loginAction(ActionEvent event) throws InterruptedException {

    LoginService loginservice = new LoginService(_usernameTextField.getText(), _passwordTextField.getText());

根据 JavaFX 常见错误列表,问题通常是因为 onAction 事件与控制器中指定的名称不同 - JavaFX 简介 对于初学者程序员 - 第 27 页 。然而事实并非如此,我的程序命名是准确的。使用

使用 JavaFX maven 插件(与 GluonClient 分开)

maven javafx:run

程序正确启动并按预期工作。如果我需要 post 更多信息,请告诉我。

这是我的pom.xml(我只是在下面只替换了我的包裹名称)

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>com-demo-management-ui</artifactId>
<version>0.0.1-SNAPSHOT</version>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
    <client.plugin.version>0.1.26</client.plugin.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-controls</artifactId>
        <version>11</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml -->
    <dependency>
        <groupId>org.openjfx</groupId>
        <artifactId>javafx-fxml</artifactId>
        <version>11</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.12</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.6</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <release>11</release>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.3</version>
            <configuration>
                <mainClass>com.demo.Launcher</mainClass>
            </configuration>
        </plugin>
        <plugin>
            <groupId>com.gluonhq</groupId>
            <artifactId>client-maven-plugin</artifactId>
            <version>${client.plugin.version}</version>
            <configuration>
                <!-- Uncomment to run on iOS: -->
                <!-- <target>ios</target> -->
                <mainClass>com.demo.Launcher</mainClass>
                <graalvmHome>/opt/graalvm-ce-java11-20.2.0-dev/</graalvmHome>
            </configuration>
        </plugin>
    </plugins>
</build>

<pluginRepositories>
    <pluginRepository>
        <id>gluon-releases</id>
        <url>http://nexus.gluonhq.com/nexus/content/repositories/releases/</url>
    </pluginRepository>
</pluginRepositories>

最后,这是我设置控制器的代码(这是一个方法调用,我根据需要交换视图,因此在创建视图时将控制器作为参数传递):

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/"+baseController.getFxmlFile()));
        fxmlLoader.setController(baseController);
        Parent parent =  fxmlLoader.load();
        Scene scene = new Scene(parent);
        Stage stage = new Stage();
        stage.setFullScreen(fullScreen);
        stage.setMaximized(setMaximized);
        stage.setScene(scene);
        stage.show();

如果您查看 Client samples repository 中的 HelloFXML 示例,您将看到它使用带有控制器的典型 FXML 文件:

<AnchorPane fx:id="pane" ... fx:controller="hellofx.HelloController">

在您的情况下,您在 FXML 文件中没有控制器,但您提供了它:

fxmlLoader.setController(new hellofx.HelloController());

如您所知,FXMLLoader 使用反射来实例化解析 FXML 文件时找到的控制器、控件和方法。

无论哪种方式,当您单击触发 loginAction 方法的按钮时,FXMLLoader 会使用此 call:

处理
MethodHelper.invoke(method, controller, params);

使用反射来处理此类事件。

对于 GraalVM,反射 是一个问题,您必须 "help" 一点点,通过提供 classes/methods/fields在某些时候反射性地使用。了解更多信息 here.

客户端插件已经负责为您添加 JavaFX 核心 classes 和方法。您可以在 target/client/x86_64-darwin/gvm/reflectionconfig-x86_64-darwin.json.

中看到添加的内容

但是,您的自定义 classes 必须添加到该文件中。有两种方法可以做到这一点:

  • 与在 HelloFXML 中一样,通过配置/reflectionList,您将提供将被反射使用的自定义 class/es:
<plugin>
    <groupId>com.gluonhq</groupId>
    <artifactId>client-maven-plugin</artifactId>
    <version>${client.plugin.version}</version>
    <configuration>
          <reflectionList>
               <list>hellofx.HelloController</list>   <!-- your custom classes -->
          </reflectionList>
          <mainClass>${mainClassName}</mainClass>
    </configuration>
</plugin>

这具有打开所有 class methods/fields 反射的效果。您将在 json 文件中看到结果为:

  {
    "name" : "hellofx.HelloController",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true
  }
...

这应该足以解决您的问题。

  • 通过配置文件。正如您在客户端的 documentation 中看到的那样,您可以将配置文件 (reflectionconfig.json) 添加到 META-INF/substrate/config
[
  {
    "name":"hellofx.HelloController",
    "methods":[{"name":"loginAction","parameterTypes":["javafx.event.ActionEvent"] }]
  }
]

这也会修复它。当然,它可能还需要添加控制器中的其他方法(如 initialize)。

这将仅对反射开放此方法,因此它对内存占用的影响较小,并遵循插件对 JavaFX 核心的作用classes。