Premiers pas avec JSF

Ce contenu est obsolète. Il peut contenir des informations intéressantes mais soyez prudent avec celles-ci.

Dans cette introduction au framework JSF, nous allons découvrir en quoi il consiste dans les grandes lignes, étudier ses composants de base, puis mettre en place un exemple très simple et enfin le comparer à son équivalent réalisé avec la technologie JSP.

Qu'est-ce que JSF ?

Présentation

Nous l'avons évoqué dans le chapitre précédent, JSF (JavaServer Faces) est un framework MVC basé sur les composants. Il est construit sur l'API Servlet et fournit des composants sous forme de bibliothèques de balises ressemblant très fortement à la JSTL. Celles-ci peuvent être utilisées dans des pages JSP comme dans toute autre technologie de vue basée sur le Java, car le framework JSF ne limite pas le développeur à une technologie particulière pour la vue. Cependant, il existe une technologie relativement récente baptisée la Facelet, qui fait partie du standard et qu'il est recommandé d'utiliser dès lors que l'on travaille avec JSF, celle-ci étant bien plus adaptée que les pages JSP. Ces dernières sont d'ailleurs considérées comme une technologie de présentation dépréciée pour JSF depuis la sortie de Java EE 6 en 2009.

Hormis les avantages inhérents à tout framework MVC basé sur les composants, que nous avons listés dans le chapitre précédent, JSF offre notamment de grandes capacités de création de templates (ou gabarits), telles que les composants composites.

Qu'est-ce que la création de templates ?

Il s'agit tout simplement de la possibilité de découper une page en plusieurs composants indépendants, assemblés ensuite pour former une page finale. À cet égard, la technologie JSP fournit seulement le tag <jsp:include/>, que nous avons déjà rencontré dans notre TP pour une tâche très simple, à savoir inclure automatiquement un menu sur chacune de nos pages.

Pour rester concis, JSF permet comme la plupart des frameworks MVC de gérer les événements et la validation des données saisies par l'utilisateur, de contrôler la navigation entre les pages, de créer des vues accessibles, de faciliter l'internationalisation des pages, etc. En outre, puisqu'il est basé sur les composants et non sur les requêtes, il permet de gérer un état pour une vue donnée. Pas de panique, nous allons revenir sur ces aspects importants en temps voulu et par la pratique.

Principe

JSF est un framework MVC et propose, en guise de contrôleur unique du cycle de vie du traitement des requêtes, la FacesServlet.

Un contrôleur unique ?

Eh oui vous avez bien lu, il n'y a qu'une seule servlet chargée d'aiguiller l'intégralité des requêtes entrantes vers les bons composants, et ce pour l'application tout entière ! Cette architecture porte un nom, il s'agit du pattern Front Controller, qui lui-même est une spécialisation du pattern Médiateur.

JSF vous évite d'avoir à écrire le code - standard, passe-partout et pénible - responsable du regroupement des saisies utilisateurs (paramètres de requêtes HTTP), de leur conversion & validation, de la mise à jour des données du modèle, de l'invocation d'actions métiers et de la génération de la réponse. Ainsi vous vous retrouvez uniquement avec une page JSP ou une page XHTML (une Facelet) en guise de vue, et un JavaBean en tant que modèle. Cela accélère le développement de manière significative ! Les composants JSF sont utilisés pour lier la vue avec le modèle, et la FacesServlet utilise l'arbre des composants JSF pour effectuer tout le travail.

Tout cela semble bien compliqué, mais en réalité si vous devez n'en retenir qu'une chose, c'est celle-ci : avec JSF, vous n'avez plus besoin d'écrire de servlets ! Ne soyez pas inquiets face à toutes ces nouveautés, nous allons y revenir calmement et en détail un peu plus loin dans ce chapitre.

Historique

Avant de passer à la suite, penchons-nous un instant sur le passé de la solution. Certains d'entre vous le savent peut-être déjà, JSF jouit d'une certaine réputation auprès des développeurs… une mauvaise réputation ! Pourtant, si l'on y regarde d'un peu plus près, on se rend très vite compte que la plupart des reproches faits à JSF sont sans fondements.

Afin de casser cette mauvaise réputation, hormis lorsqu'il est question de la relative difficulté d'apprentissage quand on ne dispose pas de solides connaissances en Java EE (servlets notamment), nous allons faire un rapide état des lieux de l'évolution du framework, de sa naissance à aujourd'hui. Bien qu'actuellement dans sa version 2.1, il a toujours besoin de se débarrasser de l'image négative qu'il dégageait lorsqu'il n'était pas encore mature. Et c'est vrai que si l'on se penche sur son histoire, on relève des inconvénients majeurs…

Cet historique contient certaines informations et certains détails qui vous échapperont certainement à la première lecture, si vous n'avez jamais travaillé avec JSF par le passé. Ne vous y attardez pas pour le moment, tâchez simplement de saisir l'évolution générale du framework. Si vous rencontrez un jour un projet basé sur une ancienne version de JSF, vous trouverez ici les différences majeures avec la version actuelle et les inconvénients importants.

JSF 1.0 (mars 2004)

Ce fut la version initiale. Bien qu'estampillée 1.0, elle était pleine de bugs à la fois au niveau des performances et du noyau, vous n'imaginez pas. Les applications ne fonctionnaient pas toujours comme attendu. En tant que développeur, vous auriez fui cette solution en hurlant !

JSF 1.1 (mai 2004)

