Passons sur ce subtil jeu de mot, et revenons un instant sur notre premier exemple de page dynamique. Maintenant que nous connaissons la technologie JSP et les EL, nous sommes capables de remplacer le code Java que nous avions écrit en dur dans notre vue par quelque chose de propre, lisible et qui suit les recommandations MVC !
Nettoyons notre exemple
Pour rappel, voici où nous en étions après l'introduction d'un bean dans notre exemple :
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 | <%@ 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 ); String parametre = request.getParameter( "auteur" ); out.println( parametre ); %> </p> <p> Récupération du bean : <% com.sdzee.beans.Coyote notreBean = (com.sdzee.beans.Coyote) request.getAttribute("coyote"); out.println( notreBean.getPrenom() ); out.println( notreBean.getNom() ); %> </p> </body> </html> |
/WEB-INF/test.jsp
Avec tout ce que nous avons appris, nous sommes maintenant capables de modifier cette page JSP pour qu'elle ne contienne plus de langage Java ! Pour bien couvrir l'ensemble des méthodes existantes, divisons le travail en deux étapes : avec des scripts et balises JSP pour commencer, puis avec des EL.
Avec des scripts et balises JSP
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 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"); %> <%= attribut %> <% String parametre = request.getParameter( "auteur" ); %> <%= parametre %> </p> <p> Récupération du bean : <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" scope="request" /> <jsp:getProperty name="coyote" property="prenom" /> <jsp:getProperty name="coyote" property="nom" /> </p> </body> </html> |
/WEB-INF/test.jsp
Vous pouvez remarquer :
- l'affichage de l'attribut et du paramètre via la balise d'expression
<%= ... %>
; - la récupération du bean depuis la requête via la balise
<jsp:useBean>
; - l'affichage du contenu des propriétés via les balises
<jsp:getProperty>
.
Quel est l'objet implicite utilisé ici pour récupérer le bean "coyote" ?
Lorsque vous utilisez l'action :
1 | <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" scope="request" />
|
Celle-ci s'appuie derrière les rideaux sur l'objet implicite request (HttpServletRequest
) : elle cherche un bean nommé "coyote" dans la requête, et si elle n'en trouve pas elle en crée un et l'y enregistre. De même, si vous aviez précisé "session" ou "application" dans l'attribut scope de l'action, alors elle aurait cherché respectivement dans les objets session (HttpSession
) et application (ServletContext
).
Enfin, lorsque vous ne précisez pas d'attribut scope :
1 | <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" />
|
Ici, l'action s'appuie par défaut sur l'objet implicite page (this
) : elle cherche un bean nommé "coyote" dans la page courante, et si elle n'en trouve pas elle en crée un et l'y enregistre.
En fin de compte notre exemple est déjà bien plus propre qu'avant, mais nous avons toujours besoin de faire appel à du code Java pour récupérer et afficher nos attributs et paramètres depuis la requête…
Avec des EL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <%@ 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> ${test} ${param.auteur} </p> <p> Récupération du bean : ${coyote.prenom} ${coyote.nom} </p> </body> </html> |
/WEB-INF/test.jsp
Ça se passe de commentaires… Vous comprenez maintenant pourquoi je vous ai annoncé dans le chapitre précédent qu'une fois les EL découvertes, vous n'utiliserez plus jamais le reste ? Leur simplicité d'utilisation est déconcertante, et notre page ne contient désormais plus une seule ligne de Java !
Pourquoi est-ce que nous n'utilisons aucun objet implicite pour accéder aux attributs test et coyote ?
J'ai jusqu'à présent lâchement évité ce sujet pour ne pas vous embrouiller, mais cette question mérite effectivement d'être posée : comment notre expression EL est-elle capable de trouver nos différents objets ? Quand nous avions découvert les actions standard, le problème ne se posait pas, car il était nécessaire de préciser dans quelle portée nous souhaitions récupérer un objet, sans quoi l'action cherchait par défaut dans la portée page…
Eh bien le fonctionnement de la technologie EL est fondamentalement différent de celui des actions standard. Dans une expression EL, lorsque vous accédez à un objet présent dans une des quatre portées de votre application, vous n'avez pas besoin de spécifier l'objet implicite (c'est-à-dire la portée) auquel vous souhaitez accéder. D'ailleurs vous le voyez bien dans notre exemple, nous n'avons pas écrit ${request.test}
pour accéder à l'objet test présent dans la portée request, ni ${request.coyote.prenom}
pour accéder au bean coyote présent lui aussi dans la portée request.
D'ailleurs, si vous aviez fait ainsi… ça n'aurait pas fonctionné ! N'oubliez pas que l'objet implicite request ne représente pas la portée request, mais directement l'objet requête en cours d'utilisation. En réalité, le mécanisme de la technologie EL est un peu évolué : une expression est capable de réaliser d'elle-même un parcours automatique des différentes portées accessibles à la recherche d'un objet portant le nom précisé, de la plus petite à la plus grande portée.
Comment ce mécanisme fonctionne-t-il ?
Vous vous souvenez de l'objet implicite pageContext ? Je vous l'avais présenté comme celui qui donne accès à toutes les portées…
Concrètement, lorsque vous écrivez par exemple l'expression ${test}
dans votre JSP, derrière les rideaux le conteneur va se rendre compte qu'il ne s'agit pas d'un objet implicite mais bien d'un objet de votre création, et va appeler la méthode findAttribute()
de l'objet PageContext
. Ensuite, cette méthode va à son tour parcourir chacune des portées - page, puis request, puis session et enfin application - pour retourner le premier objet nommé "test" trouvé !
La bonne pratique de développement est de ne jamais donner le même nom à des objets existant dans des portées différentes ! Par exemple, si vous écrivez l'expression ${test}
pour cibler un objet nommé "test" que vous avez enregistré en session, alors qu'il existe un autre objet nommé "test" en requête, puisque la portée request sera toujours parcourue avant la portée session lors de la recherche automatique du mécanisme EL, c'est l'objet enregistré dans la requête qui vous sera renvoyé…
Dans ce cas, comment éviter ce parcours automatique et cibler directement une portée ?
Pour cela, il faut utiliser les objets implicites fournis par la technologie EL donnant accès aux attributs existants : pageScope, requestScope, sessionScope et applicationScope. Ainsi, dans notre précédent exemple nous aurions très bien pu écrire ${requestScope.test}
à la place de ${test}
, et cela aurait fonctionné tout aussi bien. Lors de l'analyse de l'expression EL, le conteneur aurait ainsi reconnu l'objet implicite requestScope, et n'aurait pas effectué le parcours des portées : il aurait directement recherché un objet nommé "test" au sein de la portée request uniquement.
La bonne pratique veut qu'en complément de la rigueur dans le nommage des objets conseillée précédemment, le développeur précise toujours la portée qu'il souhaite cibler dans une expression EL. Ainsi pour accéder à un objet présent par exemple dans la portée request, cette pratique recommande non pas d'écrire ${test}
, mais ${requestScope.test}
. En procédant ainsi, le développeur s'assure qu'aucun objet ne sera ciblé par erreur.
En ce qui me concerne dans la suite de ce cours, je prendrai toujours garde à ne jamais donner le même nom à deux objets différents. Je me passerai donc de préciser la portée dans chacune des expressions EL que j'écrirai dans mes exemples, afin de ne pas les alourdir. Il va de soi que lors du développement d'une vraie application web, je vous recommande de suivre les bonnes pratiques que je vous ai énoncées à l'instant.
Qu'en est-il du paramètre "auteur" ?
Notez bien que lorsque vous souhaitez cibler un objet qui n'est pas présent dans une des quatre portées, il est bien entendu nécessaire d'expliciter l'objet implicite qui permet d'y accéder au sein de l'expression EL. Voilà pourquoi pour accéder au paramètre de requête auteur, nous devons bien préciser ${param.auteur}
. Si nous avions simplement écrit ${auteur}
cela n'aurait pas fonctionné, car le mécanisme de recherche automatique aurait tenté de trouver un objet nommé "auteur" dans une des quatre portées - page, request, session puis application - et n'aurait logiquement rien trouvé. Rappelez-vous bien qu'un paramètre de requête est différent d'un attribut de requête !
Enfin, comprenez bien que je prends ici l'exemple d'un paramètre de requête pour illustrer le principe, mais que ceci est valable pour tout objet implicite différent des quatre portées : param comme nous venons de le voir, mais également header, cookie, paramValues, etc.
Complétons notre exemple...
Tout cela se goupille plutôt bien pour le moment, oui mais voilà… il y a un "mais". Et un gros même ! Vous ne vous en êtes peut-être pas encore aperçus, mais les EL, mêmes couplées à des balises JSP, ne permettent pas de mettre en place tout ce dont nous aurons couramment besoin dans une vue.
Prenons deux exemples pourtant très simples :
- nous souhaitons afficher le contenu d'une liste ou d'un tableau à l'aide d'une boucle ;
- nous souhaitons afficher un texte différent selon que le jour du mois est pair ou impair.
Eh bien ça, nous ne savons pas encore le faire sans Java !
Manipulation d'une liste
Reprenons notre exemple, en créant une liste depuis une servlet et en essayant d'afficher son contenu depuis la JSP :
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 | ... public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{ /* Création et initialisation du message. */ String paramAuteur = request.getParameter( "auteur" ); String message = "Transmission de variables : OK ! " + paramAuteur; /* Création du bean et initialisation de ses propriétés */ Coyote premierBean = new Coyote(); premierBean.setNom( "Coyote" ); premierBean.setPrenom( "Wile E." ); /* Création de la liste et insertion de quatre éléments */ List<Integer> premiereListe = new ArrayList<Integer>(); premiereListe.add( 27 ); premiereListe.add( 12 ); premiereListe.add( 138 ); premiereListe.add( 6 ); /* Stockage du message, du bean et de la liste dans l'objet request */ request.setAttribute( "test", message ); request.setAttribute( "coyote", premierBean ); request.setAttribute( "liste", premiereListe ); /* Transmission de la paire d'objets request/response à notre JSP */ this.getServletContext().getRequestDispatcher( "/WEB-INF/test.jsp" ).forward( request, response ); } |
Ajout d'une liste depuis la servlet
Rien de problématique ici, c'est encore le même principe : nous initialisons notre objet et nous le stockons dans l'objet requête pour transmission à la JSP. Regardons maintenant comment réaliser l'affichage des éléments de cette 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 27 28 29 30 | <%@ page pageEncoding="UTF-8" %> <%@ page import="java.util.List" %> <!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> ${test} ${param.auteur} </p> <p> Récupération du bean : ${coyote.prenom} ${coyote.nom} </p> <p> Récupération de la liste : <% List<Integer> liste = (List<Integer>) request.getAttribute( "liste" ); for( Integer i : liste ){ out.println(i + " : "); } %> </p> </body> </html> |
Récupération et affichage du contenu de la liste depuis la JSP
Voilà à quoi nous en sommes réduits : réaliser un import avec la directive page, pour pouvoir utiliser ensuite le type List
lors de la récupération de notre liste depuis l'objet requête, et afficher son contenu via une boucle for. Certes, ce code fonctionne, vous pouvez regarder le résultat obtenu depuis votre navigateur. Mais nous savons d'ores et déjà que cela va à l'encontre du modèle MVC : souvenez-vous, le Java dans une page JSP, c'est mal !
Utilisation d'une condition
Voyons maintenant comment réaliser notre second exemple.
Puisque la mise en place d'une condition n'a vraiment rien de passionnant (vous savez tous écrire un if !), je profite de ce petit exemple pour vous faire découvrir une API très utile dès lors que votre projet fait intervenir la manipulation de dates : JodaTime.
Si vous avez déjà programmé en Java, vous avez très certainement déjà remarqué ce problème : la manipulation de dates en Java est horriblement peu intuitive ! Que ce soit via l'objet Date
ou via l'objet Calendar
, c'est très décevant et très loin de ce que l'on est en droit d'attendre d'une plate-forme évoluée comme Java !
Afin de pouvoir utiliser les méthodes et objets de cette API, il vous faut :
- télécharger l'archive nommée joda-time-2.1-dist disponible sur cette page, par exemple au format zip ;
- la décompresser et y chercher le fichier joda-time-2.1.jar ;
- l'inclure à votre application, en le plaçant sous le répertoire /WEB-INF/lib/ de votre projet (un simple glisser-déposer depuis votre fichier vers Eclipse suffit, voir la figure suivante).
Une fois le fichier .jar en place, vous pouvez alors utiliser l'API depuis votre projet.
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 | ... public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException{ /* Création et initialisation du message. */ String paramAuteur = request.getParameter( "auteur" ); String message = "Transmission de variables : OK ! " + paramAuteur; /* Création du bean et initialisation de ses propriétés */ Coyote premierBean = new Coyote(); premierBean.setNom( "Coyote" ); premierBean.setPrenom( "Wile E." ); /* Création de la liste et insertion de quatre éléments */ List<Integer> premiereListe = new ArrayList<Integer>(); premiereListe.add( 27 ); premiereListe.add( 12 ); premiereListe.add( 138 ); premiereListe.add( 6 ); /** On utilise ici la libraire Joda pour manipuler les dates, pour deux raisons : * - c'est tellement plus simple et limpide que de travailler avec les objets Date ou Calendar ! * - c'est (probablement) un futur standard de l'API Java. */ DateTime dt = new DateTime(); Integer jourDuMois = dt.getDayOfMonth(); /* Stockage du message, du bean, de la liste et du jour du mois dans l'objet request */ request.setAttribute( "test", message ); request.setAttribute( "coyote", premierBean ); request.setAttribute( "liste", premiereListe ); request.setAttribute( "jour", jourDuMois ); /* Transmission de la paire d'objets request/response à notre JSP */ this.getServletContext().getRequestDispatcher( "/WEB-INF/test.jsp" ).forward( request, response ); } |
Récupération du jour du mois depuis la servlet
Remarquez la facilité d'utilisation de l'API Joda. N'hésitez pas à parcourir par vous-mêmes les autres objets et méthodes proposés, c'est d'une simplicité impressionnante.
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 | <%@ page pageEncoding="UTF-8" %> <%@ page import="java.util.List" %> <!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> ${test} ${param.auteur} </p> <p> Récupération du bean : ${coyote.prenom} ${coyote.nom} </p> <p> Récupération de la liste : <% List<Integer> liste = (List<Integer>) request.getAttribute( "liste" ); for( Integer i : liste ){ out.println(i + " : "); } %> </p> <p> Récupération du jour du mois : <% Integer jourDuMois = (Integer) request.getAttribute( "jour" ); if ( jourDuMois % 2 == 0 ){ out.println("Jour pair : " + jourDuMois); } else { out.println("Jour impair : " + jourDuMois); } %> </p> </body> </html> |
Récupération du jour du mois et affichage d'un message dépendant de sa parité
Encore une fois, voilà à quoi nous en sommes réduits côté JSP : écrire du code Java pour faire un simple test sur un entier…
D'autant plus que je ne vous ai ici proposé que deux exemples basiques, mais nous pourrions lister bien d'autres fonctionnalités qu'il serait intéressant de pouvoir utiliser dans nos vues, et qui ne nous sont pas accessibles à travers la technologie JSP sans utiliser de scriptlets !
Dans ce cas, comment faire ? Comment ne pas écrire de code Java dans nos pages JSP ?
Eh bien des développeurs se sont posé la même question il y a plus de dix ans déjà, et ont imaginé la JSTL : une bibliothèque de balises préconçues qui permettent, à la manière des balises JSP, de mettre en place les fonctionnalités dont nous avons besoin couramment dans une vue, mais qui ne sont pas accessibles nativement au sein de la technologie JSP.
Le point sur ce qu'il nous manque
Avant d'attaquer l'apprentissage de cette fameuse JSTL, prenons deux minutes pour faire le point sur les limites de ce que nous avons appris jusqu'à présent.
La vue
Nous devons impérativement nettoyer nos lunettes ! Nous savons afficher des choses basiques, mais dès que notre vue se complexifie un minimum, nous ne savons plus faire. Vous êtes déjà au courant, c'est vers la JSTL que nous allons nous tourner : l'intégralité de la partie suivante lui est d'ailleurs consacrée.
L'interaction
Vous ne vous en êtes peut-être pas encore rendu compte, mais nous n'avons qu'effleuré la récupération de données envoyées par le client ! Nous devons mettre en place de l'interaction : une application web qui ne demande rien à l'utilisateur, c'est un site statique ; nous, ce que nous souhaitons, c'est une application dynamique ! Pour le moment, nous avons uniquement développé des vues basiques, couplées à des servlets qui ne faisaient presque rien. Très bientôt, nous allons découvrir que les servlets auront pour objectif de TOUT contrôler : tout ce qui arrivera dans notre application et tout ce qui en sortira passera par nos servlets.
Les données
Nous devons apprendre à gérer nos données : pour le moment, nous avons uniquement découvert ce qu'était un bean. Nous avons une vague idée de comment seront représentées nos données au sein du modèle : à chaque entité de données correspondra un bean… Toutefois, nous nous heurtons ici à de belles inconnues : d'où vont venir nos données ? Qu'allons nous mettre dans nos beans ? Comment allons-nous sauvegarder les données qu'ils contiendront ? Comment enregistrer ce que nous transmet le client ? Nous devrons, pour répondre à tout cela, apprendre à manipuler une base de données depuis notre application. Ainsi, nous allons découvrir que notre modèle sera en réalité constitué non pas d'une seule couche, mais de deux ! Miam !
Documentation
On n'insistera jamais assez sur l'importance, pour tout zéro souhaitant apprendre quoi que ce soit, d'avoir recours aux documentations et ressources externes en général. Le Site du Zéro n'est pas une bible, tout n'y est pas ; pire, des éléments sont parfois volontairement omis ou simplifiés afin de bien vous faire comprendre certains points au détriment d'autres, jugés moins cruciaux.
Les tutoriaux d'auteurs différents vous feront profiter de nouveaux points de vue et angles d'attaque, et les documentations officielles vous permettront un accès à des informations justes et maintenues à jour (en principe).
Liens utiles
- Base de connaissances portant sur Tomcat et les serveurs d'applications, sur Tomcat's Corner
- À propos des servlets, sur stackoverflow.com
- À propos des JSP, sur stackoverflow.com
- À propos des expressions EL, sur stackoverflow.com
- Base de connaissances portant sur les servlets, sur novocode.com
- FAQ générale à propos du développement autour de Java, sur jguru.com
- Tutoriel sur les Expression Language, dans la documentation officielle J2EE 1.4 sur sun.com
- La syntaxe JSP, sur sun.com
Vous l'aurez compris, cette liste ne se veut pas exhaustive, et je vous recommande d'aller chercher par vous-mêmes l'information sur les forums et sites du web. En outre, faites bien attention aux dates de création des documents que vous lisez : les ressources périmées sont légion sur le web, notamment au sujet de la plate-forme Java EE qui est en constante évolution. N'hésitez pas à demander à la communauté sur le forum Java du Site du Zéro si vous ne parvenez pas à trouver l'information que vous cherchez.
- Les expressions EL remplacent élégamment scriptlets et actions standard.
- La technologie EL ne répond malheureusement pas à tous nos besoins.
- La documentation est indispensable, à condition qu'elle soit à jour.