java JavaFX 8 中的一般异常处理

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26361559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 09:48:21  来源:igfitidea点击:

General Exception handling in JavaFX 8

javaexception-handlingjavafxjava-8javafx-8

提问by Hannes

Given the controller of a Scene calls business code which raises an Exception. How can I handle those kind of Exceptions in a general fashion?

鉴于场景的控制器调用引发异常的业务代码。我如何以一般方式处理这些异常?

I tried the Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)method but it is not invoked so I believe that the Exceptions are catched somewhere inside the JavaFX framework.

我尝试了该Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)方法,但没有调用它,所以我相信异常是在 JavaFX 框架内的某个地方捕获的。

What could I do to handle this Exceptions or at least show some useful information to the user?

我能做些什么来处理这个异常或至少向用户显示一些有用的信息?

回答by James_D

As of JavaFX 8, Thread.setDefaultUncaughtExceptionHandler(...)should work: see RT-15332.

从 JavaFX 8 开始,Thread.setDefaultUncaughtExceptionHandler(...)应该可以工作:参见RT-15332

Things are a little complicated if an uncaught exception occurs during execution of the start(...)method. Depending on how the application is launched, the code that invokes start()(e.g. the implementation of Application.launch(...)) may catch the exception and handle it, which would obviously prevent the default exception handler from being invoked.

如果在start(...)方法执行过程中发生未捕获的异常,事情会有点复杂。根据应用程序的启动方式,调用start()(例如 的实现Application.launch(...))的代码可能会捕获异常并对其进行处理,这显然会阻止调用默认异常处理程序。

In particular, on my system (JDK 1.8.0_20 on Mac OS X 10.9.5), it appears that if my application starts up via a main(...)method that invokes Application.launch(...), any exception thrown in the start()method is caught (and not rethrown).

特别是,在我的系统(Mac OS X 10.9.5 上的 JDK 1.8.0_20)上,如果我的应用程序通过main(...)调用的方法启动,则该方法中Application.launch(...)抛出的任何异常start()都会被捕获(而不是重新抛出)。

However, if I remove the main(...)method (see note below) and launch the application directly, any exception thrown in the start()method is rethrown, allowing the default exception handler to be invoked. Note that it doesn't merely propagate up. start()is invoked on the FX Application Thread and the exception is rethrown from the main thread. Indeed, when this occurs, code in the default handler that assumes the FX Application Thread is running fails to run: so my guess is that the launching code in this case catches exceptions in the start()method, and in the catchblock, shuts down the FX Application Thread, and then rethrows the exception from the calling thread.

但是,如果我删除该main(...)方法(请参阅下面的注释)并直接启动应用程序,start()则会重新抛出该方法中抛出的任何异常,从而允许调用默认异常处理程序。请注意,它不仅仅是向上传播。start()在 FX 应用程序线程上调用,并从主线程重新抛出异常。实际上,当发生这种情况时,假定 FX 应用程序线程正在运行的默认处理程序中的代码无法运行:所以我的猜测是,在这种情况下,启动代码在start()方法中捕获异常,在catch块中,关闭FX Application Thread, 和然后从调用线程重新抛出异常。

The upshot of all this is that it is important - if you want your default handler to handle exceptions in the start()method, you should not call any UI code if the exception is not thrown on the FX Application Thread (even via a Platform.runLater(...)).

所有这一切的结果是它很重要 - 如果您希望默认处理程序处理start()方法中的异常,如果异常未在 FX 应用程序线程上抛出(即使通过 a Platform.runLater(...)),则不应调用任何 UI 代码。

Note:(for those who may not be aware of this). As of Java 8, you can directly launch an Applicationsubclass even if it doesn't have a main(...)method, by passing the classname as an argument to the JVM executable in the usual way (i.e. java MyApp). This does what you'd expect: starts up the FX toolkit, starts the FX Application thread, instantiates the Applicationsubclass and calls init(), then on the FX Application Thread calls start(). Interestingly (and perhaps incorrectly), a main(...)method that invokes Application.launch()behaves slightly differently with respect to uncaught exceptions in the start(...)method.

注意:(对于那些可能不知道这一点的人)。从 Java 8 开始,您可以直接启动一个Application子类,即使它没有main(...)方法,通过以通常的方式(即java MyApp)将类名作为参数传递给 JVM 可执行文件。这符合您的预期:启动 FX 工具包,启动 FX 应用程序线程,实例化子Application类并调用init(),然后在 FX 应用程序线程上调用start()。有趣的是(也许是错误的),main(...)调用Application.launch()start(...)方法在方法中未捕获的异常方面的行为略有不同。

Here is a basic example. Uncomment the code in Controller.initialize()to see an exception thrown in the start()method.

这是一个基本的例子。取消注释中的代码Controller.initialize()以查看start()方法中抛出的异常。