Il s'agissait ici d'une mise à jour destinée à corriger les bugs de la version initiale… Et les performances n'étaient toujours pas au rendez-vous. En outre, énorme inconvénient : il était impossible d'insérer du HTML dans une page JSF sans défauts ! Étrange décision dans un framework destiné à la création d'applications web que de laisser de côté les designers web… Pour faire court, l'intégralité du code HTML brut était rendue avant les balises JSF ! Il fallait alors entourer chaque portion de code HTML par une balise JSF (<f:verbatim>) pour qu'elle soit incluse correctement dans l'arbre des composants (l'enchaînement des balises JSF) et rendue proprement. Bien que cela respecte les spécifications écrites à l'époque, cela a valu au framework un flot de critiques ! Imaginez vous devoir entourer chaque balise HTML intervenant dans vos pages JSP par une autre balise, juste pour qu'elle soit correctement prise en compte… Bref, c'était une vraie horreur.

JSF 1.2 (mai 2006)

Ce fut la première version préparée par la nouvelle équipe de développement de JSF, menée par Ryan Lubke. L'équipe a fourni un travail énorme, et les spécifications ont beaucoup évolué. L'amélioration de la gestion de la vue a été le principal axe de changement. Elle n'a pas seulement rendu la vue JSF indépendante des JSP, mais elle a également permis aux développeurs d'insérer du HTML dans une page JSF sans avoir à dupliquer sans cesse ces satanées balises <f:verbatim> nécessaires dans les versions précédentes. Un autre centre d'intérêt de l'équipe à l'époque fut l'amélioration des performances. Presque chaque version corrective mineure était alors accompagnée d'améliorations notables des performances globales du framework.

Le seul réel inconvénient commun à toutes les versions JSF 1.x (1.2 inclus) était l'absence d'une portée se plaçant entre la requête et la session (celle qui est souvent nommée "scope de conversation"). Cela forçait les développeurs à jouer avec des champs de formulaires cachés, des requêtes sur la BDD non nécessaires et/ou à abuser des sessions à chaque fois que quelqu'un voulait retenir le modèle de données initial pour les requêtes suivantes afin de procéder aux validations/conversions/mises à jour du modèle et aux invocations d'actions dans des applications web plus complexes. En somme, le développeur devait faire comme nous lorsque nous travaillons à la main sans framework. Il n'avait aucun moyen standard pour donner un état à une vue en particulier. Ce problème pouvait à l'époque être partiellement évité en utilisant une bibliothèque tierce qui sauvegardait l'état des données nécessaires pour les requêtes suivantes, notamment la balise <t:saveState> de la bibliothèque MyFaces Tomahawk, ou encore le framework de conversation MyFaces Orchestra.

Un autre point très pénalisant pour les webdesigners était le fait que JSF utilisait le caractère : en tant que séparateur d'identifiant afin d'assurer l'unicité des id des éléments HTML générés lors du rendu des balises, notamment lorsqu'un composant était utilisé plus d'une fois dans la vue (création de templates, boucles sur les composants, etc.). Si vous connaissez un petit peu CSS, vous devez savoir que ce caractère n'est pas autorisé dans les identifiants CSS, et les webdesigners devaient alors utiliser le caractère \ pour échapper les : dans les sélecteurs CSS qu'ils mettaient en place, ce qui produisait des sélecteurs étranges et sales tels que #formulaireId\:champId { ... } dans les feuilles CSS.

En outre, JSF 1.x n'était livré avec aucune fonctionnalité Ajax prête à l'emploi. Ce n'était pas vraiment un inconvénient technique, mais l'explosion du web 2.0 en a fait un inconvénient fonctionnel. Exadel introduisait alors rapidement la bibliothèque Ajax4jsf, qui a été activement développée durant les années suivantes puis intégrée au noyau de la bibliothèque de composants JBoss RichFaces. D'autres bibliothèques offrent depuis des composants ajaxisés, IceFaces étant probablement la plus connue.

Quand JSF 1.2 a atteint la moitié de sa vie, une nouvelle technologie pour la vue basée sur le XML a été introduite : les Facelets. Cela a offert d'énormes avantages sur les JSP, particulièrement pour la création de templates et pour les performances.

JSF 2.0 (juin 2009)

Ce fut la seconde version majeure. Il y eut énormément de changements à la fois techniques et fonctionnels. La technologie JSP a été remplacée par les Facelets en tant que technologie de vue par défaut, et aux Facelets ont été greffées des fonctionnalités permettant la création de composants purement XML (appelés composants composites). Ajax a été introduit notamment, via un composant qui montre des similarités avec Ajax4jsf. Les annotations et améliorations favorisant le concept convention-plutôt-que-configuration ont été introduites pour éliminer les volumineux et verbeux fichiers de configuration autant que possible. En outre, le caractère séparateur d'identifiant : est devenu configurable.

Dernière évolution, mais non des moindres, une nouvelle portée a été introduite : le scope view, se plaçant entre la requête et la session. Cela a pallié un autre inconvénient majeur de JSF 1.x, comme nous l'avons vu précédemment. Nous allons y revenir en détail dans un prochain exemple pratique.

Jusque-là, tout paraît idyllique, et cette version estampillée 2.0 semble salvatrice. Cependant, bien que la plupart des inconvénients de JSF 1.x ont disparu avec cette version, il subsiste des bugs spécifiques à JSF 2.0 qui peuvent être un facteur bloquant dans le développement d'une application. Si le cœur vous en dit, vous pouvez jeter un œil aux bugs JSF 2.x en cours pour vous faire une idée, mais ils concernent une utilisation avancée du framework, et la plupart de ces bugs sont de toute manière contournables.

Enfin pour terminer sur une note plus gaie, c'est avec JSF 2.0 que sont apparues de nouvelles bibliothèques de composants plus graphiques. On peut notamment citer l'impressionnant PrimeFaces et OpenFaces.

MVC basé sur les composants vs MVC basé sur les requêtes

Certains avancent que le plus gros inconvénient de JSF est qu'il n'autorise que peu de contrôle sur le code HTML/CSS/JS généré. Cela ne tient en réalité pas à JSF en lui-même, mais simplement au fait que c'est un framework MVC basé sur les composants, et pas sur les requêtes (actions). Si vous êtes perdus, je vous renvoie au chapitre précédent pour les définitions.

En réalité, c'est très simple : si un haut niveau de contrôle sur le rendu HTML/CSS/JS est votre principale exigence lorsque vous choisissez un framework MVC, alors vous devez regarder du côté des frameworks basés sur les requêtes comme Spring MVC.

Après ce long aparté sur l'histoire de JSF, dont vous pouvez trouver la version originale anglaise sur cette page, résumons la situation en quelques lignes :

  • JSF 2.0 est un framework web MVC qui se focalise sur la simplification de la construction d'interfaces utilisateur, autrement dit la vue. Il propose nativement plus d'une centaine de balises à cet égard, et facilite la réutilisation des composants d'une interface utilisateur à l'autre ;
  • avec JSF 1, tout devait être déclaré dans un fichier de configuration, et le développeur devait donc maintenir un énième fichier XML… Avec JSF 2, c'en est terminé de ces fichiers (inter)minables : les annotations remplacent les fichiers externes, et le développement s'en retrouve donc grandement accéléré et simplifié !
  • dans ce cours, je ne ferai dorénavant plus jamais la distinction entre JSF 1 et JSF 2, tout bonnement parce que JSF aujourd'hui, c'est JSF 2 et rien d'autre. Dans votre tête, cela doit être tout aussi clair : JSF 1 est de l'histoire ancienne, et je n'y ferai allusion que ponctuellement pour vous informer des différences importantes avec la version actuelle, afin que vous ne soyez pas trop surpris si jamais vous devez un jour travailler sur une application développée avec JSF 1 (ce que je ne vous souhaite bien évidemment pas !).

Les ressources périmées concernant JSF sont légion sur le web ! Énormément de cours, tutos, sujets de forums et documentations traitent de JSF 1, alors que JSF 2 a changé énormément de choses. Faites bien attention aux liens que vous parcourez, et basez-vous sur les dates clés de l'histoire de JSF pour déterminer si les informations que vous lisez sont d'actualité ou non. En règle générale, vous devez donc vous méfier de tout ce qui a été publié avant 2009 !

Structure d'une application JSF

Étudions brièvement comment se construit une application basée sur JSF. Cela ne devrait pas bouleverser vos habitudes, la structure globale est très similaire à celle d'une application web Java EE MVC traditionnelle :

  • la vue est généralement assurée par des pages JSP ou par des pages XHTML (on parle alors de Facelets) ;
  • le modèle est assuré par des entités ou des JavaBeans ;
  • le contrôleur, auparavant incarné par nos servlets, est dorénavant décomposé en deux éléments :
    • une unique servlet mère servant de point d'entrée à toute requête, la FacesServlet ;
    • un JavaBean particulier, déclaré via une annotation et désigné par le terme managed-bean.
  • le tout est mis en musique par des fichiers de configuration : le classique web.xml, mais également un nouveau fichier nommé faces-config.xml.

Nous observons que la différence majeure qui ressort jusqu'à présent est l'absence de servlets spécifiques à chaque page, comme nous devions en écrire dans nos applications MVC faites maison.

Facelets et composants

La page JSP mise au placard ?

Comme je vous l'ai annoncé à plusieurs reprises déjà, la page JSP n'est plus la technologie de référence pour la création de vues dans une application Java EE. Avec JSF, ce sont maintenant de simples pages XHTML qui sont utilisées, pages que l'on nomme des Facelets. Partant de ce constat, si vous avez bien suivi, vous êtes en droit de vous poser la question suivante :

Nous avons déjà découvert que les servlets n'allaient plus être nécessaires dans notre application, voilà maintenant que les JSP non plus ne vont plus nous servir… Pourquoi avoir passé des chapitres entiers à apprendre à créer et manier des servlets et des JSP, si c'est pour les laisser tomber ensuite ?

Premièrement, ces technologies sont loin d'être à la retraite, car comme nous l'avons déjà abordé les frameworks MVC ne sont pas utilisés partout. En outre, il existe dans le monde de l'entreprise énormément d'applications basées sur les servlets et les JSP, applications qui ne sont pas prêtes d'être migrées ni réécrites vers un framework MVC, bien souvent pour des raisons de coût. Plus important encore, il existe énormément d'applications basées sur des frameworks MVC différents de JSF, notamment le très populaire Spring MVC, qui utilisent encore massivement les pages JSP en guise de vue.

Deuxièmement, l'apprentissage des frameworks et de leur fonctionnement est bien plus facile lorsque vous disposez déjà des bases. Bien entendu, dans l'absolu il est possible d'attaquer directement par les solutions de haut niveau - c'est-à-dire celles qui masquent les technologies de base comme les servlets, les JSP ou encore JDBC - mais tôt ou tard vous devrez regarder sous la couverture… Et quand ce jour arrivera, vous serez alors bien plus aptes à comprendre les rouages de toute cette machinerie en connaissant les fondements que si vous découvriez le tout pour la première fois !

Enfin, dans les cas d'applications de faible envergure, d'applications nécessitant d'excellentes performances et d'applications devant être aisément déployables à très large échelle, il est encore relativement courant de ne pas utiliser de frameworks. Bref, rassurez-vous, vous n'avez pas perdu votre temps en arrivant jusqu'ici ! ;)

