5. Vues: JSP et tag libraries

Dans ce chapitre nous complétons l’introduction de Java EE avec quelques compléments sur les vues JSP/JSTL, toujours dans l’optique du développement d’une application MVC.

Important

Ne considérez surtout pas ce qui suit comme une couverture complète du sujet, le but est vraiment de connaître l’essentiel, pour en comprendre les mécanismes. Pour devenir un expert il faut aller plus loin (mais les bases acquises devraient rendre la tâche facile), en recourant par exemple aux tutoriels disponibles sur le Web.

S1: JSP, en bref

Supports complémentaires :

Les JSP sont une approche ancienne qui a beaucoup bénéficié de l’introduction de constructions syntaxiques plus élégantes que l’introduction de code Java, et d’un codage basé sur les conventions qui simplifie considérablement l’écriture. Les langages de vues proposés par les frameworks sont pour l’essentiel assez proches des JSP. Nous allons donc passer en revue calmement les concepts généraux à connaître, qu’il vous suffira ensuite de décrypter dans votre framework préféré.

Balises et directives

L’idée initiale des JSP était d’étendre HTML avec des « balises » spéciales interprétées comme des instructions Java, avec l’espoir d’aboutir à une syntaxe à peu près uniforme intégrant le HTML « statique » et les parties dynamiques. De nombreuses mises au point ont été nécessaires pour aboutir à un résultat satisfaisant. Quelques exemples de balises proposées initialements (et toujours valides):

  • <%-- --%> est un commentaire JSP;
  • <%! %> est une balise de déclaration d’une variable;
  • <% %> est une balise d’inclusion de code Java quelqconque….;
  • <%= %> est une balise d’affichage.

Cette approche fondée sur l’inclusion vaguement déguisée de code est maintenant dépréciée et doit être évitée. Elle repose clairement sur l’idée de simplifier l’écriture d’une servlet en évitant de placer des out.println devant chaque ligne HTML, une ambition louable mais limitée, ouvrant surtout la porte à des dérives vers du code illisible et non maintenable.

Note

Le conteneur compile une page JSP sous la forme d’une servlet dans laquelle les éléments HTML sont transcrits en instructions out.println(). Il s’agit donc vraiment, au moins initialement, d’un moyen simplifié de créer des servlets produisant du HTML.

La norme JSP comprend également des directives qui se présentent sous la forme de balises <%@ %>. Toujours dans une optique de programmation, on peut par exemple importer des packages Java:

<%@ page import="java.util.Date"  %>

Remarquez le mot-clé page qui indique le type de la directive. Reprenez les JSP créées précédemment: elles contiennent une directive initiale indiquant l’encodage de la page.

Une des améliorations des JSP a consisté à les rendre extensibles en ajoutant des bibliothèques de balises (tag libraries) associées à des fonctionnalités externes. Ces bilbiothèques doivent être déclarées avec une directive:

<%@ taglib uri="NsyTagLib.tld" prefix="nsy" %>

Le prefix désigne un espace de nommage (namespace) qui permet d’identifier une balise comme appartenant à cette bibliothèque. Par exemple, une balise <nsy:conv /> sera identifiée comme appartenant à la bibliothèque NsyTagLib.tld, et le code associé sera exécuté.

Enfin le dernier type de directive est include qui permet de composer des pages HTML à partir de plusieurs fragments:

<%@ include file="header.jsp" %>

Comme les balises de code, les directives sont anciennes et peuvent dans la grande majorité des cas être remplacées par une approche plus moderne et plus élégante.

Portée des objets

Nous avons vu qu’un objet instancié dans la servlet-contrôleur pouvait être mis à disposition de la vue en l’affectant à l’objet-requête request. Il existe plus généralement quatre portées de visibilité des objets dans votre application, request étant l’une d’entre elles. Un objet placé dans une portée est accessible par tous les composants de l’application (par exemple les JSP) qui ont accès à cette portée. Nous avons donc:

  • la portée page: tout objet instancié dans une JSP est accessible dans cette même JSP; c’est la portée la plus limitée;
  • la portée request: les objets placés dans cette portée restent accessibles durant toute l’exécution d’une requête HTTP par le serveur, même si l’exécution de cette requête implique plusieurs contrôleurs (par des mécanismes de forward) ou plusieurs JSP;
  • la portée session, une session étant constituée par un ensemble d’interactions client/serveur, un objet placé dans la session (par exemple le fameux panier des achats dans un site de commerce) reste accessible sur la période couverte par ces interactions;
  • enfin la portée application est la plus générale de toute: un objet placé dans cette portée reste accessible tant que l’application est active dans le conteneur de servlets.

