JavaFX WebView 无法与 Spring Boot Maven 一起使用

标签 java spring-boot javafx javafx-webengine javafx-webview

我已经使用场景生成器创建了 Spring boot JavaFX 应用程序。当我在 FXML 文件中添加 WebView 组件时,出现以下错误:

Caused by: javafx.fxml.LoadException: 
/C:/Users/pdhasar/WebProject/standalone-app/target/classes/WebView.fxml:12

    at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2605)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2583)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2445)
    at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
    at com.web.view.WebViewApplication.init(WebViewApplication.java:29)
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:841)
    ... 3 more
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
    at javafx.fxml.JavaFXBuilder$ObjectBuilder.build(JavaFXBuilderFactory.java:278)
    at javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:759)
    at javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2827)
    at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2536)
    ... 7 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at javafx.fxml.JavaFXBuilder$ObjectBuilder.build(JavaFXBuilderFactory.java:270)
    ... 10 more
Caused by: java.lang.IllegalStateException: Not on FX application thread; currentThread = JavaFX-Launcher
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:204)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:438)
    at javafx.scene.web.WebEngine.checkThread(WebEngine.java:1182)
    at javafx.scene.web.WebEngine.<init>(WebEngine.java:822)
    at javafx.scene.web.WebEngine.<init>(WebEngine.java:811)
    at javafx.scene.web.WebView.<init>(WebView.java:271)
    at javafx.scene.web.WebViewBuilder.build(WebViewBuilder.java:60)
    ... 21 more
2019-10-07 14:00:12.979  INFO 14680 --- [       Thread-6] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7f904074: startup date [Mon Oct 07 14:00:10 IST 2019]; root of context hierarchy

下面给出了 Spring Boot 的代码: WebView应用程序:

package com.web.view;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.web.view")
public class WebViewApplication extends Application {

    private ConfigurableApplicationContext context;
    private Parent rootNode;

    @Override
    public void init() throws Exception {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(WebViewApplication.class);
        context = builder.run(getParameters().getRaw().toArray(new String[0]));
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/WebView.fxml"));
        loader.setControllerFactory(context::getBean);
        rootNode = loader.load();
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
        double width = visualBounds.getWidth();
        double height = visualBounds.getHeight();

        primaryStage.setScene(new Scene(rootNode, width, height));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        context.close();
    }
}

WebViewController:

package com.web.view.controller;

import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import org.springframework.stereotype.Controller;

@Controller
public class WebViewController {

    @FXML private WebView webView;
    private WebEngine webEngine;

    @FXML
    private void handleWebViewButtonAction(ActionEvent event) {
        webEngine = webView.getEngine();
        webEngine.load("https://www.google.co.in");
    }
}

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.web.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.web.view.controller.WebViewController">
   <children>
      <WebView fx:id="webView" layoutX="-116.0" layoutY="-111.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
   </children>
</AnchorPane>

看来spring正在尝试访问WebView,并且javafx中有一个限制,不允许spring访问它。有没有办法让 WebView 与 spring boot 一起工作。谁能帮我解决这个问题吗?

最佳答案

JavaFX 线程背景

JavaFX 系统的一个限制是,并非所有控件都可以通过 JavaFX 应用程序线程构建。当给定控件或 UI 元素出现这种情况时,其文档会对此进行说明。

特别是 WebView 的文档状态:

WebView objects must be created and accessed solely from the FX thread.

JavaFX 应用程序的 init() 方法不在 JavaFX 应用程序线程上运行。在我的环境(OS X、JavaFX 13)中,JavaFX 应用程序的 init() 方法由名为“JavaFX-Launcher”的线程调用。如果将以下代码放入 init() 中,可以看出:

System.out.println("Thread name: " + Thread.currentThread().getName());

将相同的代码放在 start() 方法中表明它正在不同的线程上运行,该线程的名称为“JavaFX Application Thread”。这是创建 WebView 的正确线程。

FXML 加载器根据 FXML 文件构造对象。如果您尝试从 JavaFX 应用程序线程加载 FXML,并且它包含必须在 JavaFX 应用程序线程上构建的控件之一,您将会失败(如您所见)。大多数控件都可以在 JavaFX 线程之外构建,因此大多数时候这不是问题。但是,WebView 必须在 JavaFX 应用程序线程上构建,因此您无法从 JavaFX 应用程序加载带有 WebView 的 FXML 文档线程。

start() 而不是 init() 加载包含 WebView 引用的 FXML

解决方案是将加载包含 WebView 的 FXML 的位置从 init() 方法移动到 start() 方法(由评论中的 kleopatra)如下所示:

import javafx.fxml.FXMLLoader;
import javafx.geometry.Rectangle2D;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Screen;
import javafx.stage.Stage;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.web.view")
public class WebViewApplication extends Application {

    private ConfigurableApplicationContext context;
    private Parent rootNode;

    @Override
    public void init() throws Exception {
        SpringApplicationBuilder builder = new SpringApplicationBuilder(WebViewApplication.class);
        context = builder.run(getParameters().getRaw().toArray(new String[0]));
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/WebView.fxml"));
        loader.setControllerFactory(context::getBean);
        rootNode = loader.load();

        Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
        double width = visualBounds.getWidth();
        double height = visualBounds.getHeight();

        primaryStage.setScene(new Scene(rootNode, width, height));
        primaryStage.centerOnScreen();
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        context.close();
    }
}

不相关的设计建议

顺便说一句,我建议不要将您的 JavaFX 应用程序设置为 Spring 应用程序。

相反,为 Spring 应用程序创建一个单独的类:

@SpringBootApplication
public class MySpringApplication {
    // spring application implementation.
}

并让您的 JavaFX 应用程序在已使用特定 Spring 应用程序类初始化的 SpringApplicationBuilder 实例上调用 run() (即在 JavaFX init( )方法有):

SpringApplicationBuilder builder = new SpringApplicationBuilder(
    MySpringApplication.class
);
context = builder.run(
    getParameters().getRaw().toArray(new String[0])
);

否则,您可能会在运行时创建 JavaFX 应用程序的两个实例(一个由 JavaFX 应用程序创建),另一个由 Spring 应用程序运行程序创建,这可能会造成混淆。另外,通过将两者分离,您可以通过更强的关注点分离获得更好的设计,其中 JavaFX 应用程序负责处理 JavaFX 应用程序生命周期事件,Spring 应用程序负责 Spring 生命周期事件。

然后您可以完全独立于 JavaFX 应用程序启动和测试您的 Spring 应用程序,这将使代码的测试和分析变得更加容易。

关于JavaFX WebView 无法与 Spring Boot Maven 一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58266201/

相关文章:

java - EMF Compare 代码的编译问题

java - 如何为以下查询创建 jpa 规范或条件查询的谓词

java - 在 Spring Boot HTML 页面中显示 Maven 项目构建属性

商业产品中的 JavaFX

java - Java FX 中的 Android 适配器替代方案

java - 为什么无法使用布局约束调整 Canvas 大小?

java - 记录线程安全(Java + 注释)

java - JMS onMessage JBoss,使用了多少个线程?

java - Android Studio 中有什么方法可以让我们的应用程序防篡改吗?

spring-boot - Date 的默认序列化格式是否随着最近的 Spring Boot 版本/Jackson 版本而改变?