Structure et syntaxe

Nous allons aborder la structure d'une Facelet en la comparant avec la structure d'une technologie que nous connaissons déjà : la page JSP.

Généralités

La différence la plus importante se situe au niveau du contenu. Alors qu'une page JSP est un objet complexe, pouvant contenir à la fois des balises JSP ou JSTL et du code Java par l'intermédiaire de scriptlets, une Facelet est un fichier XML pur ! Elle ne peut par conséquent contenir que des balises, et il est impossible d'y inclure des scriplets : le Java est donc définitivement banni de la vue avec cette technologie.

Au niveau du format et du rendu, la situation n'a pas changé : tout comme les servlets et les pages JSP sont le plus souvent utilisées pour générer des pages HTML, c'est le langage XHTML qui est le plus utilisé pour créer des Facelets (XHTML étant une version de la syntaxe HTML conforme au standard XML). Toutefois, sachez que le framework JSF est markup-agnostique : cela signifie qu'il peut s'adapter à n'importe quel langage, à partir du moment où ce langage respecte la structure décrite par le standard XML. Ainsi, il est également possible de construire une Facelet avec du langage XML pur, par exemple pour créer un flux RSS, ou avec le langage XUL pour n'en citer qu'un.

Au niveau de l'aspect extérieur du fichier, une Facelet porte en général une de ces trois extensions : .jsf, .xhtml ou .faces. Il n'existe pas une unique extension pour la simple raison que nous venons de découvrir, à savoir le fait qu'une Facelet n'est rien d'autre qu'un document XML. Dans l'absolu, c'est par défaut l'extension .xml qui pourrait être utilisée… Cela dit, il est utile de pouvoir reconnaître facilement une Facelet parmi d'autres fichiers du même type, pour la démarquer du reste. Voilà pourquoi ces trois extensions ont de facto été retenues par les développeurs. Ne vous inquiétez pas, nous découvrirons très bientôt comment le framework est capable de savoir quelles extensions de fichiers lui sont destinées.

Ainsi, voici la structure à vide d'une Facelet :

1
2
3
4
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"> 
    ...
</html>

exemple.jsf

Vous retrouvez bien le doctype HTML, suivi de la balise <html> conforme au standard XHTML. Bref, en apparence ce n'est qu'une page web toute simple !

Bibliothèques et balises

Une Facelet est donc constituée de balises, dont la syntaxe est très similaire à celles des balises JSTL utilisées dans une page JSP.

Avant de poursuivre sur la forme des balises JSF, intéressons-nous aux bibliothèques. Tout comme il existe des directives permettant d'importer des bibliothèques de balises JSTL dans une page JSP, il existe un moyen pour inclure les bibliothèques de balises JSF dans une Facelet. Toutefois, il vous faut oublier le concept même de la directive JSP : il s'agissait là littéralement d'un ordre donné au conteneur, lui précisant comment il devait gérer une page JSP et générer sa servlet Java associée. Voilà pourquoi il était non seulement possible via une directive d'inclure des bibliothèques, mais également d'importer des classes Java, d'activer ou non les sessions HTTP, les expressions EL, etc.

Dans une Facelet, une bibliothèque de balises est incluse via l'ajout d'un attribut xmlns à la balise <html> qui ouvre le corps de la page. Il s'agit là d'un namespace XML. Je vous invite à vous renseigner sur cette notion si vous souhaitez comprendre comment cela fonctionne en détail. En ce qui nous concerne, nous allons simplement retenir qu'un tel attribut permet de déclarer une bibliothèque dans une Facelet ! Le framework JSF intègre nativement trois bibliothèques standard : HTML, Core et UI. Voici comment les inclure dans une Facelet :

1
2
3
4
5
6
7
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 
       xmlns:f="http://java.sun.com/jsf/core" 
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:ui="http://java.sun.com/jsf/facelets"> 
    ...
</html>

exemple.jsf

Vous reconnaissez ici le système de liens utilisé également par la JSTL. De même, vous retrouvez le système de préfixe identifiant une bibliothèque : traditionnellement, h: désigne la bibliothèque HTML, ui: la bibliothèque de templating et de composition, et f: la bibliothèque Core. Au passage, ne confondez pas cette dernière avec la bibliothèque Core de la JSTL, nous parlons bien ici de balises JSF !

Pour terminer et revenir sur les balises JSF à proprement parler, sur la forme elles ressemblent comme deux gouttes d'eau aux balises JSTL. Par exemple, voici une balise issue de la bibliothèque HTML :

1
<h:outputLabel for="confirmation">Confirmation du mot de passe <span class="requis">*</span></h:outputLabel>

Exemple de balise JSF

Vous pouvez d'ores et déjà remarquer les similitudes avec les balises JSTL : il est possible d'y préciser des attributs, une balise possède un corps, il est possible d'y inclure d'autres balises, etc.

Dans nos exemples à venir, nous allons utiliser de nombreuses balises qui vous sont encore inconnues. Si j'ai pris le temps de vous décrire les balises JSP et JSTL les plus couramment utilisées lors de nos premiers pas avec Java EE, je ne vais cette fois pas rédiger d'inventaire des balises JSF, et ce pour trois raisons :

  1. vous êtes à l'aise avec le concept de balises, vous savez comment elles se construisent ;
  2. vous êtes à l'aise avec la recherche d'informations, vous êtes capables de trouver et parcourir les ressources et documentations par vous-mêmes ;
  3. les bibliothèques standard de JSF contiennent beaucoup de balises, certaines dédiées à des tâches bien différentes de ce que vous connaissez et avez manipulé jusqu'à présent, et en faire l'inventaire détaillé et expliqué par l'exemple serait chronophage et en fin de compte peu efficace.

Pour vous faciliter la tâche, je vous communique quelques ressources extrêmement utiles : la documentation officielle des balises JSF, par Oracle ; une documentation similaire, qui date un peu mais qui est présentée de manière un peu plus conviviale que le format Javadoc, par JSFToolbox ; la page JSF de Stackoverflow, contenant des généralités, des exemples basiques et une liste de liens fournie et à jour sur le sujet.

Gardez ces liens accessibles dans les favoris de votre navigateur, vous allez en avoir besoin pour découvrir toutes les balises proposées par JSF !

Expressions EL

Une Facelet peut, tout comme une page JSP, contenir des expressions EL. Toutefois, celles-ci sont un peu différentes de celles que nous avons utilisées jusqu'à présent. Voyez dans cet exemple la forme d'une expression EL avec JSF :

1
#{inscrireBean.utilisateur.motDePasse}

Exemple d'expression EL avec JSF

Vous allez me dire, à part le symbole # qui remplace le symbole $ auparavant utilisé, a priori le reste ne change pas : les accolades, l'accès aux beans, à ses propriétés, l'opérateur . en guise de séparateur… Eh bien en réalité, c'est un peu plus compliqué que ça. Pour mieux comprendre, faisons un rapide historique de la technologie EL :

  • en juin 2002, la première version de la JSTL paraît et introduit le concept des expressions EL pour la toute première fois. Celles-ci étaient alors construites pour appeler les méthodes getter de JavaBeans, uniquement via la syntaxe ${...} et uniquement depuis les balises JSTL ;
  • en novembre 2003, la seconde mouture de la technologie JSP fait son apparition et les expressions EL deviennent partie intégrante du standard J2EE 1.4. La JSTL en version 1.1 ne contient alors plus la technologie EL, et la syntaxe ${...} fonctionne désormais en dehors des balises JSTL, dans le corps des pages JSP ;
  • en mars 2004, la première version de JSF est publiée, et introduit le concept des expressions EL dites "différées". Il s'agissait alors d'expressions sous la forme #{...} et ne fonctionnant que dans des balises JSF. La principale différence avec les expressions de la forme ${...} décrites par le standard JSP est qu'elles permettent non seulement d'appeler les méthodes getter des beans, mais également leurs méthodes setter ;
  • en mai 2005, les deux technologies EL coexistantes sont combinées en une seule spécification : les expressions EL sont alors dites "unifiées", et font partie intégrante du standard Java EE 5. La syntaxe #{...} devient de fait utilisable également depuis une page JSP, mais y est toujours limitée à l'accès aux méthodes getter, seul l'usage depuis JSF permet d'appeler les méthodes setter ;
  • en décembre 2009, une évolution des expressions EL est introduite avec le standard Java EE 6. Celles-ci permettent désormais, depuis la syntaxe #{...}, d'appeler n'importe quelle méthode d'un bean, et non plus seulement ses getters/setters. C'est par ailleurs à cette occasion que les Facelets sont devenues partie intégrante du standard Java EE 6.