La figure Les portées des variables en JEE. illustre les portées des variables.

_images/porteeJ2EE.png

Figure 1: Les portées des variables en JEE.

Note

Le protocole HTTP ne mémorise pas les échanges entre un client et un serveur. Pour effectuer un suivi, on doit donc simuler des sessions, le plus souvent en se basant sur les cookies.

Au sein d’une JSP, on peut accéder à une variable en indiquant la portée dans laquelle elle a été placée: requestScope.monbean par exemple est l’objet monBean placé au préalable comme attribut dans l’objet request.

Avertissement

Si la portée n’est pas indiquée, le conteneur va explorer les portées pour chercher un objet portant le nom indiqué. Cette pratique est potentiellement source d” ambiguité et d’erreurs difficiles à détecter: je vous conseille de toujours indiquer la portée.

Les expressions

Que faire d’un objet accessible dans un JSP? Le plus courant est d’accéder à leurs propriétés (pour les afficher dans la vue) avec une syntaxe minimaliste basée sur un langage d’expressions. Une expression désigne, en programmation, toute instruction ou ensemble d’instructions renvoyant une valeur (fonction, opération arithmétique, etc.). En JSP on garde la même définition. Les expressions sont présentées sous la forme:

${expression}

Quand la JSP est exécutée, la valeur de l’expression (après évaluation par le moteur de servlet) est insérée dans la page HTML produite, en substitution de l’expression. C’est une simplification syntaxique de la balise ancienne des JSP <%= %>. Par exemple:

${ 12 + 8 }

sera remplacé par 20 à l’exécution. Intéressant? Peut-être mais la principale utilisation des expressions est l’accès aux propriétés des objets. Pour reprendre l’exemple plus haut:

${requestScope.monbean.maprop}

a pour effet d’afficher la propriété maprop de l’objet monbean placé dans la portée request. Si cette propriété n’est pas publique (ce qui est toujours recommandé), le conteneur de servlet va chercher une méthode getMaprop() (un « getter » selon les conventions JavaBeans).

Plus généralement le conteneur va chercher à « deviner » quelle est l’interprétation naturelle de l’expression. Si votre objet mamap par exemple est une HashMap et contient une entrée indexée par bill, alors:

${requestScope.mamap.bill}

renverra la valeur de l’entrée mamap['bill']. Encore une fois il s’agit d’une tentative radicale de simplifier l’inclusion dans des pages HTML de valeurs représentées dans des structures Java. Les expressions sont tolérantes aux objets manquants. Par exemple:

${requestScope.mampa.bill}

ne renverra rien, (notez que mampa est mal écrit?) alors que l’objet n’existe pas dans la portée et qu’une exception null value devrait être levée.

Objets implicites

L’objet requestScope que nous utilisons dans les exemples qui précèdent est un objet implicite accessible par les expressions. Il en existe d’autres, l’ensemble constituant un contexte de communication entre le contrôleur (la servlet) et la vue (la page JSP). Le moment est venu de les récapituler (Tableau Objets implicites dans une page JSP). Notez que la plupart sont des tables d’association (Map).

Objets implicites dans une page JSP
Nom Type Description
pageContext   Informations sur l’environnement du serveur.
pageScope Map Attributs de portée page.
requestScope Map Attributs de portée request.
sessionScope Map Attributs de portée session.
applicationScope Map Attributs de portée application.
param Map Les paramètres de la requête HTTP, pour les paramètres monovalués.
paramValues Map Les paramètres de la requête HTTP, pour les paramètres multivalués (chaque valeur d’un attribut est un tableau de String).
header Map Les paramètres de l’entête HTTP, pour les paramètres monovalués.
headerValues Map Les paramètres de l’entête HTTP, pour les paramètres multivalués (chaque valeur d’un attribut est un tableau de String).
cookie Map Les cookies.
initParam Map Les paramètres du fichier web.xml.

Prudence

Il est évidemment tout à fait déconseillé de créer un objet portant le même nom que ceux du tableau Objets implicites dans une page JSP.

Voici maintenant quelques exercices pour mettre en pratique les notions couvertes par cette session.

Exercice: affichage des paramètres d’une requête

Créez une page JSP jspparam.jsp qui accepte trois paramètres, nommés p1, p2 et p3. Créez également un contrôleur JspParam associé à une URL jspparam. On passe donc les paramètres avec une URL de la forme /jspparam?p1=xx&p2=yy&p3=zz. Affichez les valeurs de ces paramètres dans la page.

S2: Les tag libraries: JSTL

Supports complémentaires :

