[Java] Catcher toutes les RuntimeExceptions

Hello les geeks développeurs Java (et les autres aussi :stuck_out_tongue: ),

J’ai une question qui m’interroge.
Comment je peux faire pour que mon appli (c’est une appli Java « classique », donc pas de web ni rien) affiche un message dans une fenêtre spéciale dès qu’une Exception arrive ?
Attention, je ne parle pas des Exceptions basiques, dans la mesure où un traitement adéquat peut être fait dans un try { … } catch (Exception e) { … }, mais d’une RuntimeException, tel qu’un NullPointerException, un OutOfBoundsException
En gros, dès que mon programme plante (à cause d’un bug donc), je voudrais que mon soft affiche une fenêtre contenant le message d’erreur.

J’ai tenté de gruger en créant mon propre PrintStream, et en surchargeant la méthode println, puis en appellant la méthode System.setErr(…).
Hélas, ça ne marche qu’à moitié. En effet, si j’ai un NullPointerException, mon appli. bloque. Je récupère bien le message d’erreur (l’exception appellant du coup mon println de mon PrintStream), dès que je veux faire afficher ce message dans une JFrame, c’est bloqué…

Une idée ?

La semaine dernière, je me posé exactement la même question ! Comme toi, j’ai essayé de bidouiller quelques trucs sans succés donc la réponse m’intéresse aussi beaucoup, il faut dire aussi que je n’ai pas approffondie mes recherches avec google.

Ben dans tu mets le corps entier de ta fonction main dans un try-catch, et dans le catch tu peux par exemple afficher une fenêtre qui reprend la stack de l’exception. Ça marche très bien et ça attrape à peu près tout.

Si ce n’est pas clair dites le moi et je posterai quelques bouts de code ce soir.

[quote name=‹ Twin › date=’ 22 Jul 2005, 15:52’]Ben dans tu mets le corps entier de ta fonction main dans un try-catch, et dans le catch tu peux par exemple afficher une fenêtre qui reprend la stack de l’exception. Ça marche très bien et ça attrape à peu près tout.