De cette longue épopée, vous devez retenir deux choses importantes :

  1. il est possible d'utiliser la syntaxe #{...} depuis vos pages JSP ! Je ne vous l'ai pas présentée plus tôt pour ne pas vous embrouiller, et surtout pour ne pas vous voir appeler des méthodes Java dans tous les sens sans comprendre ce que MVC implique et impose, ni comprendre comment doit être construit un bean…
  2. nous allons dorénavant utiliser la syntaxe #{...} avec JSF, car contrairement à ce que nous faisions avec la syntaxe ${...} depuis nos pages JSP, nous allons avoir besoin d'initialiser des valeurs et non plus seulement d'accéder en lecture à des valeurs.

Vous voilà au point sur les aspects superficiels de la technologie Facelet. Il est grand temps maintenant de passer derrière les rideaux, et de découvrir comment tout cela fonctionne dans les coulisses…

Comment ça marche ?

Avant tout, et parce que vous en aurez forcément un jour besoin si vous travaillez avec JSF, voici l'outil le plus utile pour comprendre les rouages du système : la documentation officielle du framework JSF dans sa version actuelle (2.1).

Vous savez déjà comment fonctionne une page JSP, mais un petit rappel ne fait jamais de mal. La technologie JSP fournit un langage de templating, permettant de créer des pages qui sont - par un procédé que vous connaissez - traduites en servlets Java, puis compilées. Pour faire court, le corps d'une page JSP devient l'équivalent de la méthode service(), la méthode mère des méthodes doGet() et doPost(). Les balises JSP et JSTL qui y sont utilisées sont directement transformées en code Java et intégrées dans la servlet générée, vous pouvez d'ailleurs vous en rendre compte par vous-mêmes en allant regarder le code des servlets auto-générées par Tomcat dans nos précédents exemples.

Dans une Facelet par contre, qui comme nous venons de le voir n'est qu'un fichier XML, les balises JSF ne sont que des appels à des composants JSF qui sont entièrement autonomes. Autrement formulé, lorsqu'un composant JSF est appelé, il génère son propre rendu dans son état courant. Le cycle de vie des composants JSF n'a par conséquent aucune relation avec le cycle de vie d'une page JSP et de sa servlet auto-générée. Une Facelet est donc une page constituée d'une suite d'appels à des composants JSF (réalisés par l'intermédiaire de balises), et ceux-ci forment ce que l'on appelle un arbre de composants, ou component tree en anglais.

Ainsi, ne vous laissez pas tromper par les apparences : même si JSF vous permet de travailler avec des balises JSF qui ressemblent comme deux gouttes d'eau à des balises JSTL, retenez bien que JSF ne fonctionne pas comme fonctionnaient nos exemples MVC basés sur des servlets et des JSP. Ces similitudes ont par contre un avantage certain : puisque vous connaissez déjà MVC avec les JSP, vous pouvez attaquer le développement avec JSF très rapidement !

En prenant un peu de recul, JSF s'apparente dans son fonctionnement à Swing ou AWT : c'est un framework qui fournit une collection de composants standards et réutilisables, permettant la création d'interfaces utilisateur (web, en l'occurrence). À la différence des JSP, les Facelets JSF conservent un état (on dit alors que la vue est stateful) : ce sont les composants autonomes appelés par l'intermédiaire des balises contenues dans les Facelets qui permettent de maintenir cet état. De la même manière que Swing et AWT, les composants JSF suivent le pattern de l'objet composite pour gérer un arbre de composants : en clair, cela signifie à la fois qu'un objet conteneur contient un composant, et qu'un objet conteneur est lui-même un composant. La vue lie ces composants graphiques à la page XHTML, et permet ainsi au développeur de directement lier des champs HTML d'interaction utilisateur (saisie de données, listes, etc.) à des propriétés de beans, et des boutons à leurs méthodes d'action.

Un processus en 6 étapes

Arrêtons les comparaisons avec d'autres solutions, et étudions concrètement le fonctionnement du framework. Avec JSF, le traitement d'une requête entrant sur le serveur est découpé en six étapes, que nous allons parcourir sommairement :

1. La restauration de la vue La requête entrante est redirigée vers l'unique servlet jouant le rôle de super-contrôleur, la FacesServlet. Celle-ci examine son contenu, en extrait le nom de la page ciblée et détermine s'il existe déjà une vue associée à cette page (eh oui, rappelez-vous bien qu'avec JSF la vue conserve un état). Voilà pourquoi cette étape s'intitule "restauration de la vue" : il s'agit en réalité de restaurer les éventuels composants déjà chargés si l'utilisateur a déjà accédé à la page par le passé.

La FacesServlet va donc chercher les composants utilisés par la vue courante. Si la vue n'existe pas déjà, elle va la créer. Si elle existe déjà, elle la réutilise. La vue contient tous les composants de l'interface utilisateur intervenant dans la page. La vue (c'est-à-dire l'ensemble des composants qui y interviennent, et donc son état) est sauvegardée dans l'objet FacesContext.

2. L'application des valeurs contenues dans la requête Arrivés à cette étape, les composants de la vue courante ont tout juste été récupérés ou créés depuis l'objet FacesContext. Chacun d'eux va maintenant récupérer la valeur qui lui est assignée depuis les paramètres de la requête, ou éventuellement depuis des cookies ou headers.

Ces valeurs vont alors être converties. Ainsi, si un champ est lié à une propriété de type Integer, alors son contenu va être converti en Integer. Si cette conversion échoue, un message d'erreur va être placé dans le FacesContext, et sera utilisé lors du futur rendu de la réponse.

À noter qu'à cette étape peut intervenir la "prise en charge immédiate des événements" : cela veut dire que si un composant est marqué comme tel, sa valeur va directement être convertie puis validée dans la foulée. Si aucun composant n'arbore cette propriété, alors les valeurs de tous les composants sont d'abord converties, puis intervient ensuite l'étape de validation sur l'ensemble des valeurs.

3. La validation des données Les valeurs tout juste converties vont ensuite être validées, en suivant les règles de validation définies par le développeur. Si la validation d'une valeur échoue, un message d'erreur est ajouté au FacesContext, et le composant concerné est marqué comme "invalide" par JSF. La prochaine étape est alors directement le rendu de la réponse, il n'y aura aucune autre étape intermédiaire.

Si les valeurs sont correctes vis-à-vis des règles de validation en place, alors la prochaine étape est la mise à jour des valeurs du modèle.

4. La mise à jour des valeurs du modèle Les composants peuvent être directement liés, par l'intermédiaire des balises présentes dans la vue, à des propriétés de beans. Ces beans sont qualifiés de managed-beans ou backing-beans, car ils sont gérés par JSF et la vue s'appuie sur eux. Si de tels liens existent, alors les propriétés de ces beans sont mises à jour avec les nouvelles valeurs des composants correspondants, fraîchement validées. Puisque la validation a eu lieu en premier lieu, le développeur est certain que les données enregistrées dans le modèle sont valides, au sens format du champ du formulaire. Il n'est par contre pas exclu que les données ne soient pas valides d'un point de vue de ce qu'attend le code métier de l'application, mais c'est tout à fait normal, puisque cette étape est la suivante dans le processus…

5. L'appel aux actions, le code métier de l'application Les actions associées à la soumission du formulaire sont alors appelées par JSF. Il s'agit enfin de l'entrée en jeu du code métier : maintenant que les données ont été converties, validées et enregistrées dans le modèle, elles peuvent être utilisées par l'application.

La fin de cette étape se concrétise par la redirection vers la vue correspondante, qui peut dépendre ou non du résultat produit par le code métier. Il s'agit donc de définir la navigation au sein des vues existantes, ce qui est réalisé directement depuis le bouton de validation dans la page, ou depuis un fichier de configuration XML externe nommé faces-config.xml.

6. Le rendu de la réponse La dernière et ultime étape est le rendu de la réponse. La vue définie dans la navigation est finalement affichée à l'utilisateur : tous les composants qui la composent effectuent alors leur propre rendu, dans leur état courant. La page HTML ainsi générée est finalement envoyée au client, mais ça, vous vous en doutiez !

Voilà comment se déroule le traitement d'une requête avec JSF. Comme vous pouvez le voir, ça change du mode de traitement linéaire que nous avions adopté dans nos exemples MVC faits maison, notamment au niveau de la possibilité de prise en charge immédiate d'un événement qui permet le court-circuitage du processus global pour un composant en particulier ! Si tout ceci est encore très flou dans votre tête, c'est normal : beaucoup de choses vous semblent encore bien abstraites. Ne vous découragez surtout pas, car comprendre le fonctionnement de JSF est l'effort le plus intense qu'il vous faudra fournir. Dès lors que vous aurez assimilé comment se goupille toute cette mécanique, vous aurez fait plus de la moitié du chemin vers l'adoption de JSF ! ;)

