Premier petit exemple

//TODO adapter à primeface / netbeans 7.4 / JSF 2.2

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.

  • Primeface, 

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 existant 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 de base fournie par netbeans

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 :

1package com.sdzee.exemple;
2
3import java.io.Serializable;
4
5import javax.faces.bean.ManagedBean;
6import javax.faces.bean.SessionScoped;
7
8@ManagedBean
9@RequestScoped
10public class BonjourBean implements Serializable {
11
12    private static final long serialVersionUID = 1L;
13
14    private String            nom;
15
16    public String getNom() {
17        return nom;
18    }
19
20    public void setNomString nom ) {
21        this.nom = nom;
22    }
23}

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'affichagebienvenue.xhtml. Toutes deux doivent être placées directement à la racine de votre application, symbolisée par le dossier /WebContent dans Eclipse :

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

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

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 gettergetNom().

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<?xml version="1.0" encoding="UTF-8"?>
2<web-app>
3    <!-- Changer cette valeur à "Production" lors du déploiement final de l'application -->
4    <context-param>
5        <param-name>javax.faces.PROJECT_STAGE</param-name>
6        <param-value>Development</param-value>
7    </context-param>
8 
9    <!-- Déclaration du contrôleur central de JSF : la FacesServlet -->
10    <servlet>
11        <servlet-name>Faces Servlet</servlet-name>
12        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
13        <load-on-startup>1</load-on-startup>
14    </servlet>
15 
16    <!-- Mapping : association des requêtes dont le fichier porte l'extension .xhtml à la FacesServlet -->
17    <servlet-mapping>
18        <servlet-name>Faces Servlet</servlet-name>
19        <url-pattern>*.xhtml</url-pattern>
20    </servlet-mapping>
21</web-app>

Aux lignes 10 à 14, nous déclarons tout simplement la servlet mère de JSF, celle qui joue le rôle duFront 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 uniqueFacesServlet. 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<!-- Mapping des différents patterns d'URL devant être associés à la FacesServlet -->
2<servlet-mapping>
3    <servlet-name>Faces Servlet</servlet-name>
4    <url-pattern>/faces/*</url-pattern>
5</servlet-mapping>
6<servlet-mapping>
7    <servlet-name>Faces Servlet</servlet-name>
8    <url-pattern>*.jsf</url-pattern>
9</servlet-mapping>
10<servlet-mapping>
11    <servlet-name>Faces Servlet</servlet-name>
12    <url-pattern>*.faces</url-pattern>
13</servlet-mapping>
14<servlet-mapping>
15    <servlet-name>Faces Servlet</servlet-name>
16    <url-pattern>*.xhtml</url-pattern>
17</servlet-mapping>

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'URLhttp://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.

Image utilisateur

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).

Image utilisateur

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" />

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.

Image utilisateur

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ètrejavax.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}" />

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.

Comments