Comme indiqué au début de ce chapitre, une évolution majeure des JSP a consisté à les rendre extensibles en permettant la définition de balises fonctionnelles associées à une librairie Java. Le placement d’une de ces balises dans une page JSP correspond à l’exécution d’une fonctionnalité dans la librairie. C’est un moyen élégant de faire appel à des éléments dynamiques (programmés) dans une vue, sans avoir à intégrer du code Java.

Ces bibliothèques de balise peuvent être définies par tout programmeur, mais certaines sont destinées à résoudre des problèmes récurrents, et à ce titre elles sont fournies sous la forme d’une librairie standard, la Java Standard Tag Library ou JSTL. Nous allons brièvement l’explorer dans cette section, en nous basant sur la version 1.2.

Installation sous Tomcat.

Pour que les balises de la JSTL soit reconnues par Tomcat, il faut installer les deux fichiers suivants :

Récupérez ces fichiers à partir des liens ci-dessus et copiez-les sous WEB-INF/lib. Il peut être nécessaire de redémarrer Tomcat pour que les librairies soient prises en compte.

Une fois les librairies mises en place, on peut les utiliser dans des JSP. Voici un exemple simplissime.

<%@ page language="java" contentType="text/html; charset=UTF-8"
     pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Ma première JSTL</title>
</head>

<body>
  <c:out value="test" />
 </body>
</html>

Deux choses sont à noter. Tout d’abord la librairie est incluse avec la directive taglib:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

L’URI (Universal Resource Identifier) identifie la librairie, que Tomcat va donc chercher dans son environnement. Si vous avez bien installé le jar comme indiqué ci-dessus, il devrait le trouver dans WEB-INF/lib.

Le préfixe c var servir, lui, à reconnaître les balises de la librairie. C’est par exemple:

<c:out value="test" />

Le préfixe agit comme un espace de nommage au sens de XML. Toutes les balises dont le nom est préfixé par c: sont considérées comme appartenant à la JSTL, et cette dernière leur associe une fonctionnalité particulière. Dans notre exemple, il n’est pas trop difficile d’imaginer que c:out permet d’afficher une valeur dans le document HTML produit. Nous allons explorer les principales balises de la JSTL. Je vous invite à prendre les fragments de code et à les inclure dans notre petite JSP ci-dessus pour les tester.

Variables et expressions

La librairie JSTL est étroitement associée aux expressions. On peut donc combiner une expression, et l’affichage de son résultat, comme dans:

<c:out value="${2+2}" />

Vous me direz: pourquoi ne pas placer directement l’expression au lieu de l’inclure dans une balise. La réponse est que JSTL effectue un pré-traitement de la chaîne de caractères à afficher, pour la rendre compatible avec XHTML. Les caractères réservés comme <, >, &, sont remplacé par des références à des entités, comme &lt;, &gt, &amp;, etc. Cela vous évite de le faire vous-mêmes. Si, par exemple, vous voulez afficher littéralement des fragments de code (comme on en trouve dans ce document), comme:

<langageC>if (x > 2 && x < 5) {}</langageC>

La bonne option est d’utiliser c:out:

<c:out value="<langageC>if (x > 2 && x < 5) {}"/>

Je vous invite à tester la différence.

Important

La protection des caractères interdits est particulièrement importante quand on veut afficher du texte provenant d’une source externe (formulaire, base de données) pour éviter les attaques d’injection (de code javascript par exemple);

La balise c:out accepte une valeur par défaut si l’expression échoue (par exemple parce qu’un objet n’existe pas):

<c:out value="${bean}" default="Où est mon bean??" />

La balise c:set sert à définir ou modifier une variable et à lui affecter une valeur:

<c:set var="uneVariable" value="${17 * 23}" />

On peut ensuite l’afficher avec ${uneVariable}. Attention cependant à ne pas vous lancer dans de la programmation compliquée dans la vue. Gardez l’objectif …. en vue: nous sommes là pour afficher des informations produites par le modèle et le contrôleur, pas pour développer une algorithmique liée à l’application.

Conditions et boucles

Supposons que vous souhaitiez afficher un message ou un fragment HTML en fonction d’une certaine condition. Par exemple, vous voulez afficher une message de bienvenue si l’utilisateur est connecté, un formulaire de connexion sinon. Vous avez besoin de tester la valeur d’une variable. Avec les JSTL, c’est la balise <c:if>, avec un attribut test contenant une expression Booléenne. Voici un exemple en supposant que vous avez défini une variable uneVariable comme ci-dessus:

<c:if test="${uneVariable > 200 }">
   Ma variable vaut plus que 200.