Pour vous aider à bien comprendre, je vous propose de découvrir ce processus dans un exemple pratique très simple. Mais avant cela, je vous propose de vous détendre un peu en découvrant une petite astuce sous Eclipse, permettant de préparer rapidement votre espace de travail au développement de Facelets, les fameuses pages que nous allons créer à la place de nos pages JSP.

Créer un template de Facelet par défaut avec Eclipse

Avec Eclipse, il est possible de définir quel sera le contenu généré par défaut lors de la création d'un nouveau fichier. Pour préparer facilement nos vues JSF, il nous suffit donc de créer un nouveau type de fichier nommé "Facelet" et de personnaliser son contenu par défaut. Pour ce faire, rendez-vous dans les préférences d'Eclipse, puis suivez Web > HTML Files > Editor > Templates et cliquez enfin sur New.

Entrez alors comme nom "New Facelet Page", puis sélectionnez le contexte "New HTML", entrez comme description "Creates a new Facelet page", puis copiez le code suivant dans le champ pattern et validez enfin en cliquant sur OK (voir la figure suivante).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!DOCTYPE html>
<html lang="fr"
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
    <h:head>
        <title>Insérer le titre ici</title>
    </h:head>
    <h:body>
        ${cursor}
    </h:body>
</html>

Modèle de nouvelle Facelet

Une fois cette configuration en place, vous pourrez simplement créer une nouvelle Facelet prête à être codée ! Il vous suffira de faire un clic droit dans un projet, puis de suivre New > HTML File. Puis de donner un nom à votre fichier et de cliquer sur Next dans la fenêtre qui s'affiche alors, avant de choisir votre template fraîchement créé dans la liste qui s'affiche enfin et de valider en cliquant sur Finish, comme indiqué à la figure suivante.

Le nouveau fichier que vous venez de créer contient alors automatiquement le code de base que vous avez défini plus tôt dans les options d'Eclipse.

Premier projet

De quoi avons-nous besoin ?

Puisque nous travaillons avec GlassFish, nous n'avons en apparence besoin de rien : tout y est déjà inclus ! En réalité, c'est encore une fois le même refrain : JSF n'est qu'une spécification, et pour utiliser JSF il faut donc disposer d'une implémentation. Souvenez-vous de JPA, c'était exactement pareil : l'implémentation de référence de JPA utilisée par défaut par GlassFish était EclipseLink, mais il existait d'autres implémentations très utilisées comme Hibernate notamment. En ce qui concerne JSF, il existe deux principales implémentations :

  • Oracle Mojarra, l'implémentation de référence, utilisée par défaut par GlassFish ;
  • Apache MyFaces, l'implémentation éditée par Apache.

Quelle implémentation choisir ?

Les différences entre ces deux solutions sont minimes aux premiers abords. Seule une utilisation très poussée d'une solution ou de l'autre vous fera prendre conscience des écarts existants entre ces deux implémentations, et de l'intérêt de préférer l'une à l'autre. Ainsi, il n'y a pas de réponse empirique à la question : selon le contexte de votre projet, vous serez peut-être amenés à changer d'une implémentation vers l'autre, en raison d'un comportement qui pose problème chez l'une mais pas chez l'autre.

Qui plus est, puisque ces deux implémentations respectent la spécification JSF, elles sont interchangeables très simplement. L'éventuel besoin de changer d'implémentation en cours de route dans un projet n'est donc pas un réel problème.

Bref, en ce qui nous concerne, nous n'en sommes qu'aux balbutiements et allons partir par défaut sur l'implémentation Mojarra, puisque c'est celle que GlassFish embarque nativement.

Et si nous n'utilisions pas GlassFish ?

Si le serveur utilisé est un serveur d'applications Java EE au sens strict du terme, alors une implémentation JSF doit être fournie par défaut. Si par contre c'est un serveur léger comme Tomcat qui est utilisé, alors il est nécessaire d'ajouter au projet les jar de l'implémentation JSF pour pouvoir l'utiliser dans votre application. Ces archives jar sont bien entendu disponibles au téléchargement sur les sites respectifs des deux solutions.

Et si nous souhaitions utiliser MyFaces sur un serveur GlassFish ?

Si nous voulions changer d'implémentation JSF et utiliser MyFaces au lieu de Mojarra, il nous suffirait alors d'importer les archives jar de l'implémentation dans notre projet, de la même manière que nous devons les importer sur un serveur léger.

Au final, retenez bien que GlassFish est livré par défaut avec une implémentation de JSF, et donc que nous pouvons travailler avec JSF sans aucun ajout.

Création du projet

Nous pouvons maintenant attaquer la création de notre premier exemple : nous allons très modestement créer une page qui demande à l'utilisateur de saisir son nom dans un champ de formulaire, et une seconde page qui se chargera d'afficher le nom saisi à l'utilisateur. Rien de transcendant je vous l'accorde, mais c'est déjà assez pour que vous puissiez découvrir en douceur le fonctionnement de JSF.

Avant tout, nous devons mettre en place un projet web sous Eclipse. La démarche est la même que pour nos précédents travaux :

  • créez un projet web dynamique ;
  • nommez-le pour cet exemple test_jsf ;
  • validez, et le projet est prêt !

Création du bean

Pour commencer, nous allons créer un simple bean pour stocker le nom saisi par l'utilisateur. Je vous donne le code, et vous explique les quelques nouveautés ensuite :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.sdzee.exemple;

import java.io.Serializable;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

@ManagedBean
@RequestScoped
public class BonjourBean implements Serializable {

    private static final long serialVersionUID = 1L;

    private String            nom;

    public String getNom() {
        return nom;
    }

    public void setNom( String nom ) {
        this.nom = nom;
    }
}

com.sdzee.exemple.BonjourBean

