Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Compte-rendu du Paris JUG, part. 2 : Servlets 3.0

Voici le compte-rendu de la seconde séance du Paris JUG du 13 octobre.
Cette fois, il s'agissait de présenter les dernières avancées de la spécification Servlets 3.0, avec au micro Rémi Maucherat (RedHat), responsable de l'implémentation des specs Servlet/JSP et membre de l'Expert Group Servlets 3.0.

Les Servlets 3.0

Il paraît que le descripteur de déploiement web.xml est trop complexe, trop lourd, trop long.

Personnellement, je n'ai jamais rechigné à écrire à la main une déclaration de servlet ou un paramètre d'initialisation, d'autant que les IDE modernes simplifient énormément le boulot - et encore, depuis l'apparition des frameworks de type MVC2, il suffit bien souvent de copier/coller bêtement le fragment de configuration depuis la documentation.
Mais, s'il faut en croire Sun, c'est apparemment encore trop compliqué pour nos amis les développeurs juniors.

La solution ?
Rajouter quelques couches de complexité inutile d'abstraction, pardi. Si possible avec des annotations. C'est bien, les annotations, ça fait moderne.

Les principales nouveautés

Modularisation du web.xml

Puisqu'il est apparemment si compliqué d'écrire et de maintenir un ficher web.xml, le mieux est sans doute de le modulariser - histoire d'avoir à en maintenir plusieurs.

Chaque "web-fragment" possède un identifiant unique (comment le garantir ?) et peut déclarer son lot de servlets, filtres, paramètres... de manière à être réutilisé en l'état dans n'importe quelle application. Les différents fragments découverts dans les jars qui composent une application sont fusionnés dynamiquement par le serveur lors du lancement de l'application, afin de recomposer le traditionnel fichier web.xml.
Idéalement, il suffirait donc de placer le jar d'un framework dans le répertoire "WEB-INF/lib" de l'application pour qu'il soit automatiquement pris en compte et configuré, simplifiant ainsi la vie du développeur.

Mais évidemment, qui dit assemblage dynamique dit gestion des inter-dépendances et des conflits associés. Les filtres, notamment, demandent à être positionnés selon un ordre précis pour fonctionner correctement.
Et c'est là que les problèmes commencent - tous les utilisateurs de Maven et d'OSGi savent de quoi je parle.

Pour gérer tout cela, le descripteur web.xml se dote de nouvelles balises permettant de fixer l'ordre (absolu ou relatif) d'intégration des fragments découverts :

  • <absolute-ordering> pour un ordre absolu
  • <before> et <after> pour un ordre relatif (ex: charger Spring avant Struts)
  • Et dans un cas comme dans l'autre, la balise <other/> permettra d'accepter l'intégration de fragments découverts mais non explicitement listés.

Attendez, il n'était pas question de simplifier le fichier web.xml ?
D'ailleurs, Rémi lui-même avoue que ce système était particulièrement lourd et difficile à implémenter...

Des annotations à tous les étages...

Pour faire moderne, chaque composant traditionnel possède désormais sa propre annotation :

  • @WebServlet déclare une Servlet, avec son mapping et ses paramètres d'initialisation par défaut.
  • @WebFilter déclare un filtre, ainsi que son mapping (sur URL ou servlet)
  • @WebListener pour les listeners

Tous les réglages habituels (notamment les "init-param" et les "url-pattern") peuvent être spécifiés au niveau de ces annotations, mais seront évidemment surchargeables dans le descripteur de déploiement web.xml.

Exemple :

  1. @WebServlet(
  2. name="myservlet",
  3. urlPatterns={"/myservlet", "/simpleservlet"},
  4. initParams={
  5. @WebInitParam(name="param1", value="value1"),
  6. @WebInitParam(name="param2", value="value2")
  7. }
  8. )
  9. public class MyServlet extends HttpServlet
  10. { ... }

Notez que, contrairement à ce que tout le monde attendait, les Servlets ont refusé de faire le grand saut vers le monde des POJOs, sûrement pour des raisons de rétro-compatibilité. Il sera donc toujours nécessaire d'implémenter des classes ou interfaces spécifiques (HttpServlet, Filter, les différents Listeners...).
A mon avis, c'est un mauvais point supplémentaire face à Spring MVC ou Stripes ; après tout, on est bientôt en 2010, cela fait plus de 4 ans que les annotations sont en standard dans JavaEE... A un moment il faut savoir forcer un peu la main aux retardataires !

