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.
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
).
# 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 balises 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 <
, >
, &
, 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:
# 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 impliciteparamValue
(donc, ici, on aura une collectionparamValues.langage
). Par curiosité, vous pouvez également afficher tout le contenu deparamValues
.
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.