Vous pouvez observer deux choses qui diffèrent des simples beans que nous utilisions dans nos exemples MVC traditionnels :

  • le bean implémente l'interface Serializable. En réalité, je vous avais déjà prévenu que nos beans pouvaient tirer parti de cette interface lorsque nous avons découvert le JavaBean pour la première fois, mais je ne vous avais alors pas expliqué à quoi cela pouvait bien servir. En réalité c'est très simple : en rendant un bean sérialisable, vous lui donnez la capacité de survivre à un redémarrage du serveur. Cela ne va pas plus loin que cela, c'est un détail qui n'a aucune importance dans notre exemple, et j'ai simplement fait intervenir cette interface pour vous expliquer son rôle. Au passage, votre IDE Eclipse vous fera remarquer la nécessité de préciser un attribut serialVersionUID, qu'il peut générer automatiquement pour vous.
  • le bean contient deux annotations spécifiques à JSF :
    • @ManagedBean : permet de préciser au serveur que ce bean est dorénavant géré par JSF. Cela signifie simplement que JSF va utiliser ce bean en tant que modèle associé à une ou plusieurs vues. Par défaut, le nom du bean correspond au nom de la classe, la majuscule en moins : en l'occurrence le nom de notre bean est donc bonjourBean. Si nous voulions désigner ce bean par un autre nom, par exemple direBonjour, alors il nous faudrait annoter le bean en précisant le nom souhaité, via @ManagedBean(name="direBonjour") ;
    • @RequestScoped : permet de préciser au serveur que ce bean a pour portée la requête. Il s'agit en l’occurrence de la portée utilisée par défaut en cas d'absence d'annotation. Ainsi, si vous omettez de l'écrire, le bean sera de toute manière placé dans la portée requête. C'est toutefois une bonne pratique de toujours écrire cette annotation, afin de clarifier le code. Il existe autant d'annotations que de portées disponibles dans JSF : @NoneScoped, @RequestScoped, @ViewScoped, @SessionScoped, @ApplicationScoped, et @CustomScope. Ne vous inquiétez pas, nous y reviendrons en détail très prochainement.

Pour information, avec JSF 1.x ces annotations n'existaient pas, il fallait déclarer chaque bean dans un fichier de configuration externe nommé faces-config.xml. Grâce aux annotations, ce n'est désormais plus nécessaire avec JSF 2.x !

Vous savez maintenant ce qu'est le fameux managed-bean ou backing-bean dont je vous avais parlé un peu plus tôt : un simple bean annoté pour le déclarer comme tel auprès de JSF. Un pas de plus vers la compréhension de JSF… ;)

Création des facelets

La seconde étape consiste à créer les vues de notre petite application. Nous allons donc créer deux Facelets, qui pour rappel ne sont rien d'autre que des pages XHTML et contenant des balises propres à JSF, chargées respectivement d'afficher un champ de formulaire à l'utilisateur, et de lui afficher les données saisies.

Nous allons nommer la page contenant le formulaire bonjour.xhtml, et la page chargée de l'affichage bienvenue.xhtml. Toutes deux doivent être placées directement à la racine de votre application, symbolisée par le dossier /WebContent dans Eclipse :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Premier exemple JSF 2.0</title>
    </h:head>
    <h:body>
        <h1>Premier exemple JSF 2.0 - bonjour.xhtml</h1>
        <h:form>
           <h:inputText value="#{bonjourBean.nom}" />
           <h:commandButton value="Souhaiter la bienvenue" action="bienvenue" />
        </h:form>
    </h:body>
</html>

/bonjour.xhtml

Cette page génère :

  • un champ de texte JSF, et le lie avec la propriété nom du bean bonjourBean (notre managed-bean, géré par JSF) ;
  • un bouton de formulaire, chargé d'afficher la page bienvenue.xhtml lorsqu'il est cliqué.

Observons la constitution de cette page. En premier lieu, vous retrouvez logiquement l'en-tête html particulier dont je vous ai parlé lorsque nous avons abordé la constitution d'une Facelet, qui contient le type de la page ainsi que les éventuelles déclarations de bibliothèques de balises JSF. En l'occurrence, nous avons ici simplement déclaré la bibliothèque HTML à la ligne 3, puisque nous n'utilisons dans la page que des balises de la bibliothèque HTML.

Vous observez ensuite des balises JSF, préfixées par h: comme déclaré dans l'en-tête html. Étudions-les dans leur ordre d'apparition :

Le header

La section header est créée par la balise <h:head>. Il s'agit d'un composant JSF qui permet notamment d'inclure des ressources JS ou CSS dans le contenu généré entre les balises HTML <head> et </head>, de manière automatisée depuis votre code Java.

Dans notre exemple, nous n'avons rien de particulier à y inclure, nous nous contentons d'y définir un titre pour notre page via la balise HTML <title>. Vous pouvez également y déclarer un charset via la balise HTML <meta>, etc., comme vous le faisiez dans vos pages JSP.

Le formulaire

Le formulaire est créé par la balise <h:form>. Il s'agit du composant JSF permettant de générer un formulaire, contenant d'éventuels champs de saisies. La méthode HTTP utilisée pour l'envoi des données est toujours POST. Si vous souhaitez utiliser la méthode GET, alors le plus simple est de ne pas utiliser le composant JSF et d'écrire directement votre formulaire en HTML brut en y spécifiant l'attribut <form... method="get">.

Les données sont par défaut renvoyées à la page contenant le formulaire.

Le champ de saisie

Le champ de type texte est créé par la balise <h:inputText>. Son attribut value permet de définir deux choses :

  • la valeur contenue dans cet attribut sera par défaut affichée dans le champ texte, il s'agit ici du même principe que pour un champ de texte HTML classique <input type="text" value="..." /> ;
  • la valeur contenue dans cet attribut sera utilisée pour initialiser la propriété nom du bean BonjourBean, par l'intermédiaire de l'expression EL #{bonjourBean.nom}. En l'occurrence, JSF va évaluer l'expression lorsque le formulaire sera validé, c'est-à-dire lorsque le bouton sera cliqué, et va alors chercher l'objet nommé bonjourBean, puis utiliser sa méthode setter setNom() pour enregistrer dans la propriété nom la valeur contenue dans le champ texte.

Le bouton de validation

Le bouton d'envoi des données du formulaire est créé par la balise <h:commandButton> :

  • son attribut value contient la valeur affichée à l'utilisateur en guise de texte du bouton, tout comme un bouton HTML classique ;
  • son attribut action permet quant à lui de définir où rediriger l'utilisateur : en l'occurrence, nous allons le rediriger vers la page qui affiche le texte saisi, c'est-à-dire bienvenue.xhtml. Il nous suffit pour cela d'écrire bienvenue dans le champ action de la balise, et le composant se chargera automatiquement derrière les rideaux d'appeler la page nommée bienvenue.xhtml.

Sous JSF 1.x, il était nécessaire de déclarer une navigation-rule dans le fichier faces-config.xml, afin de définir quelle page serait affichée une fois le bouton cliqué. Avec JSF 2.x, vous constatez qu'il est dorénavant possible de directement placer le nom de la page dans l'attribut action du bouton. Pour une navigation simple, c'est très pratique et plus que suffisant ! Toutefois, sachez que pour mettre en place une navigation un peu plus complexe, il nous faudra toujours utiliser une section navigation-rule dans le faces-config.xml.

Voilà ensuite le code de la page de bienvenue, extrêmement simple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"    
      xmlns:h="http://java.sun.com/jsf/html">
    <h:head>
        <title>Premier exemple JSF 2.0</title>
    </h:head>
    <h:body>
        <h1>Premier exemple JSF 2.0 - bienvenue.xhtml</h1>
        <p>Bienvenue #{bonjourBean.nom} !</p>
    </h:body>
</html>

/bienvenue.xhtml

Lors de l'affichage de la page, JSF va évaluer l'expression EL #{bonjourBean.nom} située à la ligne 9, et chercher l'objet intitulé bonjourBean pour afficher sa propriété nom, récupérée automatiquement via la méthode getter getNom().

Configuration de l'application

Afin de mettre ce petit monde en musique, il nous faut pour finir écrire un peu de configuration dans le fichier web.xml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <!-- Changer cette valeur à "Production" lors du déploiement final de l'application -->
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>

    <!-- Déclaration du contrôleur central de JSF : la FacesServlet -->
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Mapping : association des requêtes dont le fichier porte l'extension .xhtml à la FacesServlet -->
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
</web-app>

/WEB-INF/web.xml

Aux lignes 10 à 14, nous déclarons tout simplement la servlet mère de JSF, celle qui joue le rôle du Front Controller : la FacesServlet. C'est exactement le même principe que lorsque nous définissions nos propres servlets à la main auparavant. Il suffit de préciser un nom, en l'occurrence je l'ai nommée "Faces Servlet", et sa localisation dans l'application, en l'occurrence javax.faces.webapp.FacesServlet.

Aux lignes 17 à 20, nous procédons ensuite au mapping d'un pattern d'URL sur cette seule et unique FacesServlet. L'objectif est de rediriger toutes les requêtes entrantes vers elle. Dans notre exemple, nous nous contentons d'associer les vues portant l'extension .xhtml à la FacesServlet, puisque toutes nos vues sont ainsi constituées.