... mais aussi une API

Une nouvelle API fait également son apparition, permettant de réaliser programmatiquement toutes les déclarations jusqu'ici réalisées par le biais du fichier web.xml.

Seul problème : un serveur ne peut charger une application que si elle est totalement configurée et initialisée ; il faut donc que le code utilisant la nouvelle API soit exécuté avant que l'application ne soit chargée.
Pour cela, un nouveau point d'extension ("hook") a été créé, qui s'appuie sur le Service Provider Interface : "javax.servlet.ServletContainerInitializer". Tout code déclaré par ce biais est exécuté lors du chargement de l'application, et peut donc interagir avec elle

L'API permet notamment d'ajouter des composants (Servlets, Filtres...) et d'accéder à la liste des composants déjà déclarés :

  1. addServlet(String servletName)
  2. addServlet(String className)
  3. addServlet(Class<?> servletClass)
  4. addServlet(Servlet servlet)
  5. setLoadOnStartup(boolean loadOnStartup)
  6. setServletSecurity, setMultipartConfig...

Très clairement, cette fonctionnalité (dont la spécification date d'à peine une semaine !) s'adresse davantage aux concepteurs de frameworks qu'aux simples développeurs d'applications. Elle permettra par exemple aux frameworks de s'auto-configurer dynamiquement en fonction de leur environnement d'exécution (présence d'autres frameworks, version des jars, type du serveur...).

Fonctionnalités asynchrones

Le problème du cycle requête/réponse habituel, c'est que le thread de communication avec le client est maintenu ouvert pendant tout le temps de traitement de sa requête. Et ça peut être très long, jusqu'à plusieurs secondes si une base de données est appelée - une éternité en temps machine.

La nouvelle spécification introduit un nouveau type de Servlet à traitement asynchrone. Sa méthode startAsync() renvoie un AsyncContext qui représente la requête, mais détachée de la couche de transport, ce qui lui permet par exemple de réinvoquer la requête depuis le serveur même, de changer sa servlet cible, etc. Revers de la médaille, il est maintenant nécessaire de clore explicitement la requête à la fin du traitement métier et de libérer les ressources correspondantes.

L'AsyncContext possède une riche API : dispatch(), complete(), start(Runnable) qui permet de lancer le Runnable devant exécuter le traitement métier, addListener(AsyncListener)...
Et bien sûr, qui dit "asynchrone" dit gestion du timeout, via la méthode setTimeout(long) et les AsyncListeners, qui proposent les méthodes onComplete(), onTimeout(), onError() et onStartAsync().

Rémi déconseille vivement d'utiliser cette nouvelle API à cause de son modèle bien trop complexe pour la majorité des applications. D'autant que, pour que le mode asynchrone fonctionne, il faut que toute la chaîne de traitement soit effectivement compatible (filtres, servlets...). La promesse de meilleure scalabilité semble donc traîner un sacré boulet à son pied...

Et pour le même prix, vous recevrez également...

La gestion des ressources packagées

Il est maintenant possible de servir des ressources statiques directement depuis des jars (images, etc.), sans avoir besoin de les décompresser préalablement à la racine de l'application. Au runtime, les répertoires META-INF/resources des jars sont virtuellement fusionnés et déployés au même niveau que les autres ressources de l'application. Aucune information ne nous a été donnée quant à la résolution des conflits dans le cas où des ressources identiques seraient livrées par plusieurs jars...
Cela devrait permettre d'embarquer des applications complètes dans les jars, par exemple toute l'interface d'administration d'un framework.

La gestion des requêtes composites

Enfin une bonne nouvelle réellement utile : les servlets sont désormais capables de gérer nativement les requêtes de type "mime/multipart", qui permettent par exemple d'uploader des fichiers depuis des formulaires.

Une fois l'annotation @MultipartConfig positionnée sur la servlet, le développeur a accès à une API similaire à celle du célèbre Commons FileUpload :

  1. Collection<Part> parts = request.getParts()
  2. for (Part part : parts) {
  3. part.getName();
  4. part.getSize();
  5. part.getInputStream();
  6. }
Sécurité

Autres fonctionnalités potentiellement utiles : la gestion de la sécurité au niveau des servlets et l'authentification programmatique.

Jusqu'à présent, les contraintes de sécurité étaient définies de manière centralisée dans le fichier web.xml, et portaient généralement sur des URLs. Il y avait donc un grand risque de désynchronisation en cas de refactoring ou de modification des mappings des servlets.
Désormais, ces contraintes peuvent être directement exprimées au niveau des servlets grâce à l'annotation @ServletSecurity :

  1. @ServletSecurity(
  2. @HttpConstraint(rolesAllowed={"user","guest"}),
  3. httpMethodConstraints=@httpMethodConstraint("POST",rolesAllowed={"admin})
  4. )
  5. public class MyServlet extends HttpServlet
  6. { ... }

De plus, il est maintenant possible de déclencher programmatiquement l'authentification de l'utilisateur en s'appuyant sur le mécanisme déclaré au niveau du serveur : HTTP basic, SSL... Pour cela, la clase ServletRequest propose de nouvelles méthodes : authenticate(), login(user,pass), logout()...

Sessions

Et puisque l'on est dans l'authentification, voyons les améliorations apportées aux Sessions.
Une seule en fait, mais intéressante : <session-config> possède maintenant une sous-balise <tracking-mode> pour préciser si l'on préfère l'identification par cookie, par url-rewriting, par SSL ID (en cours d'implémentation), ou une combinaison de ces méthodes. Il est également possible de définir le nom, l'hôte et le chemin du cookie d'identification de session, qui ne s'appellera donc plus forcément jsessionid (même s'il est fortement conseillé de lui conserver ce nom, pour des raisons évidentes de rétro-compatibilité).

Indépendance du conteneur JSP

Et pour finir, il devrait être possible de sélectionner le conteneur JSP de son choix, par exemple pour utiliser celui de Tomcat sur un serveur JBoss.
Dans la pratique, il sera tout de même plus prudent de se limiter au conteneur JSP livré en standard avec votre serveur...

Conclusion

En conclusion, cette nouvelle version majeure de la spécification Servlet corrige certains manques évidents des versions précédentes (gestion des requêtes multipart, configuration de la sécurité au niveau des servlets mêmes, amélioration du système d'authentification) et explore nombre de nouvelles pistes (traitements asynchrones, modularisation).
Au final, elle semble davantage s'adresser aux concepteurs de frameworks (joueront-ils le jeu ?) qu'aux simples développeurs d'applications.

Personnellement, je suis très perplexe et déçu.
La spécification Servlets 2.4 était l'une des plus lisible de tout le répertoire JavaEE ; cette ère semble révolue.

Le prétexte initial de la simplification du fichier web.xml semble au final bien dérisoire, si l'on considère l'usine à gaz mise en place pour y répondre. La gestion des web-fragments promet d'être problématique, et les implications en termes de sécurité sont encore floues.
De même, la plupart des bonnes idées ont été mal implémentées : par exemple, le fait que les Servlets ne soient toujours pas des POJO, malgré les annotations.

Au final, je n'ai donc qu'un horrible sentiment de gâchis ; mais l'avenir me donnera peut-être tort, la spécification étant toujours en cours de rédaction. Une prise de conscience tardive est toujours possible après tout.



jee6.jpg
"Alors Java EE 6, c'est pas compliqué. Regardez, vous êtes ici !"




Commentaires

1. Le dimanche 18 octobre 2009, 20:17 par zepag

Je serais moins sévère que toi sur les annotations, car comme tu le disais, je ne pense pas qu'il y ait encore grand monde qui utilise directement des servlets, à part celles des frameworks (qui ont tendance à être remplacées par des filtres ou listeners).
Pour ce qui est des web-fragments, même impression que toi, en revanche je trouve le hook d'initialisation intéressant à de nombreux titres, à la fois pour les constructeurs de frameworks, mais aussi peut-être pour vérifier des conditions de démarrage (dépendance sur des ressources externes par exemple).
le META-INF/resources est assez intéressant car si j'ai bien compris il permettrait par exemple de packager jQuery ou un autre framework javascript, et d'enfin gérer les versions de ces frameworks dans un repository structuré (comme un repository Maven par exemple).
Il est dommage que le hook précédemment cité n'ait pas donné (mais peut-être est-ce le cas) la possibilité d'intégralement supplanter le fichier web.xml et de le remplacer par autre chose (un web.xml avec plus d'attributs et moins d'éléments par exemple, ou un descripteur en ruby/groovy/javascript/perl/scala/finlandais ;) ).

Il y a beaucoup de bonnes idées quand même là dedans... et la version 3.1 sera sans doute bien meilleure ;) (marrant j'ai l'impression d'avoir déjà dit ça sur une autre spec :) ).

P.A.

2. Le lundi 19 octobre 2009, 09:22 par Benoît

J'ai un avis contraire à toi sur l'utilité et la pertinence des nouveautés.

En ce qui concerne le traitement des multiparts en natifs, je ne vois pas à quoi ça sert de l'avoir dans le conteneur par rapport à mettre une librairie qui s'en charge (au hasard commons-fileupload). En plus je ne trouve pas sain que le conteneur connaisse à ce point des normes protocolaires qui ne sont pas du niveau du conteneur.

En ce qui concerne les web-fragments, pour moi cela résout un paint point gigantesque, et non, contrairement à ce que tu dis, leur introduction n'a pas vocation à simplifier la déclaration des servlets, mais à permettre une plus grande modularité. Effectivement si tu as à maintenir deux servlets, cela n'a aucun sens de mettre l'une dans web.xml, et l'autre dans un fragment, mais si tu as une servlet définie, par exemple dans un framework, et une autre dans un plugin, développé et maintenu par un autre acteur (prestataire, éditeur), c'est vraiment une bonne chose que les maintenances puissent se faire de manière séparées. Maintenant rien ne t'empêche de continuer à tout mettre dans web.xml si le coeur t'en dit : c'est un choix d'architecture logiciel, et c'est bien que la norme permette enfin ce choix.

Une petite note également, en ce qui concerne les traitements asynchrones. Rémi ne déconseillait, si j'ai bien compris que l'usage très spécifique à partir d'une implémentation de Runnable, pas l'API dans son ensemble.

3. Le lundi 19 octobre 2009, 14:26 par Remy Maucherat

Quelques précisions:

  • La complexité est claire pour l'implémenteur du container (c'est moi, là ... en même temps, ca ne doit pas être impossible non plus, vu que je n'ai pas de retard par rapport au processus de spécification), pour le développeur de framework ca semble un peu mieux qu'avant (installation plus facile, donc moins de support "de base" en principe), et pour l'utilisateur final c'est bien mieux. Pour moi l'intéret de la modularité de web.xml avec les fragments est évident. Par contre, certaines fonctionnalités se recouvrent partiellement (mais ca l'utilisateur s'en fiche un peu).
  • Pour les asyncs, j'ai conseillé de ne pas l'utiliser pour des applications qui n'en tireraient pas un bénéfice important (c'est à dire avec de longues périodes d'attente au cours du traitement). Cela augmente la complexité, donc il vaut mieux préférer le modèle simple sauf s'il limite trop la scalabilité. Au passage, pour aller plus loin, JBoss Web propose (depuis JBoss AS 5) une API asynchrone avec de l'IO non bloquante, pour une scalabilité optimale pour les cas les plus complexes.
  • Je ne suis pas convaincu par un modèle POJO pour cette spécification. Il y aura un besoin de faire tourner des Servlets existants pendant longtemps, et sur ce point Servlet 3.0 améliore le modèle pour l'utilisateur. Après, je n'ai rien contre une nouvelle spécification en parallèle.
  • Les annotations ne servent pas à grand chose, effectivement. Encore que, un collègue vient de me dire qu'il aimerait bien les avoir pour gérer sa petite webapp avec ses Servlets de tests. Effectivement.
  • Pour multipart, c'est disponible en standard, c'est un peu plus pratique, mais cela ne me semble pas une avancée très spectaculaire. Il s'agit juste d'inclure un fileupload en renommant les classes.

Rémy

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.