</c:if>

Pour des tests un peu plus étendus, on peut recourir à une variante du case/switch:

<c:choose>
   <c:when test="${uneVariable == 0}">Variable null</c:when>
   <c:when test="${uneVariable > 0 && uneVariable < 200}">Valeur modérée</c:when>
  ...
  <c:otherwise>Sinon...</c:otherwise>
</c:choose>

La possibilité d’exprimer des boucles est également indispensable dans une vue pour traiter des variables de type liste ou ensemble. Premier cas, le plus simple (mais pas le plus courant): on dispose d’une variable de type tableau indicé. Nous avons donc besoin d’un moyen d’énumérer toutes les valeurs de l’indice. Le voici:

<table>
        <c:forEach var="i" begin="0" end="5" step="1">
                <tr>
                        <td><c:out value="${i}" /></td>
                        </tr>
        </c:forEach>
</table>

Je pense que l’exemple se passe de longues explications: il s’agit ni plus ni moins d’une mise à la sauce « balises HTML » de la boucle for d’un langage de programmation classique. Plus intéressant: dans le cas ou la variable correspond à une collection dont on ne connaît pas à priori la taille (HashMap, ArrayMap, Collection, typiquement), la boucle JSTL prend une forme légèrement différente. Supposons que la collection soit dans une variable personnes. On la parcourt de la manière suivante:

<c:forEach items="${personnes}" var="personne">
   <c:out value="${personne.nom}" />
</c:forEach>

Une variable locale à la boucle, de nom personne, est ici définie et successivement associée à toutes les valeurs de la collection. Dans la boucle, on se retrouve donc en situation d’avoir une variable simple, objet, à laquelle on peut appliquer les opérations déjà vues d’accès aux propriétés et d’affichage.

L’objet implicite pageContext est un peu différent. Ce n’est pas une Map mais un objet donnant accès au contexte de l’exécution d’une JSP par l’ensemble des attributs suivants:

Attributs de l’objet pageContext
Nom Type Description
request ServletRequest L’objet request
response ServletResponse L’objet response
servletConfig ServletConfig Configuration de la servlet
servletContext ServletContext Contexte de la servlet
session HttpSession La session

Je vous laisse recourir au Web pour des exemples d’utilisation de ces informations. Retenez qu’elles existent et sont accessibles dans la vue.

Liens

À priori un lien peut être directement inclus comme du HTML, sous la forme standard d’une ancre <a/>. Par exemple:

http://orm.bdpedia.fr

Dans de nombreux cas les choses sont plus compliquées, et des éléments dynamiques interviennent dans la création de l’URL du lien. Voici les principaux cas:

  • le lien mène à une autre page du site: faut-il indiquer une adresse relative ou une adresse absolue, et dans ce dernier cas laquelle?
  • le lien comprend des paramètres: leur valeur doit être encodée selon la norme HTTP pour survivre au transfert réseau;
  • enfin citons une fonctionnalité sur laquelle nous revenons plus loin: la gestion des sessions.

La balise JSTL <c:url> fournit de manière transparente un support pour gérer les besoins ci-dessus. Voici une brève présentation, en commençant par la gestion du contexte. Souvenez-vous de notre première page JSP. Nous y avions introduit une URL de la forme:

${pageContext.request.contextPath}/convertisseur

L’URL interne est /convertisseur. Pour ne pas dépendre, dans l’URL complète, du contexte qui peut varier (dev pour le développeur, testapp pour la machine d’intégration, appname, etc.), nous avons introduit un contexte dynamique représenté par ${pageContext.request.contextPath}. Nous savons maintenant qu’il s’agit d’une expression, mais nous n’avions pas encore la possibilité d’utiliser les extensions JSTL. Avec ces dernières, le lien devient simplement:

<c:url value="/convertisseur"/>

Notez bien que cette adresse est absolue. Elle est automatiquement étendue par le conteneur de servlet en ajoutant le chemin de l’application. Pour nous, cela donne:

/nsy135/convertisseur

Faut-il préciser qu’il est extrêmement important de ne pas coder « en dur » des valeurs dépendants de la configuration dans du code? Le tag c:url est intégrable tel-quel dans une balise HTML a, ce qui conne une syntaxe un peu étrange:

<a href="<c:url value="/convertisseur"/>">Lien vers mon convertisseur</a>

Le second avantage de c:url est la prise en charge automatique de l’encodage des valeurs de paramètre. La syntaxe pour les paramètres est la suivante:

<c:url value="/convertisseur">
  <c:param name="param1" value="${personne.nom}"/>
  <c:param name="param2" value="${societe.id}"/>