À ce sujet, sachez que tous les développeurs n'utilisent pas l'extension .xhtml pour leurs vues, et il est courant dans les projets JSF existants de rencontrer quatre types d'URL différentes : /faces/, .jsf, .xhtml et .faces. Si vous devez un jour manipuler des vues portant une de ces extensions, il vous faudra simplement ajouter des mappings dans votre web.xml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<!-- Mapping des différents patterns d'URL devant être associés à la FacesServlet -->
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.xhtml</url-pattern>
</servlet-mapping>

Mappings pour les différents types d'URL

Ainsi avec une telle configuration, dans notre projet les quatre URLs suivantes pointeraient toutes vers la même page /bonjour.xhtml :

  • http://localhost:8088/test_jsf/bonjour.jsf
  • http://localhost:8088/test_jsf/bonjour.faces
  • http://localhost:8088/test_jsf/bonjour.xhtml
  • http://localhost:8088/test_jsf/faces/bonjour.jsf

Je me répète, mais en ce qui nous concerne le seul mapping des vues portant l'extension .xhtml est suffisant, nous n'avons pas besoin de mettre en place tous ces autres mappings.

Enfin, vous remarquerez la déclaration d'un paramètre particulier aux lignes 4 à 7. Il s'agit d'une fonctionnalité utile proposée par JSF. Lors du développement d'une application, il est recommandé d'initialiser le paramètre nommé javax.faces.PROJECT_STAGE avec la valeur “Development“. Ceci va rendre disponibles de nombreuses informations de debugging et les afficher directement au sein de vos pages en cas de problème, permettant ainsi de tracer les erreurs rapidement. Lors du déploiement final, une fois l'application achevée, il suffit alors de changer la valeur du paramètre à “Production“, et toutes ces informations non destinées au public ne seront alors plus affichées.

Le fichier web.xml que je vous donne ici en exemple est donc générique, il ne contient rien de spécifique à ce projet en particulier et se contente d'établir les propriétés nécessaires au bon fonctionnement de JSF. En d'autres termes, vous pourrez réutiliser ce fichier tel quel pour tous vos projets JSF !

Tests & observations

Notre projet est maintenant prêt pour utilisation : seuls ces deux Facelets et ce bean suffisent ! Vérifions pour commencer le bon fonctionnement de l'application en nous rendant sur l'URL http://localhost:8088/test_jsf/bonjour.xhtml depuis notre navigateur. Vous devez observer un champ de formulaire et un bouton de validation, comme indiqué sur la figure suivante.

Après un clic sur le bouton, vous devez alors être redirigés vers la page de bienvenue affichant ce que vous avez tapé dans le champ texte (voir la figure suivante).

Essayons maintenant de changer la cible précisée dans l'attribut action du bouton par une page qui n'existe pas dans notre application. Modifiez par exemple la ligne 12 de la page bonjour.xhtml par :

1
<h:commandButton value="Souhaiter la bienvenue" action="connexion" />

Modification de l'attribut action

Rendez-vous alors à nouveau sur la page http://localhost:8088/test_jsf/bonjour.xhtml, cliquez sur le bouton et observez l'erreur affichée sur la figure suivante.

Vous voilà face au type d'informations dont je vous ai parlé lorsque je vous ai expliqué l'intérêt du paramètre PROJECT_STAGE, que nous avons mis en place dans notre web.xml. Le contrôleur JSF - la FacesServlet - est incapable de trouver une Facelet nommée connexion.xhtml, et JSF vous affiche donc automatiquement l'erreur, directement au sein de votre page ! Pratique pour identifier ce qui pose problème au premier coup d'œil, n'est-ce pas ?

Faisons maintenant le même essai, mais cette fois en passant le paramètre javax.faces.PROJECT_STAGE à "Production". Effectuez la modification dans le fichier web.xml, puis rendez-vous une nouvelle fois sur la page depuis votre navigateur. Dorénavant lorsque vous cliquez sur le bouton, aucune erreur ne s'affiche ! Et c'est normal, puisque le mode de production est destiné à une utilisation publique de l'application.

Vous comprenez maintenant mieux l'intérêt de ce paramètre : il permet de définir le mode de fonctionnement de l'application, et donc de définir si les messages de debug seront affichés ou non sur les pages affichées par l'utilisateur.

Nous allons maintenant annuler nos précédentes modifications : remettons à "Development" le paramètre dans le fichier web.xml, et remettons "bienvenue" dans le champ action de notre formulaire. Nous allons ensuite modifier l'expression EL contenue dans notre champ texte, en y précisant un bean qui n'existe pas dans notre application. Modifiez par exemple la ligne 11 de la page bonjour.xhtml par :

1
<h:inputText value="#{inscriptionBean.nom}" />

Modification de l'expression EL

Rendez-vous alors à nouveau sur la page http://localhost:8088/test_jsf/bonjour.xhtml, cliquez sur le bouton et observez l'erreur affichée sur la figure suivante.

Lors de l'évaluation de l'expression EL, JSF ne trouve aucun bean nommé inscriptionBean et affiche donc une page d'erreur détaillant le problème rencontré.

Pour terminer, annulons cette dernière modification en mettant à nouveau le bean bonjourBean dans l'expression EL, puis essayons de changer le nom de la méthode setter dans le bean. Modifiez par exemple les lignes 20 à 22 de la classe BonjourBean par :

1
2
3
public void setPrenom( String nom ) {
    this.nom = nom;
}

Modification de la méthode setter

Rendez-vous alors à nouveau sur la page http://localhost:8088/test_jsf/bonjour.xhtml, cliquez sur le bouton et observez l'erreur affichée sur la figure suivante.

Lors de l'évaluation de l'expression EL, JSF ne trouve aucune méthode setter pour la propriété nom du bean bonjourBean. En effet, la règle pour un JavaBean impose que la méthode soit correctement nommée, et puisque nous avons changé sa dénomination, JSF considère alors qu'il n'existe pas de méthode setter. Il nous prévient donc logiquement qu'une exception javax.el.PropertyNotWritableException est levée, et que la propriété nom est considérée comme étant en lecture seule.

Nous y voilà, nous avons observé le fonctionnement correct de notre application et fait le tour des erreurs les plus courantes. Avant de passer aux bonnes pratiques, analysons brièvement ce qui a changé par rapport à notre ancienne méthode, sans JSF :

  • auparavant, nous aurions dû écrire deux pages JSP, en lieu de place de nos deux simples Facelets ;
  • auparavant, nous aurions dû écrire une servlet, chargée :
    • d'afficher la page bonjour.jsp lors de la réception d'une requête GET ;
    • de récupérer le contenu du champ texte du formulaire lors de la réception d'une requête POST, puis de s'en servir pour initialiser la propriété du bean BonjourBean, que nous aurions d'ailleurs dû créer, avant de transmettre le tout sous forme d'attributs à la page bienvenue.jsp pour affichage.

Ça paraît peu a priori, mais rendez-vous bien compte de ce dont nous n'avons plus à nous soucier avec JSF :

  1. la manipulation des objets requête et réponse : avec JSF, nous n'avons pas conscience de ces objets, leur existence nous est masquée par le framework.
  2. l'extraction des paramètres contenus dans une requête HTTP : avec JSF, il nous suffit d'écrire une EL au sein d'une Facelet, et le tour est joué.
  3. l'initialisation manuelle des beans : avec JSF, le cycle de vie des beans annotés est entièrement géré par le framework ! À aucun moment nous n'avons initialisé un bean, et à vrai dire, à aucun moment nous n'avons créé une classe Java susceptible de pouvoir procéder à cette initialisation ! Tout ce que nous avons mis en place, ce sont deux Facelets et un bean…
  4. la mise en place d'attributs dans un objet HttpServletRequest pour transmission à une page JSP : avec JSF, il suffit d'écrire une EL dans un composant de notre Facelet, et le framework s'occupe du reste.
  5. la redirection manuelle vers une page JSP pour affichage : avec JSF, il suffit de préciser la page ciblée dans l'attribut action du composant <h:commandButton>.

