JavaFX 8中的常规exception处理

给定一个Scene的控制器调用业务代码,该代码引发exception。 我怎样才能以一般方式处理这种exception?

我尝试了Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)方法但是没有调用它,所以我相信exception会被Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)到JavaFX框架内的某个地方。

我该怎么做才能处理此exception或至少向用户显示一些有用的信息?

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

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

特别是在我的系统上(Mac OS X 10.9.5上的JDK 1.8.0_20),似乎我的应用程序通过调用Application.launch(...)main(...)方法启动,任何exception抛出start()方法被捕获(而不是start()抛出)。

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

所有这一切的结果是重要的是 – 如果您希望默认处理程序在start()方法中处理exception,如果FX应用程序线程上没有抛出exception(即使通过Platform.runLater(...) start() ,也不应该调用任何UI代码Platform.runLater(...) )。

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

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

 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); // } } 

使用Main.fxml:

                

Controller.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:

        

ErrorController.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(); } } 

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

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

我没有进一步了解它。

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

祝你好运!