Gestion des exceptions générales dans JavaFX 8

Étant donné que le contrôleur d’une scène appelle un code commercial qui déclenche une exception. Comment puis-je gérer ce genre d’exceptions de manière générale?

J’ai essayé la Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler) mais elle n’est pas invoquée. Je crois donc que les exceptions sont capturées quelque part dans le cadre JavaFX.

Que puis-je faire pour gérer ces exceptions ou au moins montrer des informations utiles à l’utilisateur?

À partir de JavaFX 8, Thread.setDefaultUncaughtExceptionHandler(...) devrait fonctionner: voir RT-15332 .

Les choses sont un peu compliquées si une exception non interceptée survient lors de l’exécution de la méthode start(...) . Selon la manière dont l’application est lancée, le code qui appelle start() (par exemple, l’implémentation de Application.launch(...) ) peut intercepter l’exception et la gérer, ce qui empêcherait évidemment le gestionnaire d’exceptions par défaut d’être appelé.

En particulier, sur mon système (JDK 1.8.0_20 sur Mac OS X 10.9.5), il semble que si mon application démarre via une méthode main(...) qui appelle Application.launch(...) , toute exception jeté dans la méthode start() est intercepté (et non rediffusé).

Cependant, si je supprime la méthode main(...) (voir la remarque ci-dessous) et lance l’application directement, toute exception levée dans la méthode start() est renumérisée, ce qui permet d’appeler le gestionnaire d’exceptions par défaut. Notez que cela ne se propage pas simplement. start() est invoqué sur le thread d’application FX et l’exception est renumérotée à partir du thread principal. En effet, lorsque cela se produit, l’exécution du code dans le gestionnaire par défaut qui suppose que le thread d’application FX est en cours d’exécution échoue: je suppose donc que le code de lancement, dans ce cas, capture les exceptions dans la méthode start() et, dans le bloc catch , se ferme en bas du FX Application Thread , puis renvoie l’exception du thread appelant.

Le résultat de tout cela est que c’est important – si vous voulez que votre gestionnaire par défaut gère les exceptions dans la méthode start() , vous ne devez appeler aucun code d’interface utilisateur si l’exception n’est pas levée sur le thread d’application FX (même via une Platform.runLater(...)Platform.runLater(...) ).

Remarque: (pour ceux qui ne le savent peut-être pas). A partir de Java 8, vous pouvez lancer directement une sous-classe Application même si elle ne possède pas de méthode main(...) , en transmettant le nom de la classe en tant qu’argument à l’exécutable JVM de la manière habituelle (c’est-à-dire java MyApp ). Cela correspond à ce que vous attendez: démarre la boîte à outils FX, démarre le thread d’application FX, instancie la sous-classe Application et appelle init() , puis lance les appels de thread d’application FX start() . Fait intéressant (et peut-être incorrectement), une méthode main(...) qui appelle Application.launch() se comporte légèrement différemment en ce qui concerne les exceptions non capturées dans la méthode start(...) .

Voici un exemple de base. Décommentez le code dans Controller.initialize() pour voir une exception levée dans la méthode start() .

 package application; import java.io.IOException; import java.io.PrintWriter; import java.io.SsortingngWriter; 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) { SsortingngWriter errorMsg = new SsortingngWriter(); 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.toSsortingng()); dialog.setScene(new Scene(root, 250, 400)); dialog.show(); } catch (IOException exc) { exc.printStackTrace(); } } // public static void main(Ssortingng[] args) { // launch(args); // } } 

Avec 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(Ssortingng text) { errorMessage.setText(text); } @FXML private void close() { errorMessage.getScene().getWindow().hide(); } } 

C’est en fait un peu délicat, j’avais déjà rencontré le même problème et je ne pouvais pas proposer de solutions élégantes. De toute évidence, une façon très lourde (et honnêtement, probablement totalement fausse) de gérer cela consiste à utiliser chacune des méthodes de la classe du contrôleur (celles commençant par @FXML), pour envelopper le corps entier de la méthode try{} catch(Throwable t){} bloquer, puis dans votre capture jetable, effectuez une parsing du résultat de l’exception pour essayer de déterminer les informations utiles à montrer à l’utilisateur en cas de sinistre.

Il est également intéressant de noter que, du moins dans Javafx 8 (je n’ai pas essayé avec la version 2.0-2.2), si vous essayez d’emballer le lieu où vous chargez le FXML (comme dans la méthode principale “Démarrer” de vos applications, par exemple), dans Même type de bloc throwable , il throwable pas l’exception de la classe Controller, ce qui semble impliquer une sorte de séparation entre ce thread et celui utilisé dans la classe du contrôleur FXML. Cependant, il est définitivement sur le même thread d’application, puisque si vous gardez une référence à Thread.currentThread(); object dans la classe appelante, puis faites la même chose dans le contrôleur, les valeurs .equals sur les deux deviendront vraies. Donc, sous les feuilles, Javafx fait un peu de magie pour détacher les exceptions non contrôlées de ces classes.

Je n’ai pas cherché plus loin que ça.

À vrai dire, je déteste avoir même cette réponse ici, car je crains que quelqu’un ne l’utilise sans comprendre correctement à quel point c’est inexact. En tant que tel, si quelqu’un répond avec une meilleure réponse, je vais le supprimer immédiatement.

Bonne chance!