【问题标题】:JavaFX WebView is not working with spring boot mavenJavaFX WebView 不适用于 Spring Boot Maven
【发布时间】:2020-02-04 13:13:53
【问题描述】:

我已经使用场景构建器创建了 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的代码: WebViewApplication:

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 一起工作。谁能帮我解决这个问题。

【问题讨论】:

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


    【解决方案1】:

    JavaFX 线程背景

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

    特别是WebView 的文档指出:

    WebView 对象必须仅从 FX 线程创建和访问。

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

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

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

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

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

    解决方案是将加载包含 WebView 的 FXML 的位置从 init() 方法移动到 start() 方法(由 cmets 中的 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 应用程序在 SpringApplicationBuilder 实例上调用 run(),该实例已使用您的特定 Spring 应用程序类(即在您的 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 应用程序,这应该会使代码的测试和分析更加容易。

    【讨论】:

      【解决方案2】:

      我终于找到了解决方案。在 init 方法中添加以下代码后,它起作用了

      Platform.runLater(() -> { ... });
      

      【讨论】:

      • hmm ...想知道你为什么要使用 init 方法(它被记录为从 fx 应用程序线程中调用) - 而不是将它包装到 runlater 中(它将它排队等待执行fx 应用程序线程),您可以将其移至启动(在 fx 线程上调用),或者为什么不呢?
      • 它在 init 中失败,而不是在开始时。 spring 线程正在尝试初始化和加载元素,但它失败了。
      • Praveen,没有“弹簧线”。在我的环境(OS X、JavaFX 13)中,JavaFX 应用程序的init() 方法由名为JavaFX-Launcher 的线程调用。从init() => System.out.println("Thread name: " + Thread.currentThread().getName());中的以下代码可以看出。
      猜你喜欢
      • 2019-02-24
      • 1970-01-01
      • 2019-12-11
      • 1970-01-01
      • 2019-10-07
      • 2015-04-13
      • 2018-10-18
      • 2021-11-02
      • 2018-05-17
      相关资源
      最近更新 更多