Revue de Presse Xebia

Revue de Presse Xebia
La revue de presse de l’actualité Java/J2EE hebdomadaire proposée par Xebia.

Actualité éditeurs / SSII

Le coin de la technique

Evènements de notre communauté en France et à l’étranger

Actualité éditeurs / SSII

Sortie de RabbitMQ 2.0

La sortie de la version 2.0 du serveur de messaging AMQP RabbitMQ vient d’être annoncée ici.
Cette release majeure vient avec une refonte importante et son lot de nouveautés :

  • Une refonte de la gestion de la persistence permettant une alternative au moteur de persistence embarqué. Il sera possible de développer un support pour une base de données relationelle ou NoSQL ou tout autre data store.
  • Le support de différents protocoles de messaging : AMQP (0-8 et 0-9-1), XMPP, STOMP, HTTP, SMTP… Il sera aussi possible d’ajouter de nouveaux protocoles à l’aide d’extensions.
  • La mise en place d’une infrastructure ayant pour but de simplifier le développement d’outils pour la gestion et le monitoring de RabbitMQ.
  • Une amélioration de la gestion des plugins (il n’est plus nécessaire de compiler les plugins, une version pré-compilée peut être directement chargée par RabbitMQ).

SpringSource qui a récemment racheté RabbitMQ (nous vous en avions parlé ici) propose de son côté un template reprenant les principes des templates jdbc, hibernate, jms ou encore LDAP encapsulant le client Java d’AMQP. Les utilisateurs habitués à JmsTemplate retrouveront donc leurs repères en utilisant l’AmqpTemplate du projet spring AMQP.

SpringSource/VMWare continue donc de faire évoluer son portefeuil de produits avec cette évolution majeure de RabbitMQ, qu’il considère comme une pièce essentielle à sa stratégie de développement du Cloud Computing (voir cet article de Rod Johnson).

Le coin de la technique

Gestion automatique des ressources dans Java7 (ARM)

Java7 suit son petit bonhomme de chemin sans qu’on ait encore une bonne visibilité sur la date de sortie officiel. Le build 105 de la version actuelle propose une nouvelle fonctionnalité très intéressante qui devrait facilité la vie de chaque développeur: l’Automatic Resource Management.

L’idée, proposée par Joshua Bloch depuis quelques années déjà, consiste à améliorer la fermeture des ressources sous la forme d’une clause try-with-resources. Actuellement lorsqu’on manipule une ressource, par exemple un InputStream, la façon la plus propre ressemble à ceci:

public void copyFile(File in, File out) {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
       fis = new FileInputStream(in);
       fos= new FileOutputStream(out);
       // Lire le fichier in et écrire dans le out
       ...
    }  catch (Exception e)  {
       ...    
    } finally {
      close(fis);
      close(fos);
    }
}

private void close(Closeable closeable){
    if(closeable != null) {
        try{
           closeable.close();
        } catch(IOException e){...}
    }
}

Ce code devient vite très verbeux et répetitif car beaucoup de vérifications doivent être faites pour s’assurer de la possibilité de fermer la ressource. La solution pour simplifier cela est de déléguer la fermeture du flux à l’aide du try:

public void copyFile(File file) {
    try(FileInputStream fis=new FileInputStream(in); 
        FileOutputStream fos=new FileOutputStream(out)) {
       // Lire le fichier in et écrire dans le out
       ...
    } 
}

Le code gagne énormément en visibilité, les tests pour savoir si la ressource est null ou si la fermeture s’est bien déroulée sont gérés en interne de la JVM. A noter que pour être fermées les ressources devront étendre une nouvelle interface AutoCloseable.

S’il existe un certain consensus autour de l’utilité de cette évolution, il demeure encore un petit débat sur la partie catch des exceptions. Sans rentrer dans les détails, les exceptions levées pendant la fermeture d’une ressource annihilent celles levées dans le bloc try, ce qui en général est préférable mais du coup peut aussi masquer des exceptions importantes pour l’analyse du problème.

Evènements de notre communauté en France et à l’étranger

Google et les brevets

