Nous allons dans ce chapitre terminer l'apprentissage de la technologie JSP, à travers la découverte des expressions EL et des objets implicites.
Expression Language
Présentation
Dans cette seconde moitié, nous allons découvrir ensemble les bases de l'Expression Language, que l'on raccourcit très souvent EL.
Je répète ce dont je vous ai avertis dans la partie précédente : une fois que vous aurez assimilé cette technologie, vous n'aurez plus jamais à utiliser les actions standard d'utilisation des beans que nous venons de découvrir ! Rassurez-vous, je ne vous les ai pas présentées juste pour le plaisir : il est important que vous connaissiez ce mode de fonctionnement, afin de ne pas être surpris si un jour vous tombez dessus. Seulement c'est une approche différente du modèle MVC, une approche qui n'est pas compatible avec ce que nous apprenons ici.
En quoi consistent les expressions EL ?
Ces expressions sont indispensables à une utilisation optimale des JSP. C'est grâce à elles que l'on peut s'affranchir définitivement de l'écriture de scriptlets (du code Java, pour ceux qui n'ont pas suivi) dans nos belles pages JSP. Pour faire simple et concis, les expressions EL permettent via une syntaxe très épurée d'effectuer des tests basiques sur des expressions, et de manipuler simplement des objets et attributs dans une page, et cela sans nécessiter l'utilisation de code ni de script Java ! La maintenance de vos pages JSP, en fournissant des notations simples et surtout standard, est ainsi grandement facilitée.
Avant tout, étudions la forme et la syntaxe d'une telle expression :
1 | ${ expression } |
Ce type de notation ne devrait pas être inconnu à ceux d'entre vous qui ont déjà programmé en Perl. Ce qu'il faut bien retenir, c'est que ce qui est situé entre les accolades va être interprété : lorsqu'il va analyser votre page JSP, le conteneur va repérer ces expressions entourées d'accolades et il saura ainsi qu'il doit en interpréter le contenu. Aussi, ne vous étonnez pas si dans la suite de ce chapitre j'évoque l'intérieur d'une expression EL : je parle tout simplement de ce qui est situé entre les accolades !
La réalisation de tests
La première chose que vous devez savoir, c'est qu'à l'intérieur d'une expression, vous pouvez effectuer diverses sortes de tests. Pour réaliser ces tests, il vous est possible d'inclure toute une série d'opérateurs. Parmi ceux-ci, on retrouve les traditionnels :
- opérateurs arithmétiques, applicables à des nombres :
+
,-
,*
,/
,%
; - opérateurs logiques, applicables à des booléens :
&&
,||
,!
; - opérateurs relationnels, basés sur l'utilisation des méthodes
equals()
etcompareTo()
des objets comparés :==
oueq
,!=
oune
,<
oult
,>
ougt
,<=
oule
,>=
ouge
.
Légende :
- [le]: Lower or Equal
- [ne]: Not Equal
- [ge]: Greater or Equal
- [gt]: Greater Than
- [lt]: Lower Than
- [eq]: Equal
Voyons concrètement ce que tout cela donne à travers quelques exemples. Créez pour l'occasion une page nommée test_el.jsp à la racine de votre application, et placez-y ces quelques lignes :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des expressions EL</title> </head> <body> <p> <!-- Logiques sur des booléens --> ${ true && true } <br /> <!-- Affiche true --> ${ true && false } <br /> <!-- Affiche false --> ${ !true || false } <br /> <!-- Affiche false --> <!-- Calculs arithmétiques --> ${ 10 / 4 } <br /> <!-- Affiche 2.5 --> ${ 10 mod 4 } <br /> <!-- Affiche le reste de la division entière, soit 2 --> ${ 10 % 4 } <br /> <!-- Affiche le reste de la division entière, soit 2 --> ${ 6 * 7 } <br /> <!-- Affiche 42 --> ${ 63 - 8 } <br /> <!-- Affiche 55 --> ${ 12 / -8 } <br /> <!-- Affiche -1.5 --> ${ 7 / 0 } <br /> <!-- Affiche Infinity --> <!-- Compare les caractères 'a' et 'b'. Le caractère 'a' étant bien situé avant le caractère 'b' dans l'alphabet ASCII, cette EL affiche true. --> ${ 'a' < 'b' } <br /> <!-- Compare les chaînes 'hip' et 'hit'. Puisque 'p' < 't', cette EL affiche false. --> ${ 'hip' gt 'hit' } <br /> <!-- Compare les caractères 'a' et 'b', puis les chaînes 'hip' et 'hit'. Puisque le premier test renvoie true et le second false, le résultat est false. --> ${ 'a' < 'b' && 'hip' gt 'hit' } <br /> <!-- Compare le résultat d'un calcul à une valeur fixe. Ici, 6 x 7 vaut 42 et non pas 48, le résultat est false. --> ${ 6 * 7 == 48 } <br /> </p> </body> </html> |
/test_el.jsp
Rendez-vous alors sur http://localhost:8080/test/test_el.jsp, et vérifiez que les résultats obtenus correspondent bien aux commentaires que j'ai placés sur chaque ligne.
Remarquez la subtilité dévoilée ici dans l'exemple de la ligne 27, au niveau des chaînes de caractères : contrairement à du code Java, dans lequel vous ne pouvez déclarer une String
qu'en utilisant des double quotes (guillemets), vous pouvez utiliser également des simple quotes (apostrophes) dans une expression EL. Pour information, ceci a été rendu possible afin de simplifier l'intégration des expressions EL dans les balises JSP : celles-ci contenant déjà bien souvent leurs propres guillemets, cela évite au développeur de s'emmêler les crayons ! Pas de panique, vous comprendrez où je veux en venir dans la partie suivante, lorsque nous pratiquerons la JSTL !
Attention ici aux opérateurs relationnels :
- si vous souhaitez vérifier l'égalité d'objets de type non standard via une EL, il vous faudra probablement réimplémenter les méthodes citées dans le troisième point de la liste précédente ;
- si vous souhaitez effectuer une comparaison, il vous faudra vérifier que votre objet implémente bien l'interface
Comparable
.
Autrement dit, pas besoin de vous casser la tête pour un objet de type String
ou Integer
, pour lesquels tout est déjà prêt nativement, mais pour des objets de votre propre création et/ou de types personnalisés, pensez-y !
Les opérateurs suivent comme toujours un ordre de priorité : comme on a dû vous l'apprendre en cours élémentaire, la multiplication est prioritaire sur l'addition, etc. J'omets ici volontairement certaines informations, notamment certains opérateurs que je ne juge pas utile de vous présenter ; je vous renvoie vers la documentation officielle pour plus d'informations. Les applications que nous verrons dans ce cours ne mettront pas en jeu d'expressions EL très complexes, et vous serez par la suite assez à l'aise avec le concept pour comprendre par vous-même les subtilités de leur utilisation dans des cas plus élaborés.
En outre, deux autres types de test sont fréquemment utilisés au sein des expressions EL :
- les conditions ternaires, de la forme :
test ? si oui : sinon
; - les vérifications si vide ou
null
, grâce à l'opérateurempty
.
Très pratiques, ils se présentent sous cette forme :
1 2 3 4 5 6 7 8 9 | <!-- Vérifications si vide ou null --> ${ empty 'test' } <!-- La chaîne testée n'est pas vide, le résultat est false --> ${ empty '' } <!-- La chaîne testée est vide, le résultat est true --> ${ !empty '' } <!-- La chaîne testée est vide, le résultat est false --> <!-- Conditions ternaires --> ${ true ? 'vrai' : 'faux' } <!-- Le booléen testé vaut true, vrai est affiché --> ${ 'a' > 'b' ? 'oui' : 'non' } <!-- Le résultat de la comparaison vaut false, non est affiché --> ${ empty 'test' ? 'vide' : 'non vide' } <!-- La chaîne testée n'est pas vide, non vide est affiché --> |
Pour terminer, sachez enfin que la valeur retournée par une expression EL positionnée dans un texte ou un contenu statique sera insérée à l'endroit même où est située l'expression :
1 2 3 4 5 | <!-- La ligne suivante : --> <p>12 est inférieur à 8 : ${ 12 lt 8 }.</p> <!-- Sera rendue ainsi après interprétation de l'expression, 12 n'étant pas inférieur à 8 : --> <p>12 est inférieur à 8 : false.</p> |
La manipulation d'objets
Toutes ces fonctionnalités semblent intéressantes, mais ne nous serviraient pas à grand-chose si elles ne pouvaient s'appliquer qu'à des valeurs écrites en dur dans le code de nos pages, comme nous l'avons fait à l'instant dans nos exemples. La vraie puissance des expressions EL, leur véritable intérêt, c'est le fait qu'elles permettent de manipuler des objets et de leur appliquer tous ces tests ! Quels types d'objets ? Voyons cela au cas par cas…
Des beans
Sous la couverture, la technologie EL est basée sur les spécifications des JavaBeans. Qu'est-ce que cela signifie concrètement ? Eh bien tout simplement qu'il est possible via une expression EL d'accéder directement à une propriété d'un bean ! Pour illustrer cette fonctionnalité sans trop compliquer notre exemple, nous allons utiliser les actions standard que nous avons découvertes dans le chapitre précédent. Éditez le fichier test_el.jsp que nous avons mis en place, et remplacez son contenu par ce code :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des expressions EL</title> </head> <body> <p> <!-- Initialisation d'un bean de type Coyote avec une action standard, pour l'exemple : --> <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" /> <!-- Initialisation de sa propriété 'prénom' : --> <jsp:setProperty name="coyote" property="prenom" value="Wile E."/> <!-- Et affichage de sa valeur : --> <jsp:getProperty name="coyote" property="prenom" /> </p> </body> </html> |
/test_el.jsp
Grâce aux deux premières actions (lignes 10 et 12), la base de notre exemple est posée : nous créons un bean de type Coyote dans notre page JSP, et initialisons sa propriété prenom avec la valeur "Wile E.".
Vous retrouvez ensuite à la ligne 14 l'action qui affiche le contenu de cette propriété, et lorsque vous vous rendez sur http://localhost:8080/test/test_el.jsp, le résultat affiché par le navigateur est logiquement "Wile E.".
Maintenant, remplacez cette ligne 14 par l'expression EL suivante :
1 | ${ coyote.prenom } |
Actualisez alors la page de tests dans votre navigateur, et vous observerez que le contenu n'a pas changé, la valeur de la propriété prénom est toujours correctement affichée. Eh bien oui, c'est tout ce qu'il est nécessaire d'écrire avec la technologie EL ! Cette expression retourne le contenu de la propriété prenom du bean nommé coyote. Remarquez bien la syntaxe et la convention employées :
- coyote est le nom du bean, que nous avions ici défini dans l'attribut id de l'action
<jsp:useBean>
; - prenom est un champ privé du bean (une propriété) accessible par sa méthode publique
getPrenom()
; - l'opérateur point permet de séparer le bean visé de sa propriété.
Ainsi de manière générale, il suffit d'écrire ${ bean.propriete }
pour accéder à une propriété d'un bean. Simple et efficace !
Pour information, mais nous y reviendrons un peu plus tard, voici ce à quoi ressemble le code Java qui est mis en œuvre dans les coulisses lors de l'interprétation de l'expression ${ coyote.prenom }
:
1 2 3 4 5 6 7 | Coyote bean = (Coyote) pageContext.findAttribute( "coyote" ); if ( bean != null ) { String prenom = bean.getPrenom(); if ( prenom != null ) { out.print( prenom ); } } |
Peu importe que nous la comparions avec une scriptlet Java ou avec une action standard, l'expression EL simplifie l'écriture de manière frappante ! Ci-dessous, je vous propose d'étudier quelques exemples des utilisations et erreurs de syntaxe les plus courantes :
1 2 3 4 5 6 7 8 | <!-- Syntaxe conseillée pour récupérer la propriété 'prenom' du bean 'coyote'. --> ${ coyote.prenom } <!-- Syntaxe correcte, car il est possible d'expliciter la méthode d'accès à la propriété. Préférez toutefois la notation précédente. --> ${ coyote.getPrenom() } <!-- Syntaxe erronée : la première lettre de la propriété doit être une minuscule. --> ${ coyote.Prenom } |
Voilà pour la syntaxe à employer pour accéder à des objets. Maintenant comme je vous l'annonçais en début de paragraphe, la vraie puissance de la technologie EL réside non seulement dans le fait qu'elle permet de manipuler des beans, mais également dans le fait qu'elle permet de les faire intervenir au sein de tests ! Voici quelques exemples d'utilisations, toujours basés sur la propriété prenom du bean coyote :
1 2 3 4 5 6 7 8 | <!-- Comparaison d'égalité entre la propriété prenom et la chaîne "Jean-Paul" --> ${ coyote.prenom == "Jean-Paul" } <!-- Vérification si la propriété prenom est vide ou nulle --> ${ empty coyote.prenom } <!-- Condition ternaire qui affiche la propriété prénom si elle n'est ni vide ni nulle, et la chaîne "Veuillez préciser un prénom" sinon --> ${ !empty coyote.prenom ? coyote.prenom : "Veuillez préciser un prénom" } |
En outre, sachez également que les expressions EL sont protégées contre un éventuel retour null
:
1 2 3 4 5 6 7 8 9 10 11 | <!-- La scriptlet suivante affiche "null" si la propriété "prenom" n'a pas été initialisée, et provoque une erreur à la compilation si l'objet "coyote" n'a pas été initialisé : --> <%= coyote.getPrenom() %> <!-- L'action suivante affiche "null" si la propriété "prenom" n'a pas été initialisée, et provoque une erreur à l'exécution si l'objet "coyote" n'a pas été initialisé : --> <jsp:getProperty name="coyote" property="prenom" /> <!-- L'expression EL suivante n'affiche rien si la propriété "prenom" n'a pas été initialisée, et n'affiche rien si l'objet "coyote" n'a pas été initialisé : --> ${ coyote.prenom } |
Des collections
Les beans ne sont pas les seuls objets manipulables dans une expression EL, il est également possible d'accéder aux collections au sens large. Cela inclut donc tous les objets de type java.util.List
, java.util.Set
, java.util.Map
, etc.
Nous allons, pour commencer, reprendre notre page test_el.jsp, et remplacer son code par cet exemple illustrant l'accès aux éléments d'une liste :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des expressions EL</title> </head> <body> <p> <% /* Création d'une liste de légumes et insertion de quatre éléments */ java.util.List<String> legumes = new java.util.ArrayList<String>(); legumes.add( "poireau" ); legumes.add( "haricot" ); legumes.add( "carotte"); legumes.add( "pomme de terre" ); request.setAttribute( "legumes" , legumes ); %> <!-- Les quatre syntaxes suivantes retournent le deuxième élément de la liste de légumes --> ${ legumes.get(1) }<br /> ${ legumes[1] }<br /> ${ legumes['1'] }<br /> ${ legumes["1"] }<br /> </p> </body> </html> |
/test_el.jsp
Toujours afin de ne pas compliquer l'exemple, j'initialise directement une liste de légumes à l'aide d'une scriptlet Java. Rappelez-vous bien que je procède ainsi uniquement pour gagner du temps dans la mise en place de notre exemple, et que lors du développement d'une vraie application, il est hors de question d'écrire une page JSP aussi sale.
Vous découvrez aux lignes 20 à 23 quatre nouvelles notations :
- l'appel à une méthode de l'objet legumes. En l'occurrence notre objet est une liste, et nous appelons sa méthode
get()
en lui passant l'indice de l'élément voulu ; - l'utilisation de crochets à la place de l'opérateur point, contenant directement l'indice de l'élément voulu ;
- l'utilisation de crochets à la place de l'opérateur point, contenant l'indice de l'élément entouré d'apostrophes ;
- l'utilisation de crochets à la place de l'opérateur point, contenant l'indice de l'élément entouré de guillemets.
Chacune d'elles est valide, et retourne bien le second élément de la liste de légumes que nous avons initialisée (souvenez-vous, l'indice du premier élément d'une collection est 0 et non pas 1). Rendez-vous sur la page http://localhost:8080/test/test_el.jsp et constatez par vous-mêmes !
Remplacez ensuite le code de l'exemple par le suivant, illustrant l'accès aux éléments d'un tableau :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des expressions EL</title> </head> <body> <p> <% /* Création d'un tableau */ String[] animaux = {"chien", "chat", "souris", "cheval"}; request.setAttribute("animaux" , animaux); %> <!-- Les trois syntaxes suivantes retournent le troisième élément du tableau --> ${ animaux[2] }<br /> ${ animaux['2'] }<br /> ${ animaux["2"] }<br /> </p> </body> </html> |
/test_el.jsp
Rendez-vous à nouveau sur la page de tests depuis votre navigateur, et vous constaterez que les trois notations avec les crochets, appliquées à une liste dans le précédent exemple, fonctionnent de la même manière avec un tableau.
Sachez par ailleurs qu'il est impossible d'utiliser directement l'opérateur point pour accéder à un élément d'une liste ou d'un tableau. Les syntaxes ${entiers.1}
ou ${animaux.2}
enverront une exception à l'exécution.
Enfin, remplacez le code de l'exemple par le suivant, illustrant l'accès aux éléments d'une Map
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des expressions EL</title> </head> <body> <p> <% /* Création d'une Map */ java.util.Map<String,Integer> desserts = new java.util.HashMap<String, Integer>(); desserts.put("cookies", 8); desserts.put("glaces", 3); desserts.put("muffins", 6); desserts.put("tartes aux pommes", 2); request.setAttribute("desserts" , desserts); %> <!-- Les quatre syntaxes suivantes retournent la valeur associée à la clé "cookies" de la Map de desserts --> ${ desserts.cookies }<br /> ${ desserts.get("cookies") }<br /> ${ desserts['cookies'] }<br /> ${ desserts["cookies"] }<br /> <% /* Création d'une chaîne nommée "element" et contenant le mot "cookies" */ String element = "cookies"; request.setAttribute("element",element); %> <!-- Il est également possible d'utiliser un objet au lieu d'initialiser la clé souhaitée directement dans l'expression --> ${ desserts[element] }<br /> </p> </body> </html> |
/test_el.jsp
Rendez-vous une nouvelle fois sur la page de tests depuis votre navigateur, et remarquez deux choses importantes :
- la notation avec l'opérateur point fonctionne, à la ligne 21, de la même manière que lors de l'accès à une propriété d'un bean ;
- une expression peut en cacher une autre ! En l'occurrence à la ligne 32, lorsque le conteneur va analyser l'expression EL il va d'abord récupérer l'attribut de requête element, puis utiliser son contenu en guise de clé afin d'accéder à la valeur associée dans la
Map
de desserts.
Par ailleurs, faites bien attention à la syntaxe ${desserts[element]}
employée dans ce dernier cas :
- il est impératif de ne pas entourer le nom de l'objet d'apostrophes ou de guillemets au sein des crochets. En effet, écrire
${desserts["element"]}
reviendrait à essayer d'accéder à la valeur associée à la clé nommée "element", ce qui n'est pas le comportement souhaité ici ; - il ne faut pas entourer l'expression contenue dans l'expression englobante avec des accolades. Autrement dit, il ne faut pas écrire
${desserts[${element}]}
, cette syntaxe n'est pas valide. Une seule paire d'accolades suffit !
Puisque les notations avec l'opérateur point et avec les crochets fonctionnent toutes deux dans le cas d'une Map
, quelle est la notation recommandée ?
Une pratique efficace veut que l'on réserve la notation avec l'opérateur point pour l'accès aux propriétés d'un bean, et que l'on utilise les crochets pour accéder aux contenus d'une Map
. Ainsi en lisant le code d'une page JSP, il devient très simple de savoir si l'objet manipulé dans une expression EL est un bean ou une Map
.
Toutefois, ce n'est pas une obligation et vous êtes libres de suivre ou non cette bonne pratique dans vos projets, je ne vous conseille rien de particulier à ce sujet. Personnellement j'utilise dès que j'en ai l'occasion la notation avec l'opérateur point, car je trouve qu'elle a tendance à moins surcharger visuellement les expressions EL dans le code de mes pages JSP.
Pour clore sur ce sujet, je ne vous l'ai pas dit lorsque nous avons découvert la manipulation des beans dans une expression EL, mais sachez que l'opérateur point n'est pas la seule manière d'accéder à une propriété d'un bean, il est également possible d'utiliser la notation avec les crochets : ${bean["propriété"]}.
Désactiver l'évaluation des expressions EL
Le format utilisé par le langage EL, à savoir ${ ... }
, n'était pas défini dans les premières versions de la technologie JSP. Ainsi, si vous travaillez sur de vieilles applications non mises à jour, il est possible que vous soyez amenés à empêcher de telles expressions d'être interprétées, afin d'assurer la rétro-compatibilité avec le code. C'est pour cela qu'il est possible de désactiver l’évaluation des expressions EL :
- au cas par cas, grâce à la directive page que nous avons brièvement découverte dans le chapitre précédent ;
- sur tout ou partie des pages, grâce à une section à ajouter dans le fichier web.xml.
Avec la directive page
La directive suivante désactive l'évaluation des EL dans une page JSP :
1 | <%@ page isELIgnored ="true" %> |
Directive à placer en tête d'une page JSP
Les seules valeurs acceptées par l'attribut isELIgnored sont true
et false
:
- s'il est initialisé à
true
, alors les expressions EL seront ignorées et apparaîtront en tant que simples chaînes de caractères ; - s'il est initialisé à
false
, alors elles seront interprétées par le conteneur.
Vous pouvez d'ailleurs faire le test dans votre page test_el.jsp. Éditez le fichier et insérez-y en première ligne la directive. Enregistrez la modification et actualisez alors l'affichage de la page dans votre navigateur. Vous observerez que vos expressions ne sont plus évaluées, elles sont cette fois simplement affichées telles quelles.
Avec le fichier web.xml
Vous pouvez désactiver l'évaluation des expressions EL sur tout un ensemble de pages JSP dans une application grâce à l'option <el-ignored>
du fichier web.xml. Elle se présente dans une section de cette forme :
1 2 3 4 5 6 | <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <el-ignored>true</el-ignored> </jsp-property-group> </jsp-config> |
Section à ajouter dans le fichier web.xml
Vous connaissez déjà le principe du champ <url-pattern>
, que nous avons découvert lorsque nous avons mis en place notre première servlet. Pour rappel donc, il permet de définir sur quelles pages appliquer ce comportement. En l'occurrence, la notation *.jsp ici utilisée signifie que toutes les pages JSP de l'application seront impactées par cette configuration.
Le champ <el-ignored>
permet logiquement de définir la valeur de l'option. Le comportement est bien entendu le même qu'avec la directive précédente : si true
est précisé alors les EL seront ignorées, et si false
est précisé elles seront interprétées.
Comportement par défaut
Si vous ne mettez pas de configuration spécifique en place, comme celles que nous venons à l'instant de découvrir, la valeur de l'option isELIgnored va dépendre de la version de l'API servlet utiliséé par votre application :
- si la version est supérieure ou égale à 2.4, alors les expressions EL seront évaluées par défaut ;
- si la version est inférieure à 2.4, alors il est possible que les expressions EL soient ignorées par défaut, pour assurer la rétro-compatibilité dont je vous ai déjà parlé.
Comment savoir si une application permet d'utiliser les expressions EL ou non ?
Pour commencer, il faut s'assurer de la version de l'API servlet supportée par le conteneur qui fait tourner l'application. En l'occurrence, nous utilisons Tomcat 7 et celui-ci implémente la version 3.0 : tout va bien de ce côté, notre conteneur est capable de gérer les expressions EL.
Ensuite, il faut s'assurer que l'application est déclarée correctement pour utiliser cette version. Cela se passe au niveau de la balise <web-app>
du fichier web.xml. Lorsque nous avions mis en place celui de notre projet, je vous avais dit de ne pas vous soucier des détails et de vous contenter d'écrire :
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> ... </web-app> |
/WEB-INF/web.xml
La mise en place de tous ces attributs implique que notre application va se baser sur la version 3.0 de l'API servlet.
Ainsi, puisque notre conteneur le supporte et que notre application est correctement configurée, nous pouvons en déduire que les expressions EL seront interprétées par défaut dans notre application.
Si nous changeons de serveur d'applications, que va-t-il se passer ?
Eh oui, si jamais vous déployez votre application sur un autre serveur que Tomcat 7, il est tout à fait envisageable que la version supportée par le conteneur utilisé soit différente. Si par exemple vous travaillez sur une application un peu ancienne tournant sur un conteneur Tomcat 6, alors la version maximum de l'API servlet supportée est 2.5. Voici alors comment vous devrez déclarer votre application :
1 2 3 4 5 6 7 8 9 10 | <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> ... </web-app> |
/WEB-INF/web.xml
Cette introduction à la technologie EL se termine là. Il y en a bien assez à dire sur le sujet pour écrire un tutoriel entier, mais ce que nous avons appris ici nous suffira dans la plupart des cas. Retenez bien que ces expressions vont vous permettre de ne plus faire intervenir de Java dans vos JSP, et puisque vous savez maintenant de quoi il retourne, vous ne serez pas surpris de retrouver la syntaxe ${...}
dans tous les futurs exemples de ce cours.
Les objets implicites
Il nous reste un concept important à aborder avant de passer à la pratique : les objets implicites. Il en existe deux types :
- ceux qui sont mis à disposition via la technologie JSP ;
- ceux qui sont mis à disposition via la technologie EL.
Les objets de la technologie JSP
Pour illustrer ce nouveau concept, revenons sur la première JSP que nous avions écrite dans le chapitre sur la transmission des données :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <%@ page pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test</title> </head> <body> <p>Ceci est une page générée depuis une JSP.</p> <p> <% String attribut = (String) request.getAttribute("test"); out.println( attribut ); %> </p> </body> </html> |
/WEB-INF/test.jsp
Je vous avais alors fait remarquer qu'à la ligne 13, nous avions directement utilisé l'objet out sans jamais l'avoir instancié auparavant. De même, à la ligne 12 nous accédions directement à la méthode request.getAttribute()
sans jamais avoir instancié d'objet nommé request…
Comment est-ce possible ?
Pour répondre à cette question, nous devons nous intéresser une nouvelle fois au code de la servlet auto-générée par Tomcat, comme nous l'avions fait dans le second chapitre de cette partie. Retournons donc dans le répertoire work du serveur, qui rappelez-vous est subtilisé par Eclipse, et analysons à nouveau le code du fichier test_jsp.java :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | ... public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html"); pageContext = _jspxFactory.getPageContext(this, request, response, null, true, 8192, true); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write("<!DOCTYPE html>\r\n"); out.write("<html>\r\n"); out.write(" <head>\r\n"); out.write(" <meta charset=\"utf-8\" />\r\n"); out.write(" <title>Test</title>\r\n"); out.write(" </head>\r\n"); out.write(" <body>\r\n"); out.write(" <p>Ceci est une page générée depuis une JSP.</p>\r\n"); out.write(" <p>\r\n"); out.write(" "); String attribut = (String) request.getAttribute("test"); out.println( attribut ); out.write("\r\n"); out.write(" </p>\r\n"); out.write(" </body>\r\n"); out.write("</html>"); } ... |
Extrait de la servlet auto-générée par Tomcat
Analysons ce qui se passe dans le cas de l'objet out :
- à la ligne 10, un objet nommé out et de type
JspWriter
est créé ; - à la ligne 24, il est initialisé avec l'objet writer récupéré depuis la réponse ;
- à la ligne 39, c'est tout simplement notre ligne de code Java, basée sur l'objet out, qui est recopiée telle quelle de la JSP vers la servlet auto-générée !
Pour l'objet request, c'est un peu différent. Comme je vous l'ai déjà expliqué dans le second chapitre, notre JSP est ici transformée en servlet. Si elle en diffère par certains aspects, sa structure globale ressemble toutefois beaucoup à celle de la servlet que nous avons créée et manipulée dans nos exemples jusqu'à présent. Regardez la ligne 3 : le traitement de la paire requête/réponse est contenu dans une méthode qui prend pour arguments les objets HttpServletRequest
et HttpServletResponse
, exactement comme le fait notre méthode doGet()
! Voilà pourquoi il est possible d'utiliser directement les objets request et response depuis une JSP.
Vous devez maintenant comprendre pourquoi vous n'avez pas besoin d'instancier ou de récupérer les objets out et request avant de les utiliser dans le code de votre JSP : dans les coulisses, le conteneur s'en charge pour vous lorsqu'il traduit votre page en servlet ! Et c'est pour cette raison que ces objets sont dits "implicites" : vous n'avez pas besoin de les déclarer de manière… explicite. Logique, non ?
Par ailleurs, si vous regardez attentivement le code ci-dessus, vous constaterez que les lignes 6 à 13 correspondent en réalité toutes à des initialisations d'objets : pageContext, session, application… En fin de compte, le conteneur met à votre disposition toute une série d'objets implicites, tous accessibles directement depuis vos pages JSP. En voici la liste :
Identifiant |
Type de l'objet |
Description |
---|---|---|
pageContext |
|
Il fournit des informations utiles relatives au contexte d'exécution. Entre autres, il permet d'accéder aux attributs présents dans les différentes portées de l'application. Il contient également une référence vers tous les objets implicites suivants. |
application |
|
Il permet depuis une page JSP d'obtenir ou de modifier des informations relatives à l'application dans laquelle elle est exécutée. |
session |
|
Il représente une session associée à un client. Il est utilisé pour lire ou placer des objets dans la session de l'utilisateur courant. |
request |
|
Il représente la requête faite par le client. Il est généralement utilisé pour accéder aux paramètres et aux attributs de la requête, ainsi qu'à ses en-têtes. |
response |
|
Il représente la réponse qui va être envoyée au client. Il est généralement utilisé pour définir le Content-Type de la réponse, lui ajouter des en-têtes ou encore pour rediriger le client. |
exception |
|
Il est uniquement disponible dans les pages d'erreur JSP. Il représente l'exception qui a conduit à la page d'erreur en question. |
out |
|
Il représente le contenu de la réponse qui va être envoyée au client. Il est utilisé pour écrire dans le corps de la réponse. |
config |
|
Il permet depuis une page JSP d'obtenir les éventuels paramètres d'initialisation disponibles. |
page |
objet |
Il est l'équivalent de la référence |
De la même manière que nous avons utilisé les objets request et out dans notre exemple précédent, il est possible d'utiliser n'importe lequel de ces neuf objets à travers le code Java que nous écrivons dans nos pages JSP…
Hein ?! Encore du code Java dans nos pages JSP ?
Eh oui, tout cela est bien aimable de la part de notre cher conteneur, mais des objets sous cette forme ne vont pas nous servir à grand-chose ! Souvenez-vous : nous avons pour objectif de ne plus écrire de code Java directement dans nos pages.
Les objets de la technologie EL
J'en vois déjà quelques-uns au fond qui sortent les cordes… Vous avez à peine digéré les objets implicites de la technologie JSP, je vous annonce maintenant qu'il en existe d'autres rendus disponibles par les expressions EL ! Pas de panique, reprenons tout cela calmement. En réalité, et heureusement pour nous, la technologie EL va apporter une solution élégante au problème que nous venons de soulever : nous allons grâce à elle pouvoir profiter des objets implicites sans écrire de code Java !
Dans les coulisses, le concept est sensiblement le même que pour les objets implicites JSP : il s'agit d'objets gérés automatiquement par le conteneur lors de l'évaluation des expressions EL, et auxquels nous pouvons directement accéder depuis nos expressions sans les déclarer auparavant. Voici un tableau des différents objets implicites mis à disposition par la technologie EL :
Catégorie |
Identifiant |
Description |
---|---|---|
JSP |
pageContext |
Objet contenant des informations sur l'environnement du serveur. |
Portées |
pageScope |
Une |
requestScope |
Une |
|
sessionScope |
Une |
|
applicationScope |
Une |
|
Paramètres de requête |
param |
Une |
paramValues |
Une |
|
En-têtes de requête |
header |
Une |
headerValues |
Une |
|
Cookies |
cookie |
Une |
Paramètres d’initialisation |
initParam |
Une |
La première chose à remarquer dans ce tableau, c'est que le seul objet implicite en commun entre les JSP et les expressions EL est le pageContext. Je ne m'attarde pas plus longtemps sur cet aspect, nous allons y revenir dans le chapitre suivant.
La seconde, c'est la différence flagrante avec les objets implicites JSP : tous les autres objets implicites de la technologie EL sont des Map
!
D'ailleurs, qu'est-ce que c'est que toute cette histoire de Map
et d'associations entre des noms et des valeurs ?
Ça peut vous paraître compliqué, mais en réalité c'est très simple. C'est un outil incontournable en Java, et nous venons d'en manipuler une lorsque nous avons découvert les expressions EL. Mais si jamais vous ne vous souvenez pas bien des collections Java, sachez qu'une Map
est un objet qui peut se représenter comme un tableau à deux colonnes :
- la première colonne contient ce que l'on nomme les clés, qui doivent obligatoirement être uniques ;
- la seconde contient les valeurs, qui peuvent quant à elles être associées à plusieurs clés.
Chaque ligne du tableau ne peut contenir qu'une clé et une valeur. Voici un exemple d'une Map<String, String>
représentant une liste d'aliments et leurs types :
Aliments (Clés) |
Types (Valeurs) |
---|---|
pomme |
fruit |
carotte |
légume |
boeuf |
viande |
aubergine |
légume |
… |
… |
Table: Exemple
Vous voyez bien ici qu'un même type peut être associé à différents aliments, mais qu'un même aliment ne peut exister qu'une seule fois dans la liste. Eh bien c'est ça le principe d'une Map
: c'est un ensemble d'éléments uniques auxquels on peut associer n'importe quelle valeur.
Quel est le rapport avec la technologie EL ?
Le rapport, c'est que comme nous venons de le découvrir, nos expressions EL sont capables d'accéder au contenu d'une Map
, de la même manière qu'elles sont capables d'accéder aux propriétés d'un bean. En guise de rappel, continuons notre exemple avec la liste d'aliments, et créons une page test_map.jsp, dans laquelle nous allons implémenter rapidement cette Map
d'aliments :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <%@ page import="java.util.Map, java.util.HashMap" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des Maps et EL</title> </head> <body> <p> <% Map<String, String> aliments = new HashMap<String, String>(); aliments.put( "pomme","fruit" ); aliments.put( "carotte","légume" ); aliments.put( "boeuf","viande" ); aliments.put( "aubergine","légume" ); request.setAttribute( "aliments", aliments ); %> ${ aliments.pomme } <br /> <!-- affiche fruit --> ${ aliments.carotte } <br /> <!-- affiche légume --> ${ aliments.boeuf } <br /> <!-- affiche viande --> ${ aliments.aubergine } <br /><!-- affiche légume --> </p> </body> </html> |
/test_map.jsp
J'utilise ici une scriptlet Java pour initialiser rapidement la Map
et la placer dans un attribut de la requête nommé aliments. Ne prenez bien évidemment pas cette habitude, je ne procède ainsi que pour l'exemple et vous rappelle que nous cherchons à éliminer le code Java de nos pages JSP ! Rendez-vous alors sur http://localhost:8080/test/test_map.jsp, et observez le bon affichage des valeurs. Comme je vous l'ai annoncé un peu plus tôt, j'utilise la notation avec l'opérateur point - ici dans les lignes 18 à 21 - pour accéder aux valeurs contenues dans la Map
, mais il est tout à fait possible d'utiliser la notation avec les crochets.
D'accord, avec des expressions EL, nous pouvons accéder au contenu d'objets de type Map
. Mais ça, nous le savions déjà… Quel est le rapport avec les objets implicites EL ?
Le rapport, c'est que tous ces objets sont des Map
, et que par conséquent nous sommes capables d'y accéder depuis des expressions EL, de la même manière que nous venons de parcourir notre Map
d'aliments ! Pour illustrer le principe, nous allons laisser tomber nos fruits et légumes et créer une page nommée test_obj_impl.jsp, encore et toujours à la racine de notre session, application, et y placer le code suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des objets implicites EL</title> </head> <body> <p> <% String paramLangue = request.getParameter("langue"); out.println( "Langue : " + paramLangue ); %> <br /> <% String paramArticle = request.getParameter("article"); out.println( "Article : " + paramArticle ); %> </p> </body> </html> |
/test_obj_impl.jsp
Vous reconnaissez aux lignes 10 et 15 la méthode request.getParameter()
permettant de récupérer les paramètres transmis au serveur par le client à travers l'URL. Ainsi, il vous suffit de vous rendre sur http://localhost:8080/test/test_obj_impl.jsp?langue=fr&article=782 pour que votre navigateur vous affiche ceci (voir la figure suivante).
Cherchez maintenant, dans le tableau fourni précédemment, l'objet implicite EL dédié à l'accès aux paramètres de requête… Trouvé ? Il s'agit de la Map
nommée param. La technologie EL va ainsi vous mettre à disposition un objet dont le contenu peut, dans le cas de notre exemple, être représenté sous cette forme :
Nom du paramètre (Clé) |
Valeur du paramètre (Valeur) |
---|---|
langue |
fr |
article |
782 |
Si vous avez compris l'exemple avec les fruits et légumes, alors vous avez également compris comment accéder à nos paramètres de requêtes depuis des expressions EL, et vous êtes capables de réécrire notre précédente page d'exemple sans utiliser de code Java ! Éditez votre fichier test_obj_impl.jsp et remplacez le code précédent par le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test des objets implicites EL</title> </head> <body> <p> Langue : ${ param.langue } <br /> Article : ${ param.article } </p> </body> </html> |
/test_obj_impl.jsp
Actualisez la page dans votre navigateur, et observez le même affichage que dans l'exemple précédent. Pratique et élégant, n'est-ce pas ?
D'accord, dans ce cas cela fonctionne bien : chaque paramètre a un nom unique, et est associé à une seule valeur quelconque. Mais qu'en est-il des lignes marquées avec ** dans le tableau ? Est-il possible d'associer un unique paramètre à plusieurs valeurs à la fois ?
Oui, il est tout à fait possible d'associer une clé à des valeurs multiples. C'est d'ailleurs tout à fait logique, puisque derrière les rideaux il s'agit tout simplement d'objets de type Map
! L'unique différence entre les objets implicites param et paramValues, ainsi qu'entre header et headerValues, se situe au niveau de la nature de l'objet utilisé dans la Map
et des valeurs qui y sont stockées :
- pour param et header, une seule valeur est associée à chaque nom de paramètre, via une
Map<String,String>
; - pour paramValues et headerValues par contre, ce sont plusieurs valeurs qui vont être associées à un même nom de paramètre, via une
Map<String,String[]>
.
Quand pouvons-nous rencontrer plusieurs valeurs pour un seul et même paramètre ?
Tout simplement en précisant plusieurs fois un paramètre d'URL avec des valeurs différentes ! Par exemple, accédez cette fois à la page de tests avec l'URL http://localhost:8080/test/test_obj_impl.jsp?langue=fr&article=782&langue=zh. Cette fois, la technologie EL va vous mettre à disposition un objet dont le contenu peut être représenté ainsi :
Nom du paramètre (Clé) |
Valeur du paramètre (Valeur) |
---|---|
langue |
[fr,zh] |
article |
782 |
La Map
permettant d'accéder aux valeurs du paramètre langue n'est plus une Map<String,String>
, mais une Map<String,String[]>
. Si vous ne modifiez pas le code de l'expression EL dans votre page JSP, alors vous ne pourrez qu'afficher la première valeur du tableau des langues, retournée par défaut lorsque vous utilisez l'expression ${param.langue}
.
Afin d'afficher la seconde valeur, il faut cette fois non plus utiliser l'objet implicite param, mais utiliser l'objet implicite nommé paramValues. Remplacez à la ligne 9 de votre fichier test_obj_impl.jsp l'expression ${param.langue}
par l'expression ${paramValues.langue[1]}
. Actualisez alors la page dans votre navigateur, et vous verrez alors s'afficher la valeur zh !
Le principe est simple : alors qu'auparavant en écrivant ${param.langue}
vous accédiez directement à la String
associée au paramètre langue, cette fois en écrivant ${paramValues.langue}
vous accédez non plus à une String
, mais à un tableau de String
. Voilà pourquoi il est nécessaire d'utiliser la notation avec crochets pour accéder aux différents éléments de ce tableau !
En l'occurrence, puisque seules deux langues ont été précisées dans l'URL, il n'existe que les éléments d'indices 0 et 1 dans le tableau, contenant les valeurs fr et zh. Si vous essayez d'accéder à un élément non défini, par exemple en écrivant ${paramValues.langue[4]}
, alors l'expression EL détectera une valeur nulle et n'affichera rien. De même, vous devez obligatoirement cibler un des éléments du tableau ici. Si vous n'écrivez que ${paramValues.langue}
, alors l'expression EL vous affichera la référence de l'objet Java contenant votre tableau…
Par ailleurs, sachez qu'il existe d'autres cas impliquant plusieurs valeurs pour un même paramètre. Prenons un exemple HTML très simple : un <select>
à choix multiples !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <form method="post" action=""> <p> <label for="pays">Dans quel(s) pays avez-vous déjà voyagé ?</label><br /> <select name="pays" id="pays" multiple="multiple"> <option value="france">France</option> <option value="espagne">Espagne</option> <option value="italie">Italie</option> <option value="royaume-uni">Royaume-Uni</option> <option value="canada">Canada</option> <option value="etats-unis">Etats-Unis</option> <option value="chine" selected="selected">Chine</option> <option value="japon">Japon</option> </select> </p> </form> |
Alors que via un <select>
classique, il n'est possible de choisir qu'une seule valeur dans la liste déroulante, dans cet exemple grâce à l'option multiple="multiple"
, il est tout à fait possible de sélectionner plusieurs valeurs pour le seul paramètre nommé pays. Eh bien dans ce genre de cas, l'utilisation de l'objet implicite paramValues est nécessaire également : c'est le seul moyen de récupérer la liste des valeurs associées au seul paramètre nommé pays !
Pour ce qui est de l'objet implicite headerValues par contre, sa réelle utilité est discutable. En effet, s'il est possible de définir plusieurs valeurs pour un seul paramètre d'un en-tête HTTP, celles-ci sont la plupart du temps séparées par de simples points-virgules et concaténées dans une seule et même String
, rendant l'emploi de cet objet implicite inutile. Bref, dans 99 % des cas, utiliser la simple Map
header est suffisant. Ci-dessous un exemple d'en-têtes HTTP :
1 2 3 4 5 6 7 8 9 | GET / HTTP/1.1 Host: www.google.fr User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive |
Vous remarquez bien dans cet exemple que chaque paramètre (Host, User-Agent, Accept, etc.) n'est défini qu'une seule fois, et que les valeurs sont simplement concaténées les unes à la suite des autres sur la même ligne.
Voilà donc la solution à notre problème : les objets implicites EL sont des raccourcis qui rendent l'accès aux différentes portées et aux différents concepts liés à HTTP extrêmement pratiques !
Nous allons nous arrêter là pour les explications sur les objets implicites, l'important pour le moment est que vous compreniez bien leur mode de fonctionnement. Ne vous inquiétez pas si vous ne saisissez pas l'utilité de chacun d'entre eux, c'est tout à fait normal, certains concepts vous sont encore inconnus. La pratique vous fera prendre de l'aisance, et j'apporterai de plus amples explications au cas par cas dans les exemples de ce cours. Avant de passer à la suite, un petit avertissement quant au nommage de vos objets.
Faites bien attention aux noms des objets implicites listés ci-dessus. Il est fortement déconseillé de déclarer une variable portant le même nom qu'un objet implicite, par exemple param ou cookie. En effet, ces noms sont déjà utilisés pour identifier des objets implicites, et cela pourrait causer des comportements plutôt inattendus dans vos pages et expressions EL. Bref, ne cherchez pas les ennuis : ne donnez pas à vos variables un nom déjà utilisé par un objet implicite.
Beaucoup de nouvelles notations vous ont été présentées, prenez le temps de bien comprendre les exemples illustrant l'utilisation des balises et des expressions. Lorsque vous vous sentez prêts, passez avec moi au chapitre suivant, et tentez alors de réécrire notre précédente page d'exemple JSP, en y faisant cette fois intervenir uniquement ce que nous venons d'apprendre !
- La technologie EL est fondée sur les JavaBeans et sur les collections Java, et existe depuis la version 2.4 de l'API Servlet.
- Les expressions EL remplacent les actions standard de manipulation des objets.
- Une expression EL permet d'effectuer des tests, interprétés à la volée lors de l'exécution de la page.
- L'interprétation des expressions EL peut être désactivée via une section dans le fichier web.xml.
- Un objet implicite n'est pas géré par le développeur, mais par le conteneur de servlets.
- Chaque objet implicite JSP donne accès à un objet mis à disposition par le conteneur.
- Chaque objet implicite EL est un raccourci vers des données de l'application.