Ainsi, vous pouvez déjà vous rendre compte de la simplification opérée par JSF : rien que sur ce petit exemple extrêmement simple, le code à produire est bien plus léger que si nous avions fait du MVC à la main !

Enfin, je vous invite à examiner le code source HTML de la page bonjour.xhtml que vous visualisez depuis votre navigateur. Vous y trouverez le code HTML produit par le rendu des différents composants de votre Facelet, c'est-à-dire le rendu des balises <h:form>, <h:inputText>, etc. Vous comprendrez alors mieux ce que je vous annonçais dans le chapitre précédent : avec un framework basé sur les composants, vous n'êtes plus aussi maîtres du HTML final envoyé à l'utilisateur que vous l'étiez lorsque vous créiez vos vues entièrement à la main par l'intermédiaire de pages JSP.

Les ressources

Pour terminer ce chapitre difficile et relativement abstrait sur une note plus légère, nous allons découvrir le système des ressources mis en place dans les projets JSF.

Vous avez dû vous en rendre compte, nous n'avons pas utilisé de styles CSS dans notre précédent exemple. Je vous rassure tout de suite, nous aurions pu faire comme nous en avions l'habitude, et déclarer une feuille CSS dans la section header de notre Facelet bonjour.xhtml :

1
2
3
4
<h:head>
    <title>Premier exemple JSF 2.0</title>
    <link type="text/css" rel="stylesheet" href="inc/form.css" />
</h:head>

Déclaration d'une feuille CSS

Nous aurions alors bien évidemment dû prendre soin de recopier la feuille form.css et le dossier /inc que nous utilisions dans nos précédents exemples, et de les placer à la racine du projet, comme indiqué sur la figure suivante.

Faites ces modifications, rendez-vous une nouvelle fois sur la page http://localhost:8088/test_jsf/bonjour.xhtml depuis votre navigateur, et constatez les légers changements intervenant dans l'affichage des éléments HTML.

Tout est donc normal, cependant nous n'allons pas procéder de cette manière dans la suite du cours. Avec JSF 2.x, une bonne pratique est de placer toutes les ressources web telles que les fichiers CSS, les images ou les fichiers JavaScript, dans un répertoire intitulé resources et placé directement à la racine de votre application, c'est-à-dire au même niveau que le dossier /WEB-INF.

Cette bonne pratique veut que nous mettions en place ce dossier resources, et que nous y créions des sous-dossiers pour délimiter les différentes ressources web que nous souhaitons utiliser. Si par exemple nous avions des images, des feuilles CSS et des scripts JS, pour respecter la bonne pratique nous pourrions par exemple créer l'arborescence indiquée sur la figure suivante et y placer nos différentes ressources.

Pour le moment, nous n'avons que le fichier form.css à y mettre.

Une fois ceci en place, nous pouvons maintenant utiliser un composant dédié à l'inclusion de ressources depuis nos Facelets. Par exemple, pour utiliser la feuille CSS form.css placée dans le dossier /resources/default/css, il suffit de remplacer l'inclusion traditionnelle via <link type="text/css" ... > par :

1
<h:outputStylesheet library="default" name="css/form.css"  />

Ce composant JSF permet de cibler directement le sous-dossier du répertoire resources via le contenu de son attribut library, que nous avons donc renseigné par "default" afin d'accéder au sous-dossier que nous avons mis en place. Il cible enfin le fichier souhaité via le contenu de son attribut name.

Effectuez les modifications (création du dossier /resources/default/css, déplacement du fichier form.css et suppression de l'ancien dossier /inc, puis modification de l'appel au fichier CSS depuis votre Facelet) et rendez-vous à nouveau sur la page depuis votre navigateur. Vous n'observerez aucun changement, preuve que le composant a bien fonctionné et a correctement inclus votre feuille CSS !

Le composant JSF ici utilisé est dédié à l'inclusion de feuilles CSS. Il en existe d'autres similaires, pour l'inclusion des fichiers JavaScript et des images. Nous y reviendrons le moment venu dans la suite du cours.

JSF, ou ne pas JSF ?

Faut-il opter pour JSF, ou du MVC avec des servlets/JSP faites maison ?

Dans le cas d'une application web destinée à votre apprentissage personnel, créer votre propre framework n'est pas une mauvaise idée, c'est un très bon exercice. Cela dit, si le long terme est envisagé pour votre application, ce n'est clairement pas judicieux. La plupart des frameworks MVC existants sont bien pensés, et la majorité des imprévus y sont pris en compte. En outre, l'API d'un framework public reconnu est bien documentée et maintenue par une communauté tierce.

Dans un autre registre, si jamais votre application devient populaire et que vous devez intégrer des développeurs supplémentaires à votre projet, afin par exemple de satisfaire aux divers besoins du client, il est bien plus aisé de trouver quelqu'un qui est déjà familier avec un framework existant. Avec un framework fait maison et probablement buggé, vous trouverez peu de développeurs prêts à se former sur une telle technologie et à prendre en charge la maintenance sachant pertinemment qu'ils ne réutiliseront probablement jamais cette technologie dans leurs projets futurs…

Enfin, sachez que tout cela ne s'applique pas uniquement à JSF, mais également à tous les autres frameworks populaires existant, comme Spring MVC par exemple.

Quelle est la différence entre JSF et Servlets/JSP/HTML/CSS/JS ?

Pour faire une analogie avec une technologie d'actualité dans le domaine du web, comparer JSF au pur combo Servlets/JSP/HTML/CSS/JS revient à comparer jQuery à du pur JavaScript : faire plus avec moins de code. Pour prendre PrimeFaces comme exemple, explorez sa vitrine et découvrez des exemples de codes complets. RichFaces propose lui aussi une vitrine avec des exemples de codes complets. Si vous étudiez avec attention ces exemples, vous comprendrez alors que vous n'avez bien souvent pas à vous soucier de la qualité du rendu HTML/CSS/JS d'une part, et d'autre part que vous n'avez besoin que d'un simple bean pour réaliser le modèle et d'une simple page XHTML pour la vue.

Remarquez toutefois que vous ne devez pas voir JSF comme une alternative à HTML/CSS/JS uniquement, mais bien prendre en compte également la partie serveur (typiquement JSP/Servlets). JSF permet d'éviter tout le code passe-partout en charge du regroupement des paramètres de requêtes HTTP, de leur conversion/validation, de la mise à jour des données du modèle et de l'exécution de la bonne méthode Java pour réaliser les traitements métiers. Avec JSF, vous n'avez plus qu'une page XHTML en guise de vue, et un JavaBean en tant que modèle. Cela accélère le développement de manière significative !

Bien entendu, comme c'est le cas avec tous les frameworks MVC basés sur les composants, vous disposez avec JSF d'une faible marge de contrôle sur le rendu HTML/CSS/JS. Ajouter du code JS personnalisé n'est par exemple pas chose aisée. Si c'est un obstacle pour votre projet, regardez plutôt du côté des frameworks MVC basés sur les actions comme Spring MVC. Vous devez toutefois savoir que vous allez devoir écrire tout ce code HTML/CSS/JS vous-mêmes et par le biais de pages JSP, là où les Facelets de JSF vous offriraient des templates avancés.


  • JSF est un framework MVC basé sur les composants. C'est un standard, car il fait partie intégrante de la plate-forme Java EE 6.
  • Avec JSF, une unique servlet joue entre autres le rôle d'aiguilleur géant : la FacesServlet.
  • Avec JSF, il n'est plus nécessaire d'écrire de servlets : seuls des Facelets et des backing-beans sont nécessaires.
  • Dans sa version actuelle, JSF tire partie des annotations Java pour simplifier grandement le développement et la configuration.
  • Une Facelet est une simple page XHTML, contenant des balises qui lient littéralement la vue aux composants JSF, et leur associe d'éventuelles valeurs via des expressions EL.
  • L'ensemble des balises d'une vue, qui peut être constituée d'une ou plusieurs Facelets, constitue l'arbre des composants associé à cette vue.
  • Avec JSF, le processus de traitement d'une requête rassemble les étapes de récupération, conversion, validation et sauvegarde des données.
  • Lors du développement d'une application JSF, il est possible d'activer un mode de debug pour faciliter le pistage des erreurs rencontrées.