Cet ensemble est consacré à l'apprentissage de la technologie JSP : nous y étudierons la syntaxe des balises, directives et actions JSP ainsi que le fonctionnement des expressions EL, et enfin nous établirons une liste de documentations utiles sur le sujet. Trop volumineux pour entrer dans un unique chapitre, j'ai préféré le scinder en deux chapitres distincts.
Ce premier opus a pour objectif de vous présenter les bases de la syntaxe JSP et ses actions dites standard, toutes illustrées par de brefs exemples.
Les balises
Balises de commentaire
Tout comme dans les langages Java et HTML, il est possible d'écrire des commentaires dans le code de vos pages JSP. Ils doivent être compris entre les balises <%--
et --%>
. Vous pouvez les placer où vous voulez dans votre code source. Ils sont uniquement destinés au(x) développeur(s), et ne sont donc pas visibles par l'utilisateur final dans la page HTML générée :
1 2 3 4 5 6 7 8 9 10 11 12 | <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Exemple</title> </head> <body> <%-- Ceci est un commentaire JSP, non visible dans la page HTML finale. --%> <!-- Ceci est un simple commentaire HTML. --> <p>Ceci est un simple texte.</p> </body> </html> |
Balises de déclaration
Cette balise vous permet de déclarer une variable à l'intérieur d'une JSP. Vous savez déjà et nous y reviendrons par la suite qu'il est déconseillé d'écrire du code Java dans vos JSP mais, la balise existant, je préfère vous la présenter. Si vous tombez dessus un jour, ne soyez pas déstabilisés :
1 | <%! String chaine = "Salut les zéros."; %> |
Il est possible d'effectuer plusieurs déclarations au sein d'un même bloc. Ci-dessous, les déclarations d'une variable puis d'une méthode :
1 2 3 4 5 6 | <%! String test = null; public boolean jeSuisUnZero() { return true; } %> |
Balises de scriptlet
Derrière ce mot étrange, un mélange atroce entre "script" et "servlet", se cache simplement du code Java. Cette balise, vous la connaissez déjà, puisque nous l'avons utilisée dans le chapitre précédent. Elle sert en effet à inclure du code Java au sein de vos pages mais, tout comme la balise précédente, elle est à proscrire dans la mesure du possible ! À titre d'information seulement donc, voici le tag en question, ici au sein d'une balise HTML <form>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <form action="/tirage" method="post"> <% for(int i = 1; i < 3; i++){ out.println("Numéro " + i + ": <select name=\"number"+i+"\">"); for(int j = 1; j <= 10; j++){ out.println("<option value=\""+j+"\">"+ j + "</option>"); } out.println("</select><br />"); } %> <br /> <input type="submit" value="Valider" /> </form> |
Oui je sais, c'est un exemple très moche, car il y a du code Java dans une JSP, code qui contient à son tour des éléments de présentation HTML… Mais c'est juste pour l'exemple ! Je vous préviens : le premier que je vois coder comme ça, je le pends à un arbre !
Balises d'expression
La balise d'expression est en quelque sorte un raccourci de la scriptlet suivante :
1 | <% out.println("Bip bip !"); %> |
Elle retourne simplement le contenu d'une chaîne. Voici sa syntaxe :
1 | <%= "Bip bip !" %> |
Notez bien l'absence de point-virgule lors de l'utilisation de ce raccourci.
Les directives
Les directives JSP permettent :
- d'importer un package ;
- d'inclure d'autres pages JSP ;
- d'inclure des bibliothèques de balises (nous y reviendrons dans un prochain chapitre) ;
- de définir des propriétés et informations relatives à une page JSP.
Pour généraliser, elles contrôlent comment le conteneur de servlets va gérer votre JSP. Il en existe trois : taglib, page et include.
Elles sont toujours comprises entre les balises <%@
et %>
, et hormis la directive d'inclusion de page qui peut être placée n'importe où, elles sont à placer en tête de page JSP.
Directive taglib
Le code ci-dessous inclut une bibliothèque personnalisée nommée maTagLib :
1 | <%@ taglib uri="maTagLib.tld" prefix="tagExemple" %> |
Je ne détaille pas, nous reviendrons plus tard sur ce qu'est exactement une bibliothèque et sur cet attribut "prefix".
Directive page
La directive page définit des informations relatives à la page JSP. Voici par exemple comment importer des classes Java :
1 | <%@ page import="java.util.List, java.util.Date" %> |
Ici, l'import de deux classes est réalisé : List
et Date
. Cette fonctionnalité n'est utile que si vous mettez en place du code Java dans votre page JSP, afin de rendre disponibles les différentes classes et interfaces des API Java. En ce qui nous concerne, puisque notre objectif est de faire disparaître le Java de nos vues, nous allons très vite apprendre à nous en passer !
D'autres options sont utilisables via cette balise page, comme le contentType ou l'activation de la session. Toutes ont des valeurs par défaut, et je ne vais pas m'attarder sur les détails de chacune d'elles ici. Vous ne vous en servirez que dans des cas très spécifiques que nous découvrirons au cas par cas dans ce cours. Voici à titre d'information l'ensemble des propriétés accessibles via cette directive :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <%@ page language="..." extends="..." import="..." session="true | false" buffer="none | 8kb | sizekb" autoFlush="true | false" isThreadSafe="true | false" isELIgnored ="true | false" info="..." errorPage="..." contentType="..." pageEncoding="..." isErrorPage="true | false" %> |
Vous retrouvez ici celle que je vous ai fait utiliser depuis la mise en place de votre première JSP : le pageEncoding. C'est à travers cette option que vous pouvez spécifier l'encodage qui va être précisé dans l'en-tête de la réponse HTTP envoyée par votre page JSP.
Directive include
Lorsque vous développez une vue, elle correspond rarement à une JSP constituée d'un seul bloc. En pratique, il est très courant de découper littéralement une page web en plusieurs fragments, qui sont ensuite rassemblés dans la page finale à destination de l'utilisateur. Cela permet notamment de pouvoir réutiliser certains blocs dans plusieurs vues différentes ! Regardez par exemple le menu des cours sur le site du zéro : c'est un bloc à part entière, qui est réutilisé dans l'ensemble des pages du site.
Pour permettre un tel découpage, la technologie JSP met à votre disposition une balise qui inclut le contenu d'un autre fichier dans le fichier courant. Via le code suivant par exemple, vous allez inclure une page interne à votre application (en l'occurrence une page JSP nommée uneAutreJSP, mais cela pourrait très bien être une page HTML ou autre) dans votre JSP courante :
1 | <%@ include file="uneAutreJSP.jsp" %> |
La subtilité à retenir, c'est que cette directive ne doit être utilisée que pour inclure du contenu "statique" dans votre page : l'exemple le plus courant pour un site web étant par exemple le header ou le footer de la page, très souvent identiques sur l'intégralité des pages du site.
Attention, ici quand je vous parle de contenu "statique", je n'insinue pas que ce contenu est figé et ne peut pas contenir de code dynamique… Non, si je vous parle d'inclusion "statique", c'est parce qu'en utilisant cette directive pour inclure un fichier, l'inclusion est réalisée au moment de la compilation ; par conséquent, si le code du fichier est changé par la suite, les répercussions sur la page l'incluant n'auront lieu qu'après une nouvelle compilation !
Pour simplifier, cette directive peut être vue comme un simple copier-coller d'un fichier dans l'autre : c'est comme si vous preniez l'intégralité de votre premier fichier, et que vous le colliez dans le second. Vous pouvez donc bien visualiser ici qu'il est nécessaire de procéder à cette copie avant la compilation de la page : on ne va pas copier un morceau de page JSP dans une servlet déjà compilée…
Action standard include
Une autre balise d'inclusion dite "standard" existe, et permet d'inclure du contenu de manière "dynamique". Le contenu sera ici chargé à l'exécution, et non à la compilation comme c'est le cas avec la directive précédente :
1 2 3 4 5 6 7 8 | <%-- L'inclusion dynamique d'une page fonctionne par URL relative : --%> <jsp:include page="page.jsp" /> <%-- Son équivalent en code Java est : --%> <% request.getRequestDispatcher( "page.jsp" ).include( request, response ); %> <%-- Et il est impossible d'inclure une page externe comme ci-dessous : --%> <jsp:include page="http://www.siteduzero.com" /> |
Cela dit, ce type d'inclusion a un autre inconvénient : il ne prend pas en compte les imports et inclusions faits dans la page réceptrice. Pour clarifier, prenons un exemple. Si vous utilisez un type List
dans une première page, et que vous comptez utiliser une liste dans une seconde page que vous souhaitez inclure dans cette première page, il vous faudra importer le type List
dans cette seconde page…
Je vous ai perdus ? Voyons tout cela au travers d'un exemple très simple. Créez une page test_inc.jsp contenant le code suivant, sous le répertoire WebContent de votre projet Eclipse, c'est-à-dire à la racine de votre application :
1 2 3 4 5 | <% ArrayList<Integer> liste = new ArrayList<Integer>(); liste.add( 12 ); out.println( liste.get( 0 ) ); %> |
/test_inc.jsp
Ce code ne fait qu'ajouter un entier à une liste vide, puis l'affiche. Cependant cette page ne contient pas de directive d'import, et ne peut par conséquent pas fonctionner directement : l'import de la classe ArrayList
doit obligatoirement être réalisé auparavant pour que nous puissions l'utiliser dans le code. Si vous tentez d'accéder directement à cette page via http://localhost:8080/test/test_inc.jsp, vous aurez droit à une jolie exception :
Créez maintenant une page test_host.jsp, toujours à la racine de votre application, qui va réaliser l'import de la classe ArrayList
puis inclure la page test_inc.jsp :
1 2 | <%@ page import="java.util.ArrayList" %> <%@ include file="test_inc.jsp" %> |
test_host.jsp
Pour commencer, vous découvrez ici en première ligne une application de la directive page, utilisée ici pour importer la classe ArrayList
. À la seconde ligne, comme je vous l'ai expliqué plus haut, la directive d'inclusion peut être vue comme un copier-coller : ici, le contenu de la page test_inc.jsp est copié dans la page test_host.jsp, puis la nouvelle page test_host.jsp contenant tout le code est compilée. Vous pouvez donc appeler la page test_host.jsp, et la page web finale affichera bien "12" !
Mais si maintenant nous décidons de remplacer la directive présente dans notre page test_host.jsp par la balise standard d'inclusion :
1 2 | <%@ page import="java.util.ArrayList" %> <jsp:include page="test_inc.jsp" /> |
test_host.jsp
Eh bien lorsque nous allons tenter d'accéder à la page test_host.jsp, nous retrouverons la même erreur que lorsque nous avons tenté d'accéder directement à test_inc.jsp ! La raison est la suivante : les deux pages sont compilées séparément, et l'inclusion ne se fera que lors de l’exécution. Ainsi fatalement, la compilation de la page test_inc.jsp ne peut qu'échouer, puisque l'import nécessaire au bon fonctionnement du code n'est réalisé que dans la page hôte.
Pour faire simple, les pages incluses via la balise <jsp:include ... />
doivent en quelque sorte être "indépendantes" ; elles ne peuvent pas dépendre les unes des autres et doivent pouvoir être compilées séparément. Ce n'est pas le cas des pages incluses via la directive <%@ include ... %>
.
Pour terminer sur ces problématiques d'inclusions, je vous donne ici quelques informations et conseils supplémentaires.
- Certains serveurs d'applications sont capables de recompiler une page JSP incluant une autre page via la directive d'inclusion, et ainsi éclipser sa principale contrainte. Ce n'est toutefois pas toujours le cas, et ça reste donc à éviter si vous n'êtes pas sûrs de votre coup…
- Pour inclure un même header et un même footer dans toutes les pages de votre application ou site web, il est préférable de ne pas utiliser ces techniques d'inclusion, mais de spécifier directement ces portions communes dans le fichier web.xml de votre projet. J'en reparlerai dans un prochain chapitre.
- Très bientôt, nous allons découvrir une meilleure technique d'inclusion de pages avec la JSTL !
La portée des objets
Un concept important intervient dans la gestion des objets par la technologie JSP : la portée des objets. Souvent appelée visibilité, ou scope en anglais, elle définit tout simplement leur durée de vie.
Dans le chapitre traitant de la transmission de données, nous avions découvert un premier type d'attributs : les attributs de requête. Eh bien de tels objets, qui je vous le rappelle sont accessibles via l'objet HttpServletRequest
, ne sont visibles que durant le traitement d'une même requête. Ils sont créés par le conteneur lors de la réception d'une requête HTTP, et disparaissent dès lors que le traitement de la requête est terminé.
Ainsi, nous avions donc, sans le savoir, créé des objets ayant pour portée la requête !
Il existe au total quatre portées différentes dans une application :
- page (JSP seulement) : les objets dans cette portée sont uniquement accessibles dans la page JSP en question ;
- requête : les objets dans cette portée sont uniquement accessibles durant l'existence de la requête en cours ;
- session : les objets dans cette portée sont accessibles durant l'existence de la session en cours ;
- application : les objets dans cette portée sont accessibles durant toute l'existence de l'application.
Pourquoi préciser "JSP seulement" pour la portée page ?
Eh bien c'est très simple : il est possible de créer et manipuler des objets de portées requête, session ou application depuis une page JSP ou depuis une servlet. Nous avions d'ailleurs dans le chapitre traitant de la transmission de données créé un objet de portée requête depuis notre servlet, puis utilisé cet objet depuis notre page JSP. En revanche, il n'est possible de créer et manipuler des objets de portée page que depuis une page JSP, ce n'est pas possible via une servlet.
Qu'est-ce qu'une session ?
Une session est un objet associé à un utilisateur en particulier. Elle existe pour la durée pendant laquelle un visiteur va utiliser l'application, cette durée se terminant lorsque l'utilisateur ferme son navigateur, reste inactif trop longtemps, ou encore lorsqu'il se déconnecte du site.
Ainsi, il est possible de garder en mémoire des données concernant un visiteur d'une requête à l'autre, autrement dit de page en page : la session permet donc de garder une trace de la visite effectuée. Plus précisément, une session correspond en réalité à un navigateur particulier, plutôt qu'à un utilisateur : par exemple, si à un même instant vous utilisez deux navigateurs différents pour vous rendre sur le même site, le site créera deux sessions distinctes, une pour chacun des navigateurs.
Un objet session concernant un utilisateur est conservé jusqu'à ce qu'une certaine durée d’inactivité soit atteinte. Passé ce délai, le conteneur considère que ce client n'est plus en train de visiter le site, et détruit alors sa session.
Pour que vous visualisiez bien le principe, voici à la figure suivante un schéma regroupant les différentes portées existantes.
Remarquez bien les points suivants :
- un objet de portée page n'est accessible que sur une page JSP donnée ;
- un objet de portée requête n'est accessible que durant le cheminement d'une requête dans l'application, et n'existe plus dès lors qu'une réponse est renvoyée au client ;
- un objet de portée session est accessible durant l'intégralité de la visite d'un client donné, à condition bien sûr que le temps d'inactivité défini par le conteneur ne soit pas dépassé durant cette visite ;
- un objet de portée application est accessible durant toute l'existence de l'application et par tous les clients.
Vous devez bien réaliser que l'utilisation dans votre code d'objets ayant pour portée l'application est délicate. Rendez-vous compte : ces objets sont accessibles partout, tout le temps et par tout le monde ! Afin d'éviter notamment des problèmes de modifications concurrentes, si vous avez besoin de mettre en place de tels objets, il est recommandé de les initialiser dès le chargement de l'application, puis de ne plus toucher à leur contenu et d'y accéder depuis vos classes et pages uniquement en lecture seule. Nous étudierons ce scénario dans un prochain chapitre.
Nous reviendrons au cas par cas sur chacune de ces portées dans certains exemples des chapitres à venir.
Les actions standard
Autant vous prévenir tout de suite, le déroulement de ce chapitre peut vous perturber : je vais dans cette partie du chapitre vous présenter une certaine manière de faire pour accéder à des objets depuis une page JSP. Ensuite, je vais vous expliquer dans la partie suivante qu'il existe un autre moyen, plus simple et plus propre, et que nous n'utiliserons alors plus jamais cette première façon de faire…
Maintenant que vous connaissez les beans et les portées, vous avez presque tout en main pour constituer le modèle de votre application (le M de MVC) ! C'est lui et uniquement lui qui va contenir les données de votre application, et les traitements à y appliquer. La seule chose qui vous manque encore, c'est la manipulation de ces beans depuis une page JSP.
Vous avez déjà fait connaissance avec l'action standard <jsp:include>
, je vais vous en présenter quatre autres : <jsp:useBean>
, <jsp:getProperty>
, <jsp:setProperty>
et enfin <jsp:forward>
.
L'action standard useBean
Voici pour commencer l'action standard permettant d'utiliser un bean, ou de le créer s'il n'existe pas, depuis une page JSP :
1 2 3 4 5 6 7 8 9 10 11 12 | <%-- L'action suivante récupère un bean de type Coyote et nommé "coyote" dans la portée requête s'il existe, ou en crée un sinon. --%> <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" scope="request" /> <%-- Elle a le même effet que le code Java suivant : --%> <% com.sdzee.beans.Coyote coyote = (com.sdzee.beans.Coyote) request.getAttribute( "coyote" ); if ( coyote == null ){ coyote = new com.sdzee.beans.Coyote(); request.setAttribute( "coyote", coyote ); } %> |
Étudions les différents attributs de cette action.
- La valeur de l'attribut id est le nom du bean à récupérer, ou le nom que vous souhaitez donner au bean à créer.
- L'attribut class correspond logiquement à la classe du bean. Il doit obligatoirement être spécifié si vous souhaitez créer un bean, mais pas si vous souhaitez simplement récupérer un bean existant.
- L'attribut optionnel scope correspond à la portée de l'objet. Si un bean du nom spécifié en id existe déjà dans ce scope, et qu'il est du type ou de la classe précisé(e), alors il est récupéré, sinon une erreur survient. Si aucun bean de ce nom n'existe dans ce scope, alors un nouveau bean est créé. Enfin, si cet attribut n'est pas renseigné, alors le scope par défaut sera limité à la page en cours.
- L'attribut optionnel type doit indiquer le type de déclaration du bean. Il doit être une superclasse de la classe du bean, ou une interface implémentée par le bean. Cet attribut doit être spécifié si class ne l'est pas, et vice-versa.
En résumé, cette action permet de stocker un bean (nouveau ou existant) dans une variable, qui sera identifiée par la valeur saisie dans l'attribut id.
Il est également possible de donner un corps à cette balise, qui ne sera exécuté que si le bean est créé :
1 2 3 4 5 | <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote"> <%-- Ici, vous pouvez placer ce que vous voulez : définir des propriétés, créer d'autres objets, etc. --%> <p>Nouveau bean !</p> </jsp:useBean> |
Ici, le texte qui est présent entre les balises ne sera affiché que si un bean est bel et bien créé, autrement dit si la balise <jsp:useBean>
est appelée avec succès. À l'inverse, si un bean du même nom existe déjà dans cette page, alors le bean sera simplement récupéré et le texte ne sera pas affiché.
L'action standard getProperty
Lorsque l'on utilise un bean au sein d'une page, il est possible par le biais de cette action d'obtenir la valeur d'une de ses propriétés :
1 2 3 4 5 6 7 | <jsp:useBean id="coyote" class="com.sdzee.beans.Coyote" /> <%-- L'action suivante affiche le contenu de la propriété 'prenom' du bean 'coyote' : --%> <jsp:getProperty name="coyote" property="prenom" /> <%-- Elle a le même effet que le code Java suivant : --%> <%= coyote.getPrenom() %> |
Faites bien attention à la subtilité suivante ! Alors que <jsp:useBean>
récupère une instance dans une variable accessible par l'id défini, cette action standard ne récupère rien, mais réalise seulement l'affichage du contenu de la propriété ciblée. Deux attributs sont utiles ici :
- name : contient le nom réel du bean, en l'occurrence l'id que l'on a saisi auparavant dans la balise de récupération du bean ;
- property : contient le nom de la propriété dont on souhaite afficher le contenu.
L'action standard setProperty
Il est enfin possible de modifier une propriété du bean utilisé. Il existe pour cela quatre façons de faire via l'action standard dédiée à cette tâche :
1 2 3 4 5 | <%-- L'action suivante associe une valeur à la propriété 'prenom' du bean 'coyote' : --%> <jsp:setProperty name="coyote" property="prenom" value="Wile E." /> <%-- Elle a le même effet que le code Java suivant : --%> <% coyote.setPrenom("Wile E."); %> |
Syntaxe 1
1 2 3 4 5 6 | <%-- L'action suivante associe directement la valeur récupérée depuis le paramètre de la requête nommé ici 'prenomCoyote' à la propriété 'prenom' : --%> <jsp:setProperty name="coyote" property="prenom" param="prenomCoyote"/> <%-- Elle a le même effet que le code Java suivant : --%> <% coyote.setPrenom( request.getParameter("prenomCoyote") ); %> |
Syntaxe 2
1 2 3 4 5 6 | <%-- L'action suivante associe directement la valeur récupérée depuis le paramètre de la requête nommé ici 'prenom' à la propriété de même nom : --%> <jsp:setProperty name="coyote" property="prenom" /> <%-- Elle a le même effet que le code Java suivant : --%> <% coyote.setPrenom( request.getParameter("prenom") ); %> |
Syntaxe 3
1 2 3 4 5 6 7 8 | <%-- L'action suivante associe automatiquement la valeur récupérée depuis chaque paramètre de la requête à la propriété de même nom : --%> <jsp:setProperty name="coyote" property="*" /> <%-- Elle a le même effet que le code Java suivant : --%> <% coyote.setNom( request.getParameter("nom") ); %> <% coyote.setPrenom( request.getParameter("prenom") ); %> <% coyote.setGenius( Boolean.valueOf( request.getParameter("genius") ) ); %> |
Syntaxe 4
L'action standard forward
La dernière action que nous allons découvrir permet d'effectuer une redirection vers une autre page. Comme toutes les actions standard, elle s'effectue côté serveur et pour cette raison il est impossible via cette balise de rediriger vers une page extérieure à l'application. L'action de forwarding est ainsi limitée aux pages présentes dans le contexte de la servlet ou de la JSP utilisée :
1 2 3 4 5 6 7 8 | <%-- Le forwarding vers une page de l'application fonctionne par URL relative : --%> <jsp:forward page="/page.jsp" /> <%-- Son équivalent en code Java est : --%> <% request.getRequestDispatcher( "/page.jsp" ).forward( request, response ); %> <%-- Et il est impossible de rediriger vers un site externe comme ci-dessous : --%> <jsp:forward page="http://www.siteduzero.com" /> |
Une particularité du forwarding est qu'il n'implique pas d'aller/retour passant par le navigateur de l'utilisateur final. Autrement dit, l'utilisateur final n'est pas au courant que sa requête a été redirigée vers une ou plusieurs JSP différentes, puisque l'URL qui est affichée dans son navigateur ne change pas. Pas d'inquiétude, nous y reviendrons en détail lorsque nous étudierons un cas particulier, dans le chapitre concernant les sessions.
Sachez enfin que lorsque vous utilisez le forwarding, le code présent après cette balise dans la page n'est pas exécuté. Je vous présente toutes ces notations afin que vous sachiez qu'elles existent, mais vous devez comprendre que la plupart de celles-ci étaient d'actualité… il y a une petite dizaine d'années maintenant ! Depuis, d'importantes évolutions ont changé la donne et tout cela n'est aujourd'hui utilisé que dans des cas bien spécifiques.
La vraie puissance de la technologie JSP, c'est dans le chapitre suivant que vous allez la découvrir !
- Les commentaires compris entre
<%--
et--%>
ne sont pas visibles dans la page finale générée. - L'insertion directe de code Java dans une JSP est possible mais très déconseillée.
- Les directives se placent en début de fichier et permettent de configurer une JSP sous différents angles.
- Il existe 4 portées d'objets différentes, représentant 4 durées de vie différentes : page, request, session et application.
- Une session suit un visiteur de son arrivée sur le site jusqu'à son départ.
- Les actions standard permettent pour la plupart de manipuler des objets au sein d'une JSP, mais sont aujourd'hui de l'histoire ancienne.