Si ce n’est pas clair dites le moi et je posterai quelques bouts de code ce soir.
[right][post=« 379445 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Merci de l’idée, mais j’ai déjà essayé, et ça ne marche pas.
Dans mon main, il n’y a qu’une ligne, à savoir celle qui permet d’initialiser ma fenêtre principale. Donc une fois l’initialisation de celle-ci terminée, la fonction main se termine (mais pas le programme, hein :stuck_out_tongue: )…

[quote name=‹ rorotaz › date=’ 22 Jul 2005, 17:02’]Merci de l’idée, mais j’ai déjà essayé, et ça ne marche pas.
Dans mon main, il n’y a qu’une ligne, à savoir celle qui permet d’initialiser ma fenêtre principale. Donc une fois l’initialisation de celle-ci terminée, la fonction main se termine (mais pas le programme, hein :stuck_out_tongue: )…
[right][post=« 379450 »]<{POST_SNAPBACK}>[/post][/right][/quote]

Et oui, c’est aussi ma première tentative, si c’était aussi simple que cela, il n’y aurait pas eu ce post !

Ca te suffit pas le message d’erreur de la console ?
Pour ce genre de chose, j’imagine qu’il faut directement aller voir du côté de la JVM pour lui dire d’afficher un message lors d’une exception…

Y a pas un truc comme dans dans le debugguer d’Eclipse ou autre IDE? C’est la que ca me semblerait le plus logique, comme dans VS tu vas dans Debug->Exceptions et tu coches “Break on First Chance Exceptions” et tu peux meme choisir les exceptions qui t’interessent dans la liste par namespace.

Euh, j’insiste, ça marche bien comme méthode (où alors je n’ai pas compris la question).

Mon main:

public static void main(String[] saArgs) &nbsp; { &nbsp; &nbsp; &nbsp;SimpleDeckBuilder sdb = null; &nbsp; &nbsp; &nbsp;try &nbsp; &nbsp; &nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp; sdb = new SimpleDeckBuilder(); &nbsp; &nbsp; &nbsp; &nbsp; if(!sdb.isDbLocked()) &nbsp; &nbsp; &nbsp; &nbsp; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;sdb.initDB(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if(saArgs.length > 0 && saArgs[0].equals("fillDB")) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // special mode, just used by me to create the DB, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // the .csv files must be in the appli root dir &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sdb.createAndFillTables("vtescrypt.csv", "vteslib.csv"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sdb.m_dbmDataBase.shutdown(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; System.exit(0); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;else &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sdb.initGUI(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp;catch(Throwable t) &nbsp; &nbsp; &nbsp;{ &nbsp; &nbsp; &nbsp; &nbsp; Message.showError(t, "The application will shutdown."); &nbsp; &nbsp; &nbsp; &nbsp; sdb.windowClosing(null); &nbsp; &nbsp; &nbsp; &nbsp; System.exit(-1); &nbsp; &nbsp; &nbsp;} }

Avec ça tout exception (Runtime ou pas) est forcément catchée (si elle ne l’est pas avant).

Le pattern de base c’est de mettre un try/catch sur throwable dans ta classe de base (ton main ou celle directement appelée par ton main) et de printer dans ton catch

Mieux que printer je te conseille de logger (cf log4j / commons-logging)

moi, j’avais fait un catch(Exception) et non un catch(Throwable), je testerai ta solution twin, merci.

Oui enfin ca a juste rien a voir, tu veux pas rattraper tout ce qui arrive jusqu’en haut, tu veux savoir qui lance quoi et ou des que c’est lance, avec possibilite d’avoir deja ete rattrape plus bas, que ca soit rattrape ou pas en filtrant par type d’exception. Au niveau debug, c’est d’une utilitee super importante, en particulier en multi thread, ou en appels delegues et autre trucs du genre.

[quote name=‘GloP’ date=’ 23 Jul 2005, 09:43’]tu veux pas rattraper tout ce qui arrive jusqu’en haut, tu veux savoir qui lance quoi et ou des que c’est lance, avec possibilite d’avoir deja ete rattrape plus bas, que ca soit rattrape ou pas en filtrant par type d’exception
[right][post=“379590”]<{POST_SNAPBACK}>[/post][/right][/quote]
Il me semble pourtant que c’est ce que Rorotaz voulait: rattraper tout ce qui n’a pas été précédemment catché et l’afficher, non ?

Parce que si on parle de vrai débuggage dans les situations pénibles que tu évoques, alors là oui Eclipse est tout àfait équippé pour faire ce genre de choses.

[quote name=‘Twin’ date=’ 22 Jul 2005, 20:37’]Euh, j’insiste, ça marche bien comme méthode (où alors je n’ai pas compris la question).
[right][post=“379515”]<{POST_SNAPBACK}>[/post][/right][/quote]

Ben non: le main initialise le GUI puis se termine. C’est un autre thread qui gère l’interface graphique.

Rorotaz, ton idée était bonne. Chez moi le code suivant marche:

[code]public class Test extends JFrame{

    JButton b;

    public Test(){
           super(“Test”);
           this.setDefaultCloseOperation(EXIT_ON_CLOSE);
           this.setSize(300,200);
           b = new JButton(“lancer exception”);
           b.addActionListener(new ActionListener(){
              public void actionPerformed(ActionEvent e) {
                   Object o=null;
                   o.toString();
              }
           });
           this.getContentPane().add(b,null);
           this.show();
 
           PrintStream err = new PrintStream(System.out){
              public void println(Object o){
                   JOptionPane.showMessageDialog(null, o);
              }
           };
           System.setErr(err);
    }

    public static void main(String[] args) {
         Test t= new Test();
    }

}[/code]
En cliquant sur le bouton, on a bien une fenêtre qui s’affiche en indiquant une NullPointer Exception. Le seul problème que je vois, c’est qu’il n’y a pas moyen d’afficher la stackTrace, vu qu’on ne connaît pas sa taille: si on surcharge la méthode print(String s) en affichant dans une JOptionPane, une fenêtre va s’ouvrir pour chaque élément de la pile…

Bon, de retour de WE, je vois que j’ai obtenu quelques réponses. Glop glop glop.
Alors, voilà quelques explications supplémentaires :

  1. Le coup du message d’erreur dans la console, ou dans Eclipse (bien que là j’utilise Netbeans 4.1 parce qu’il y a une GUI, mais sinon d’habitude je suis sur Eclipse), ou encore en utilisant log4j, je dis non. Pourquoi ? Parce que le fait d’afficher/logguer l’erreur, c’est bien quand il s’agit de faire du débuggage. Mais dans mon cas, je veux récupérer des erreurs quand un utilisateur lambda qui utilise mon soft, lorsqu’une erreur non catchée apparait. Je veux qu’il puisse voir une fenêtre lui disant « Attention, une erreur machin est apparue. Contactez bidule et gna gna gna ». Sachant en plus que mon appli se présente sous une forme de JAR, il est fort possible que l’utilisateur n’ait pas de console lancée, donc aucun moyen sinon de voir qu’une erreur est apparue…

  2. Le coup du try {} catch dans le main, ça ne marche pas. Dans mon cas, le main ne fait qu’initialiser la GUI, qui tourne après « toute seule » si on veut. Donc le code exécutée lors d’une erreur a toutes les chances de se trouver en dehors du main. Donc cette solution ne marche pas ici.

  3. Soltan> Je pense que je dois chercher vers cette solution, mais à mon avis, il va falloir bidouiller quand même.

  4. Pour information, mon appli doit être compatible JDK 1.3.1, voire à la limite 1.4.2. Donc si vous connaissez des solutions faciles à mettre en place en 1.5, je suis tout ouïe, mais ce ne sera que pour ma culture personnelle (mais postez quand même :stuck_out_tongue: )…

Voilà. Des nouvelles idées ?
Si je trouve une solution sympa avec mon PrintStream (ou une autre), je posterais ici.

Merci pour votre aide.

Regarde ce code source : Main.java

Le code suivant marche très bien de mon coté :

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;try { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;frame = new AnalyseFrame(splash); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;splash.setProgress(100); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;try { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;frame.setVisible(true); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;splash.setVisible(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} catch(Throwable t) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;frame.setVisible(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} catch (Throwable t) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;splash.setVisible(false); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;new ExceptionDialog(t); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}

C’est peut-être pas ce qui y a de plus propre, mais c’est juste un premier jet.

Ok, j’avais capté (= pas lu :P" ) que tu étais dans une application client riche.
Si tu utilises Swing, ce qui se passe c’est que la plupart de ton code (à part le main) est exécuté dans le thread AWT et que, par défaut, un thread ne récupère pas les exceptions des threads fils (donc le thread main ne récupère pas les ex du thread awt)

A partir de là 2 solutions :

  • tu utilises le JDK 1.5 : utilises la feature Thread.setDefaultUncaughtExceptionHandler
  • sinon : ajoute un eventListener sur la file des event listener AWT, qui va récupérer chacun des évènements AWT, traiter l’erreur le cas échéant, puis propager l’évènement aux autres event listener

[code]  static class NotifyEventQueue extends EventQueue {
       public NotifyEventQueue() {
           Toolkit.getDefaultToolkit().getSystemEventQueue().push(this);
       }
       protected void dispatchEvent(java.awt.AWTEvent event) {
           try {
               super.dispatchEvent(event);
           } catch (Throwable t) {
               Test.notify(t);
           }
       }
       
   }

   static void notify(final Throwable t) {
       SwingUtilities.invokeLater(new Runnable() {
           public void run() {
               JOptionPane.showMessageDialog(null,t.getMessage(),“Unexpected Error”,JOptionPane.ERROR_MESSAGE);
           }
       });
   }
   
   public static void main(String[] args) {
       new NotifyEventQueue();
       new Thread() {
           public void run() {
               new MaGui().start();
           }
       }.start();[/code]

Pour finir si tu as des exceptions dans le thread de ta GUI mais pas dans AWT, tu peux utiliser les ThreadGroup qui permettent de récupérer les exceptions de tous les thread fils

Viewww, ta solution pourrait marcher, mais j’en ai trouvé une autre qui marche (merci le forum Developpez :stuck_out_tongue: ).
Attention, ça ne marche plus avec la 1.5 (mais Viewww nous donne le moyen de les gérer en 1.5). C’est un peu un hack, mais bon :

Au tout début de son application, faire un (c’est possible aussi de le mettre en java -D lors de l’exécution si on préfère) :
System.setProperty(« sun.awt.exception.handler », « nom.de.la.classe.qui.va.gerer.les.exceptions »);

Et dans ma classe qui va gérer les exceptions, je surcharge juste une méthode :

public void handle(Throwable t) {

}

Et dans ce code, je fais afficher une fenêtre d’erreur avec le message d’erreur et tout. Ca marche impec :stuck_out_tongue:

Voilà voilà, une question qui a finit de m’interroger…