package application;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {

        Thread.setDefaultUncaughtExceptionHandler(Main::showError);

        Parent root = FXMLLoader.load(getClass().getResource("Main.fxml"));
        Scene scene = new Scene(root,400,400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static void showError(Thread t, Throwable e) {
        System.err.println("***Default exception handler***");
        if (Platform.isFxApplicationThread()) {
            showErrorDialog(e);
        } else {
            System.err.println("An unexpected error occurred in "+t);

        }
    }

    private static void showErrorDialog(Throwable e) {
        StringWriter errorMsg = new StringWriter();
        e.printStackTrace(new PrintWriter(errorMsg));
        Stage dialog = new Stage();
        dialog.initModality(Modality.APPLICATION_MODAL);
        FXMLLoader loader = new FXMLLoader(Main.class.getResource("Error.fxml"));
        try {
            Parent root = loader.load();
            ((ErrorController)loader.getController()).setErrorText(errorMsg.toString());
            dialog.setScene(new Scene(root, 250, 400));
            dialog.show();
        } catch (IOException exc) {
            exc.printStackTrace();
        }
    }

//  public static void main(String[] args) {
//      launch(args);
//  }
}

With Main.fxml:

使用 Main.fxml:

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

<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.geometry.Insets?>

<HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller"
    alignment="center" spacing="5">
    <children>
        <Button text="Do something safe" onAction="#safeHandler" />
        <Button text="Do something risky" onAction="#riskyHandler" />
        <Label fx:id="label" />
    </children>
    <padding>
        <Insets top="10" left="10" right="10" bottom="10" />
    </padding>
</HBox>

Controller.java:

控制器.java:

package application;

import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class Controller {
    private final IntegerProperty counter = new SimpleIntegerProperty(1);

    @FXML
    private Label label ;

    public void initialize() throws Exception {
        label.textProperty().bind(Bindings.format("Count: %s", counter));

        // uncomment the next line to demo exceptions in the start() method:
        // throw new Exception("Initializer exception");
    }

    @FXML
    private void safeHandler() {
        counter.set(counter.get()+1);
    }

    @FXML
    private void riskyHandler() throws Exception {
        if (Math.random() < 0.5) {
            throw new RuntimeException("An unknown error occurred");
        }
        safeHandler();
    }
}

Error.fxml:

错误.fxml:

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

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.ErrorController">
    <center>
        <ScrollPane>
            <content>
                <Label fx:id="errorMessage" wrapText="true" />
            </content>
        </ScrollPane>
    </center>
    <bottom>
        <HBox alignment="CENTER">
            <Button text="OK" onAction="#close"/>
        </HBox>
    </bottom>
</BorderPane>

ErrorController.java:

错误控制器.java:

package application;

import javafx.fxml.FXML;
import javafx.scene.control.Label;

public class ErrorController {
    @FXML
    private Label errorMessage ;

    public void setErrorText(String text) {
        errorMessage.setText(text);
    }

    @FXML
    private void close() {
        errorMessage.getScene().getWindow().hide();
    }
}

回答by WillBD

This is actually kind of tricky, I encountered the same issue before, and I couldn't come up with any elegant solutions. Obviously, one really heavy-handed way (and honestly, probably totally wrong way) to handle this, is in each of the controller class methods (the ones that begin with @FXML), wrap the whole body of the method in a try{} catch(Throwable t){}block, and then inside of your throwable catch, do some analysis on the result of the exception to try and determine what useful information to show the user in the event of disaster.

这实际上有点棘手,我之前遇到过同样的问题,我想不出任何优雅的解决方案。显然,处理这个问题的一种非常严厉的方法(老实说,可能是完全错误的方法)是在每个控制器类方法(以@FXML 开头的方法)中,将整个方法体包装在一个try{} catch(Throwable t){}块中,然后在你的 throwable catch 内部,对异常的结果进行一些分析,以尝试确定在发生灾难时向用户显示哪些有用的信息。

It's also worth noting, that at least in Javafx 8 (I haven't tried with 2.0-2.2) if you try wrapping the place where you load the FXML (like in your applications main 'Start' method, for example), in the same kind of throwableblock, it Does not catch the exception from the Controller classWhich seems to imply some sort of separation between that thread and the one being used in the FXML Controller class. However, It is definitely on the same Application thread, since if you keep a reference to the Thread.currentThread();object in the calling class, and then do the same in the controller, the .equals on the two will turn out true. So under the sheets Javafx is doing some magic to detach unchecked exceptions from those classes.

还值得注意的是,至少在 Javafx 8 中(我还没有尝试过 2.0-2.2),如果您尝试包装加载 FXML 的位置(例如在您的应用程序主“开始”方法中),在相同类型的throwable块,它不会捕获来自 Controller 类的异常,这似乎意味着该线程与 FXML Controller 类中使用的线程之间存在某种分离。但是,它肯定在同一个应用程序线程上,因为如果您Thread.currentThread();在调用类中保留对对象的引用,然后在控制器中执行相同操作,则两者的 .equals 将变为 true。因此,Javafx 正在做一些魔术来从这些类中分离未经检查的异常。

I haven't looked any further into it than that.

我没有进一步研究它。

Truth be told, I hate even having this answer up here, because I'm afraid someone will use it without the proper understanding of just how incorrect this is. As such, if someone pipes in with a better answer, I'm going to delete this straightaway.

说实话,我什至讨厌把这个答案放在这里,因为我担心有人会在没有正确理解这是多么不正确的情况下使用它。因此,如果有人提出更好的答案,我将立即删除它。

Good luck!

祝你好运!