</cr:url>

Si un paramètre contient des caractères accentués, des espaces, ou autres caractères interdits, la balise <c:param> sera encodée selon les règles décrites ici: http://www.w3schools.com/TAGs/ref_urlencode.asp.

Note

D’autres balises non décrites ici sont <c:redirect>``(envoie une directive de redirection au navigateur) et ``<c:import> pour importer des fragments HTML. Je vous laisse recourir à un tutorial JSTL récent si vous souhaitez comprendre ces balises. Notez également que la partie du JSTL présentée ici est la librairie core. Il existe quelques autres librairies d’un intérêt moins central: xml (traitement de documents XML), fmt (formatage, par exemple des dates), fn (chaînes de caractères).

Exercice: affichage des objets implicites

Reprenez les objets implicites du tableau Objets implicites dans une page JSP et affichez leur contenu. Par exemple, affichez le contenu du tableau header. Vous devez exprimer une boucle qui récupère un à un les élements, chacun comprenant une clé key et une valeur value.

Exercice: affichage des paramètres saisis dans un formulaire

Nous allons faire un premier essai d’association formulaire HTML/page de traitement. Voici un formulaire simple (mais notez qu’il comprend un champ select à choix multiple.

<form method="get" action='jstlparam.jsp'>
     <table>
           <tr>
                   <td>Prénom:</td>
                   <td><input type='text' name='prenom' /></td>
           </tr>
           <tr>
                   <td>Nom:</td>
                   <td><input type='text' name='nom' /></td>
           </tr>
           <tr>
                   <td>Langages que vous connaissez:</td>
                   <td><select name='langages' size='7' multiple='true'>
                                   <option value='C'>C</option>
                                   <option value='C++'>C++</option>
                                   <option value='Objective-C'>Objective-C</option>
                                   <option value='Java'>Java</option>
                   </select></td>
           </tr>
      </table>
      <p>
           <input type='submit' value='Afficher!' />
 </form>

Ecrivez la page jstlparam.jsp qui affiche les paramètres transmis par ce formulaire. Attention: les attributs à choix multiples sont représentés dans l’objet implicite paramValue (donc, ici, on aura une collection paramValues.langage). Par curiosité, vous pouvez également afficher tout le contenu de paramValues.

Exercice: intégrer un template à nos JSP.

Cet exercice consiste à intégrer automatiquement nos fragments HTML produits par nos JSP dans un cadre de présentation du site agréable, doté de menus, d’une jolie image, etc. La méthode pour utiliser des templates est indiquée sur ce site: http://mobiarch.wordpress.com/2013/01/04/page-templating-using-jsp-custom-tag/

Voici un peu d’aide. Supposons que notre graphiste décide que toutes les pages seront affichées d’après le modèle suivant:

<html>
 <head>
  <title>Un titre</title>
 </head>
 <body>
  <!-- Partie dynamique: le contenu de la page -->
 </body>
</html>

Il ne s’est pas beaucoup fatigué, mais ce n’est pas notre problème. Nous allons créer un template à partir de ce modèle de page. Le voici:

 <%@tag description="Simple Template" pageEncoding="UTF-8"%>
 <%@attribute name="body_area" fragment="true" %>

 <html>
  <head>
  <title>Un titre</title>
 </head>
 <body>
   <jsp:invoke fragment="body_area"/>
 </body>
</html>

On comprend intuitivement que nous définissons des fragments HTML avec un nom (ici, body_area) et que nous indiquons avec <jsp:invoke> l’emplacement dans le template où doit être inséré le fragment. Sauvegardons ce template dans WEB-INF/tags/layout.tag.

Voici maintenant une JSP qui applique ce template.

 <%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

 <t:layout>
  <jsp:attribute name="body_area">
   <div>
    Je suis le contenu: <c:out value="${1+3}"/>
  </div>
 </jsp:attribute>
</t:layout>

La première ligne indique le répertoire où se trouvent les templates. Ensuite on définit le contenu des fragments à insérer dans les parties dynamiques (ici, body_area).

Nous avons déjà au début du cours récupéré un template HTML pour l’intégrer à notre serveur Tomcat. Nous allons le reprendre, et identifier dans le fichier principal (index.html) la partie correspondant au contenu (par opposition aux menus, entêtes, pieds de page, etc.) Il faut remplacer cette partie statique par le contenu du fragment produit par notre JSP. À vous de jouer.

Correction

Vous trouverez dans cette archive le code correspondant à une correction possible de cet exercice, et des précédents de ce chapitre.

Résumé: savoir et retenir