Semaine chargée pour Google:

  • InfoQ nous annonce que le MPEG LA a décidé de continuer à ne pas réclamer de royalties pour les videos encodées en H.264 et distribuées gratuitement aux utilisateurs. Seules les plateformes vendant des vidéos sont, elles, toujours soumises à taxation. C’est donc une réponse à Google qui avait il y a quelques temps annoncé WebM, un codec vidéo semblable au H.264 mais plus libre et débarrassé des problèmes de brevets et de royalties qui entravent ce dernier. Si InfoQ ne voit pas trop de problèmes à continuer d’utiliser H.264, ce n’est pas du tout l’avis de Mike Shaver ingénieur haut gradé chez Mozilla et qui rappelle que les logiciels permettant de produire ou de consommer ces vidéos devront eux toujours s’acquitter d’une licence. La bataille entre WebM et H.264 ne fait donc que commencer.
  • Ensuite, c’est Paul Allen, le co-fondateur de Microsoft, qui attaque Google et nombre de ses petits camarades (AOL, Yahoo, Facebook) pour avoir enfreint 4 brevets détenus par sa compagnie « Interval Licensing ». Le blog Digits du Wall Street Journal s’est penché sur 2 de ces brevets. Nous vous déconseillons fortement la lecture de son analyse si vous avez réalisé quoi que ce soit dans l’informatique ces 10 dernières années. En effet, vous risquez de ne plus trouver le sommeil en vous rendant compte que vous enfreignez sans doutes, vous aussi, nombre de brevets !
  • Et pour finir sur Google et les brevets, vous vous souvenez des récentes poursuites engagées par Oracle concernant Java et Android. Google vient de riposter, en annoncant qu’aucun de ses employés ne participera à JavaOne cette année. Pour ceux qui voulaient échanger avec les employés de Google, il semble que c’est plus tard dans l’année, à Devoxx, qu’il faille se rendre. Mais rassurez vous, il y aura quand même de l’ambiance à JavaOne: James Gosling, père de Java, demande sur son blog à toute personne y assistant de porter un TShirt arborant le slogan « Java just Free it ». Si cette idée ne risque pas trop de faire bouger Oracle, elle devrait quand même apporter sa dose de fun à l’évènement !

