web-dev-qa-db-ja.com

JavaFX8での一般的な例外処理

シーンのコントローラーがビジネスコードを呼び出すと、例外が発生します。これらの種類の例外を一般的な方法で処理するにはどうすればよいですか?

Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)メソッドを試しましたが、呼び出されないため、JavaFXフレームワーク内のどこかで例外がキャッチされていると思います。

この例外を処理したり、少なくともユーザーに役立つ情報を表示したりするにはどうすればよいですか?

15
Hannes

JavaFX 8以降、Thread.setDefaultUncaughtExceptionHandler(...)は機能するはずです。 RT-15332 を参照してください。

start(...)メソッドの実行中にキャッチされない例外が発生した場合、状況は少し複雑になります。アプリケーションの起動方法によっては、start()を呼び出すコード(たとえば、Application.launch(...)の実装)が例外をキャッチして処理する場合があります。これにより、デフォルトの例外ハンドラーが明らかに妨げられます。呼び出されました。

特に、私のシステム(Mac OS X10.9.5のJDK1.8.0_20)では、アプリケーションがmain(...)を呼び出すApplication.launch(...)メソッドを介して起動した場合、例外が発生するようです。 start()メソッドでスローされたものはキャッチされます(再スローされません)。

ただし、main(...)メソッド(以下の注を参照)を削除してアプリケーションを直接起動すると、start()メソッドでスローされた例外が再スローされ、デフォルトの例外ハンドラーを呼び出すことができます。それは単に伝播するだけではないことに注意してください。 start()はFXアプリケーションスレッドで呼び出され、例外はメインスレッドから再スローされます。実際、これが発生すると、FXアプリケーションスレッドが実行されていると想定するデフォルトハンドラーのコードは実行に失敗します。したがって、この場合の起動コードは、start()メソッドとで例外をキャッチすると思います。 catchブロック、_FX Application Thread_をシャットダウンしてから、呼び出し元のスレッドから例外を再スローします。

このすべての結果は重要です-デフォルトのハンドラーでstart()メソッドの例外を処理する場合、例外がFXアプリケーションスレッドでスローされない限り、UIコードを呼び出さないでください( Platform.runLater(...))経由でも。

注:(これに気付いていない人のために)。 Java 8の時点で、クラス名を引数として渡すことにより、main(...)メソッドがなくても、Applicationサブクラスを直接起動できます。通常の方法(つまり、_Java MyApp_)でJVM実行可能ファイルに追加します。これにより、FXツールキットが起動し、FXアプリケーションスレッドが起動し、Applicationサブクラスがインスタンス化され、init()、次にFXアプリケーションスレッドでstart()を呼び出します。興味深いことに(そしておそらく間違って)、main(...)を呼び出すApplication.launch()メソッドはわずかに異なる動作をします。 start(...)メソッドで捕捉されなかった例外に関して。

これが基本的な例です。 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);
//  }
}
_

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:

_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:

_<?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:

_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();
    }
}
_
28
James_D

これは実際にはちょっとトリッキーです、私は以前に同じ問題に遭遇しました、そして私はどんなエレガントな解決策も思い付くことができませんでした。明らかに、これを処理するための非常に手間のかかる方法(そして正直なところ、おそらく完全に間違った方法)の1つは、各コントローラークラスメソッド(@FXMLで始まるメソッド)で、メソッドの本体全体をtry{} catch(Throwable t){}ブロック、次にスロー可能なキャッチの内部で、例外の結果を分析して、災害時にユーザーに表示するのに役立つ情報を特定します。

また、少なくともJavafx 8(2.0-2.2では試していません)では、FXMLをロードする場所をラップしようとすると(たとえば、アプリケーションのメインの「Start」メソッドのように)、同じ種類のthrowableブロック、それControllerクラスからの例外をキャッチしませんこれは、そのスレッドとFXMLControllerクラスで使用されているスレッドとの間の何らかの分離を意味しているようです。ただし、呼び出し元のクラスでThread.currentThread();オブジェクトへの参照を保持し、コントローラーで同じことを行うと、2つの.equalsが判明するため、これは間違いなく同じアプリケーションスレッド上にあります。本当。したがって、シートの下で、Javafxは、チェックされていない例外をそれらのクラスから切り離すためにいくつかの魔法を実行しています。

私はそれ以上それを調べていません。

正直なところ、私はこの答えをここに載せることさえ嫌いです。なぜなら、これがどれほど間違っているかを正しく理解せずに誰かがそれを使用するのではないかと心配しているからです。そのため、誰かがより良い答えをパイプで送った場合、私はこれをすぐに削除します。

幸運を!

1
WillBD