12 Responses

  • Salut,

    Je ne suis pas vraiment d’accord avec votre code pour la fermeture des ressources. Il y a une duplication de la gestion des exceptions, et surtout une obligation de les traiter (si on laisse remonter les exceptions cela peut poser des problèmes sur les fermetures : essayez tout simplement de code la même méthode en laissant remonter les exceptions en cas d’erreur).

    Je lui préfère de loin l’utilisation d’un try/finally par ressources, avec un try/catch indépendant (et optionnel), même si cela s’avère un peu plus verbeux :

    public void readFile(File in, File out) {
    	try {
    		FileInputStream fis = new FileInputStream(in);
    		try {
    			FileOutputStream fos = new FileOutputStream(out);
    			try {
    				// Lire le fichier in et écrire dans le out
    			} finally {
    				fos.close();
    			}
    		} finally {
    			fis.close();
    		}
    	} catch (IOException e) {
    		...
    	}
    }
    

    D’ailleurs c’est en partie la solution utilisée par le try-with-resources…

    Il est vrai qu’avec ce code, si une exception surgit dans le bloc finally on peut « perdre » la trace d’une autre exception remontée dans le bloc try correspondant, et donc perdre une information qui pourrait être importante… mais ce cas se révèle quand même assez rare.

    Toutefois, contrairement à ce qui est dit, ce problème n’existe plus avec le try-with-resources, car il s’occupe de récupérer les exceptions « perdus » et les rajoutent au stacktrace via une nouvelle méthode de Throwable : addSuppressedException().

    a++

  • @adiGuba

    Merci pour ce commentaire et pour les billets que vous avez publiés sur ce sujet sur votre blog.

    A mon sens les, les défaut que vous annoncez me semblent pour la plupart des cas excusables (visibilité des variables, masquage de l’exception d’origine (en fonction de votre code)…). Dans l mesure ou la gestion de ce genre de ressources fait l’objet d’une méthode spécifique, je ne suis pas prêt à sacrifier la lisibilité du code de la sorte. Tout ça pour dire, que j’utilise régulièrement la solution proposée par Guillaume dans cet article. Au final, les deux solutions ne sont pas si éloignées.

    Quoi qu’il en soit, s’il on revient au coeur du sujet, il est clair que la solution jdk7 est nettement plus simple d’utilisation. Pourtant, il faut bien admettre qu’il ne s’agit d’un suc syntaxique … le glas de la fin des problèmes de ressources n’a pas encore sonné :)

    Au plaisir de lire vos prochains articles.

    Erwan (Xebia)

  • @adiGuba

    Merci pour cette précision sur le addSuppressedException(), en effet ça devrait permettre d’avoir une bonne lisibilité sur les causes d’une exception.

    Pour le code Java actuel, je suis partagé, je pense que ça va dépendre des use cases.
    Quoiqu’il en soit cet exemple était surtout pour illustrer le côté verbeux et certainement pas la seule et unique façon de faire.

    (désolé Erwan nos réponses se sont croisés :) )

    (Guillaume de Xebia)

  • @Erwan : Les défauts que j’indique sont excusables dans l’état actuelle, mais le try-with-resources a l’avantage de les corriger ;)

    @Guillaume : En fait ce qui me gêne avec cette solution c’est qu’on est forcé de traiter les exceptions. On ne peut pas les ignorer et les laisser remonter sans modifier grandement le code, à moins d’ignorer les exceptions lors de la fermeture :(

    a++

  • Le point clé c’est le « Throwable.addSuppressedException() » (1).

    A partir du moment où il n’y a plus d’exception masquée, chaque développeur peut choisir la forme qui lui convient.
    Celle du try-with-ressource ou celle des try finally imbriqués.
    C’est une superbe progression de Java.

    Deux remarques :
    1)Les interfaces et classes jdbc du java.sql n’implémentent pas AutoCloseable.
    2)Je préfère la solution try finally qui présente pour moi l’avantage de remonter des exceptions typées. Disons qu’avec qu’en traitant avec un FileOuputStream je suis certain de n’attraper que de(s) IOException. Alors qu’avec le try-with-ressource j’ai l’impression de pouvoir attraper aussi une Exception car c’est ce que lance AutoCloseable. Ai-je bien compris ?

    En reformulant la question, une méthode copyFile utilisant try-with-resource se déclare-t-elle

    public void copyFile(File file) throws IOException

    ou bien

    public void copyFile(File file) throws IOException, Exception

    ?

    (1) : http://download.java.net/jdk7/docs/api/java/lang/Throwable.html#getSuppressedExceptions

  • @Stephane : Les suppressed-exceptions ne seront gérées que par le try-with-resource. Ce dernier génère en fait un try-finally, donc il n’y a plus de raison de l’utiliser…

    Concernant tes deux remarques :

    1) Les interfaces de JDBC devrait implémenter AutoCloseable dans JDBC 4.1 (mais on ne sait toujours pas si ce dernier sera présent dans Java 7 :( )

    Au pire on pourra passer par une méthode utilitaire qui renverrait une instance d’AutoCloseable :

    Connection con = …
    try (Utils.autoCloseable(con)) {
    // …
    }

    2) Chaque classe qui implémente AutoCloseable peut modifier le type de l’exception remontée par la méthode close(), ou même la supprimer. Le try-with-resource s’adapte donc bien à l’exception précise (donc on n’est pas polluer par des throws Exception inutile)

    a++

  • @Stéphane : si ca peut te rassurer, la méthode copyFile() ressemblerait à ceci (testé à l’instant sur le dernier build du jdk7) :

    	public void copyFile(File in, File out) throws IOException {
    		try (FileInputStream fis = new FileInputStream(in);
    		     FileOutputStream fos = new FileOutputStream(out) ) {
            	        // Lire le fichier in et écrire dans le out
    		}
    	}
    

    a++

  • @adiGuba

    « Les suppressed-exceptions ne seront gérées que par le try-with-resource. Ce dernier génère en fait un try-finally, donc il n’y a plus de raison de l’utiliser… »

    J’ai l’impression que le problème a été pris à l’envers.
    Le véritable problème ce sont justement ces exceptions masquées, ce sont elles qui obligent à d’interminables try/catch/finally, contenants de nombreux ifelse, tout cela pour ne pas perdre la première exception, celle qui est à l’origine de toutes les autres et qui contient le message d’erreur qui compte.

    S’ils avaient fait que la JVM gère elle-même getSuppressedExceptions, ou plutôt getMasked() le problème serait définitivement résolut.

    AutoCloseable n’aurait été qu’une facilité de plus, alors que là c’est la finalité.

    De plus, nous obtenons des exceptions qui deviennent Mutable, à l’exception de setStackTrace() qui est limité, elles ne l’étaient pas.

  • @adiGuba

    De tout ce que j’ai vu dans le framework Spring, c’est que les exceptions lors de fermeture de ressource sont systématiquement ignorées et loggées en DEBUG (voir TRACE).

    De mon point de vue je ne vois pas de raison de faire remonter une exception d’un close(). Et en JDK 6 si on perd l’exception d’origine, c’est contre productif.

    Dans le cas de fichier, il suffit de « commiter » l’écriture par un flush() dans le try{} pour ne plus avoir à se soucier du close().

  • Pour ceux qui ne veulent pas attendre Java 7, il y a Lombok (un « annotation processor compiler plugin » pour javac) et son annotation @Cleanup. Petite demo:

    Pur Java:

    import java.io.*;
    
    public class CleanupExample {
      public static void main(String[] args) throws IOException {
        InputStream in = new FileInputStream(args[0]);
        try {
          OutputStream out = new FileOutputStream(args[1]);
          try {
            byte[] b = new byte[10000];
            while (true) {
              int r = in.read(b);
              if (r == -1) break;
              out.write(b, 0, r);
            }
          } finally {
            out.close();
          }
        } finally {
          in.close();
        }
      }
    }
    

    Avec Lombok:

    import lombok.Cleanup;
    import java.io.*;
    
    public class CleanupExample {
      public static void main(String[] args) throws IOException {
        @Cleanup InputStream in = new FileInputStream(args[0]);
        @Cleanup OutputStream out = new FileOutputStream(args[1]);
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      }
    }
    

    Et voilà, sans les mains. Et il y a d’autres petites pépites dans Lombok.

  • @Stephane : Il y a une discussion sur l’intégration de cela automatiquement dans le try/finally, mais cela pose un problème de compatibilité.

    Regarde ce code :

    try {
    	throw new A();
    } finally {
    	throw new B();
    }
    

    Quel est l’exception qui doit être remontée ? Et quel est celle qui doit être ajouté dans les « suppressed-exceptions » ?

    La logique voudrait qu’on remonte A en cachant B, car A est l’exception d’origine, mais on obtient alors un comportement totalement différent de ce qui est fait actuellement (seul B est remontée)
    Il faudrait faire l’inverse pour rester compatible, mais on obtient alors l’inverse de ce qui est souhaité…

    Mais en fait, ce problème se pose principalement sur la gestion des ressources. Je ne vois pas beaucoup de cas où l’on a besoin de traiter des exceptions dans un finally.

    Enfin, les exceptions ne sont pas mutable. La méthode initCause() permet déjà de modifier le stacktrace…

    @Sebastien : La plupart du temps le close() ne génèrera pas d’exception (et du coup les deux solutions ne posent aucun problème).

    Mais lorsqu’on utilise des flux bufférisé ou un peu plus évolué, il est possible que le close() écrivent des données sur le disque. Dans ce cas là ignorer l’exception pourrait poser quelque problème car on ne pourra pas détecter les erreurs d’écriture.

    Imagine un code comme celui-ci qui déplace un fichier (copie puis suppression du fichier d’origine) :

    copyFile(a, b);
    a.delete();
    

    En cas d’erreur lors du close() pour X raison, le fichier peut être corrompu.
    En ignorant l’exception on continuera normalement en supprimant notre fichier source…

    Avec le code de l’article on ne peut pas détecter cela programmatiquement.
    Avec le pattern d’un try/finally par ressource on recevra bien l’exception.

    a++

  • @adiGuba

    Je suis bien d’accord que si le close() modifie l’état de la ressource (flush du buffer) alors une exception dans le close() doit être remontée et le traitement interrompu. D’où ma précision sur l’appel de flush().

Laisser un commentaire