Le modèle MVC nous conseille de placer tout ce qui touche à l'affichage final (texte, mise en forme, etc.) dans une couche à part : la vue. Nous avons rapidement survolé dans la première partie de ce cours comment ceci se concrétisait en Java EE : la technologie utilisée pour réaliser une vue est la page JSP. Nous allons dans ce chapitre découvrir comment fonctionne une telle page, et apprendre à en mettre une en place au sein de notre embryon d'application.
Introduction aux JSP
À quoi ressemble une page JSP ?
C'est un document qui, à première vue, ressemble beaucoup à une page HTML, mais qui en réalité en diffère par plusieurs aspects :
- l'extension d'une telle page devient .jsp et non plus .html ;
- une telle page peut contenir des balises HTML, mais également des balises JSP qui appellent de manière transparente du code Java ;
- contrairement à une page HTML statique directement renvoyée au client, une page JSP est exécutée côté serveur, et génère alors une page renvoyée au client.
L'intérêt est de rendre possible la création de pages dynamiques : puisqu'il y a une étape de génération sur le serveur, il devient possible de faire varier l'affichage et d’interagir avec l'utilisateur, en fonction notamment de la requête et des données reçues !
Bien que la syntaxe d'une page JSP soit très proche de celle d'une page HTML, il est théoriquement possible de générer n'importe quel type de format avec une page JSP : du HTML donc, mais tout aussi bien du CSS, du XML, du texte brut, etc. Dans notre cas, dans la très grande majorité des cas d'utilisation il s'agira de pages HTML destinées à l'affichage des données de l'application sur le navigateur du client.
Ne vous fiez pas au titre de ce sous-chapitre, nous n'allons pas pour le moment nous intéresser à la technologie JSP en elle-même, ceci faisant l'objet des chapitres suivants. Nous allons nous limiter à l'étude de ce qu'est une JSP, de la manière dont elle est interprétée par notre serveur et comment elle s'insère dans notre application.
Nature d'une JSP
Quoi ?
Les pages JSP sont une des technologies de la plate-forme Java EE les plus puissantes, simples à utiliser et à mettre en place. Elles se présentent sous la forme d'un simple fichier au format texte, contenant des balises respectant une syntaxe à part entière. Le langage JSP combine à la fois les technologies HTML, XML, servlet et JavaBeans (nous reviendrons sur ce terme plus tard, pour le moment retenez simplement que c'est un objet Java) en une seule solution permettant aux développeurs de créer des vues dynamiques.
Pourquoi ?
Pour commencer, mettons noir sur blanc les raisons de l'existence de cette technologie.
- La technologie servlet est trop difficile d'accès et ne convient pas à la génération du code de présentation : nous l'avons souligné en fin de chapitre précédent, écrire une page web en langage Java est horriblement pénible. Il est nécessaire de disposer d'une technologie qui joue le rôle de simplification de l'API servlet : les pages JSP sont en quelque sorte une abstraction "haut niveau" de la technologie servlet.
- Le modèle MVC recommande une séparation nette entre le code de contrôle et la présentation. Il est théoriquement envisageable d'utiliser certaines servlets pour effectuer le contrôle, et d'autres pour effectuer l'affichage, mais nous rejoignons alors le point précédent : la servlet n'est pas adaptée à la prise en charge de l'affichage…
- Le modèle MVC recommande une séparation nette entre le code métier et la présentation : dans le modèle on doit trouver le code Java responsable de la génération des éléments dynamiques, et dans la vue on doit simplement trouver l'interface utilisateur ! Ceci afin notamment de permettre aux développeurs et designers de travailler facilement sur la vue, sans avoir à y faire intervenir directement du code Java.
Comment ?
On peut résumer la technologie JSP en une technologie offrant les capacités dynamiques des servlets tout en permettant une approche naturelle pour la création de contenus statiques. Ceci est rendu possible par :
- un langage dédié : les pages JSP sont des documents au format texte, à l'opposé des classes Java que sont les servlets, qui décrivent indirectement comment traiter une requête et construire une réponse. Elles contiennent des balises qui combinent à la fois simplicité et puissance, via une syntaxe simple, semblable au HTML et donc aisément compréhensible par un humain ;
- la simplicité d'accès aux objets Java : des balises du langage rendent l'utilisation directe d'objets au sein d'une page très aisée ;
- des mécanismes permettant l'extension du langage utilisé au sein des pages JSP : il est possible de mettre en place des balises qui n'existent pas dans le langage JSP, afin d'augmenter les fonctionnalités accessibles. Pas de panique, ça paraît complexe a priori mais nous y reviendrons calmement dans la partie concernant la JSTL, et tout cela n'aura bientôt plus aucun secret pour vous !
Bon, assez gambergé ! Maintenant que nous avons une bonne idée de ce que sont les pages JSP, rentrons dans le concret en étudiant leur vie au sein d'une application !
Mise en place d'une JSP
Création de la vue
Le contexte étant posé, nous pouvons maintenant créer notre première page JSP. Pour ce faire, depuis votre projet Eclipse faites un clic droit sur le dossier WebContent
de votre projet, puis choisissez New > JSP File
, et dans la fenêtre qui apparaît renseignez le nom de votre page JSP, ainsi qu'indiqué aux figures suivantes.
Une page JSP par défaut est alors générée par Eclipse : supprimez tout son contenu, et remplacez-le par notre modèle d'exemple :
1 2 3 4 5 6 7 8 9 10 11 | <!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> </body> </html> |
test.jsp
Rendez-vous ensuite dans la barre d'adresses de votre navigateur, et entrez l'URL correspondant à la page que vous venez de créer :
1 | http://localhost:8080/test/test.jsp |
Nous obtenons alors le résultat de la figure suivante.
Qu'est-ce que c'est que cette horreur ? Que s'est-il passé ?
Le problème que vous voyez ici, c'est l'encodage ! Eh oui, dans le chapitre précédent nous avions modifié l'encodage par défaut directement depuis notre servlet, et je ne vous avais alors pas expliqué pourquoi c'était vraiment nécessaire de procéder ainsi, je vous avais uniquement expliqué l'intérêt d'utiliser UTF-8. Cette fois dans notre JSP, nous n'avons rien modifié. Par conséquent, la réponse créée par notre page utilise la valeur par défaut, c'est-à-dire l'encodage latin ISO-8859-1.
Si l'encodage latin est utilisé par défaut, alors pourquoi les lettres accentuées ne s'affichent-elles pas correctement ? Ce sont bien des caractères de notre alphabet !
Nous y voilà, vous devez bien comprendre comment le principe d'encodage fonctionne. Il s'agit d'un processus en deux étapes, avec d'une part la manière dont sont encodés et gérés les caractères sur le serveur, et d'autre part la manière dont les données contenues dans la réponse envoyée vont être interprétées par le navigateur :
- en ce qui concerne le serveur, c'est simple : si vous avez bien suivi mes conseils lors de la configuration d'Eclipse, vos fichiers source sont tous encodés en UTF-8 ;
- en ce qui concerne le navigateur, celui-ci va uniquement se baser sur le contenu de l'en-tête Content-type de la réponse HTTP afin d'interpréter les données reçues.
Or, comme je viens de vous le dire, votre page JSP a par défaut envoyé la réponse au client en spécifiant l'encodage latin dans l'en-tête HTTP. Voilà donc l'explication de l'affreux micmac observé : votre navigateur a essayé d'afficher des caractères encodés en UTF-8 en utilisant l'encodage latin ISO-8859-1, et il se trouve que l'encodage des caractères accentués en ISO n'a rien à voir avec celui des caractères accentués en UTF ! D'où les symboles bizarroïdes observés…
Comment modifier l'en-tête HTTP depuis notre page JSP, afin de faire savoir au navigateur qu'il doit utiliser l'encodage UTF-8 pour interpréter les données reçues ?
Pour cela, il va falloir ajouter une instruction en tête de votre page JSP. Je ne vais pas vous l'expliquer dès maintenant, je reviendrai sur son fonctionnement dans un chapitre à venir. Contentez-vous simplement d'ajouter cette ligne tout en haut du code de votre page pour le moment :
1 | <%@ page pageEncoding="UTF-8" %> |
Une fois la modification effectuée et enregistrée, actualisez la page dans votre navigateur et vous constaterez qu'il vous affiche maintenant correctement le texte attendu. Tout va bien donc, notre JSP est fonctionnelle !
Cycle de vie d'une JSP
En théorie
Tout tient en une seule phrase : quand une JSP est demandée pour la première fois, ou quand l'application web démarre, le conteneur de servlets va vérifier, traduire puis compiler la page JSP en une classe héritant de HttpServlet, et l'utiliser durant l'existence de l'application.
Cela signifie-t-il qu'une JSP est littéralement transformée en servlet par le serveur ?
C'est exactement ce qui se passe. Lors de la demande d'une JSP, le moteur de servlets va exécuter la classe JSP auparavant traduite et compilée et envoyer la sortie générée (typiquement, une page HTML/CSS/JS) depuis le serveur vers le client à travers le réseau, sortie qui sera alors affichée dans son navigateur !
Pourquoi ?
Je vous l'ai déjà dit, la technologie JSP consiste en une véritable abstraction de la technologie servlet : cela signifie concrètement que les JSP permettent au développeur de faire du Java sans avoir à écrire de code Java ! Bien que cela paraisse magique, rassurez-vous il n'y a pas de miracles : vous pouvez voir le code JSP écrit par le développeur comme une succession de raccourcis en tous genres qui, dans les coulisses, appellent en réalité des portions de code Java toutes prêtes !
Que se passe-t-il si le contenu d'une page JSP est modifié ? Que devient la servlet auto-générée correspondante ?
C'est une très bonne question ! Voici ce qui se passe au sein du conteneur de servlets lorsqu'une requête HTTP est destinée à une JSP :
- le conteneur vérifie si la JSP a déjà été traduite et compilée en une servlet :
- si non, il vérifie la syntaxe de la page, la traduit en une servlet (du code Java) et la compile en une classe exécutable prête à l'emploi ;
- si oui, il vérifie que l'âge de la JSP et de la servlet est identique :
- si non, cela signifie que la JSP est plus récente que la servlet et donc qu'il y a eu modification, le conteneur effectue alors à nouveau les tâches de vérification, traduction et compilation ;
- il charge ensuite la classe générée, en crée une instance et l'exécute pour traiter la requête.
J'ai représenté cette suite de décisions sur la figure suivante, afin de vous faciliter la compréhension du cycle.
De tout cela, il faut retenir que le processus initial de vérification/traduction/compilation n'est pas effectué à chaque appel ! La servlet générée et compilée étant sauvegardée, les appels suivants à la JSP sont beaucoup plus rapides : le conteneur se contente d'exécuter directement l'instance de la servlet stockée en mémoire.
En pratique
Avant de passer à la suite, revenons sur cette histoire de traduction en servlet. Je vous ai dit que le conteneur de servlets, en l'occurrence ici Tomcat, générait une servlet à partir de votre JSP. Eh bien sachez que vous pouvez trouver le code source ainsi généré dans le répertoire de travail du serveur : sous Tomcat, il s'agit du répertoire /work.
Qu'en est-il de notre première JSP ? Existe-t-il une servlet auto-générée depuis nos quelques lignes de texte ?
La réponse est oui bien entendu, à partir du moment où vous avez appelé au moins une fois cette JSP depuis votre navigateur ! Cette servlet est bien présente dans le répertoire de travail de Tomcat, seulement comme nous gérons notre serveur directement depuis Eclipse, par défaut ce dernier va en quelque sorte prendre la main sur Tomcat, et mettre tous les fichiers dans un répertoire à lui ! Le fourbe… Bref, voilà où se trouve ma servlet pour cet exemple :
1 2 | C:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\test\org\apache\jsp\test_jsp.java C:\eclipse\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\test\org\apache\jsp\test_jsp.class |
Elle doit se situer sensiblement au même endroit chez vous, à un ou deux noms de dossier près selon la configuration que vous avez mise en place et bien entendu selon le système d'exploitation que vous utilisez. Vous remarquerez que Tomcat suffixe les servlets qu'il auto-génère à partir de pages JSP avec "_jsp".
Je vous conseille alors d'éditer le fichier .java et de consulter les sources générées, c'est un exercice très formateur pour vous que de tenter de comprendre ce qui y est fait : n'oubliez pas, la Javadoc est votre amie pour comprendre les méthodes qui vous sont encore inconnues. Ne prenez surtout pas peur devant ce qui s'apparente à un joyeux bordel, et passez faire un tour sur le forum Java si vous avez des questions précises sur ce qui s'y trouve !
Sans surprise, au milieu du code généré par Tomcat nous retrouvons bien des instructions très semblables à celles que nous avions dû écrire dans notre servlet dans le chapitre précédent, et qui correspondent cette fois à ce que nous avons écrit dans notre JSP :
1 2 3 4 5 6 7 8 9 10 | 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(" </body>\r\n"); out.write("</html>"); |
Extrait de la servlet test_jsp.java auto-générée par Tomcat depuis notre page test.jsp
Retenez bien que c'est cette classe Java qui est compilée et exécutée lorsque votre JSP est appelée. Ce court aparté se termine ici, dorénavant nous ne nous préoccuperons plus de ce qui se trame dans les coulisses de notre serveur : nous aurons bien assez de travail avec nos JSP… et le reste !
Mise en relation avec notre servlet
Garder le contrôle
Dans l'exemple que nous venons de réaliser, nous nous sommes contentés d'afficher du texte statique, et avons visualisé le résultat en appelant directement la page JSP depuis son URL. C'était pratique pour le coup, mais dans une application Java EE il ne faut jamais procéder ainsi ! Pourquoi ? La réponse tient en trois lettres : MVC. Ce modèle de conception nous recommande en effet la mise en place d'un contrôleur, et nous allons donc tâcher de toujours associer une servlet à une vue.
Mais je viens de vous montrer qu'une JSP était de toute façon traduite en servlet… Quel est l'intérêt de mettre en place une autre servlet ?
Une JSP est en effet automatiquement traduite en servlet, mais attention à ne pas confondre : les contrôleurs du MVC sont bien représentés en Java EE par des servlets, mais cela ne signifie pas pour autant que toute servlet joue le rôle d'un contrôleur dans une application Java EE. En l'occurrence, les servlets résultant de la traduction des JSP dans une application n'ont pour rôle que de permettre la manipulation des requêtes et réponses HTTP. En aucun cas elles n'interviennent dans la couche de contrôle, elles agissent de manière transparente et font bien partie de la vue : ce sont simplement des traductions en un langage que comprend le serveur (le Java !) des vues présentes dans votre application (de simples fichiers textes contenant de la syntaxe JSP).
Dorénavant, nous allons donc systématiquement créer une servlet lorsque nous créerons une page JSP. Ça peut vous sembler pénible au début, mais c'est une bonne pratique à prendre dès maintenant : vous gardez ainsi le contrôle, en vous assurant qu'une vue ne sera jamais appelée par le client sans être passée à travers une servlet. Souvenez-vous : la servlet est le point d'entrée de votre application !
Nous allons même pousser le vice plus loin, et déplacer notre page JSP dans le répertoire /WEB-INF. Si vous vous souvenez de ce que je vous ai dit dans le chapitre sur la configuration de Tomcat, vous savez que ce dossier a une particularité qui nous intéresse : il cache automatiquement les ressources qu'il contient. En d'autres termes, une page présente sous ce répertoire n'est plus accessible directement par une URL côté client ! Il devient alors nécessaire de passer par une servlet côté serveur pour donner l'accès à cette page… Plus d'oubli possible !
Faites le test. Essayez depuis une URL de joindre votre page JSP après l'avoir déplacée sous /WEB-INF : vous n'y arriverez pas !
Nous devons donc associer notre servlet à notre vue. Cette opération est réalisée depuis la servlet, ce qui est logique puisque c'est elle qui décide d'appeler la vue.
Reprenons notre servlet, vidons la méthode doGet()
du contenu que nous avons depuis fait migrer dans la JSP, et regardons le code à mettre en place pour effectuer l'association :
1 2 3 4 5 | ... public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException { this.getServletContext().getRequestDispatcher( "/WEB-INF/test.jsp" ).forward( request, response ); } |
com.sdzee.servlets.Test
Analysons ce qui se passe :
- depuis notre instance de servlet (
this
), nous appelons la méthodegetServletContext()
. Celle-ci nous retourne alors un objetServletContext
, qui fait référence au contexte commun à toute l'application : celui-ci contient un ensemble de méthodes qui permettent à une servlet de communiquer avec le conteneur de servlet ; - celle qui nous intéresse ici est la méthode permettant de manipuler une ressource,
getRequestDispatcher()
, que nous appliquons à notre page JSP. Elle retourne un objetRequestDispatcher
, qui agit ici comme une enveloppe autour de notre page JSP. Vous pouvez considérer cet objet comme la pierre angulaire de votre servlet : c'est grâce à lui que notre servlet est capable de faire suivre nos objets requête et réponse à une vue. Il est impératif d'y préciser le chemin complet vers la JSP, en commençant obligatoirement par un / (voir l'avertissement et la précision ci-dessous) ; - nous utilisons enfin ce dispatcher pour réexpédier la paire requête/réponse HTTP vers notre page JSP via sa méthode
forward()
.
De cette manière notre page JSP devient accessible au travers de la servlet ; d'ailleurs, notre servlet ne faisant actuellement rien d'autre, son seul rôle est de transférer le couple requête reçue et réponse vers la JSP finale.
Ne soyez pas leurrés : comme je vous l'ai déjà dit lorsque je vous ai présenté la structure d'un projet, le dossier WebContent existe uniquement dans Eclipse ! Je vous ai déjà expliqué qu'il correspondait en réalité à la racine de l'application, et c'est donc pour ça qu'il faut bien écrire /WEB-INF/test.jsp en argument de la méthode getRequestDispatcher()
, et non pas /WebContent/WEB-INF/test.jsp !
À retenir donc : nulle part dans votre code ne doit être mentionné le répertoire WebContent ! C'est une représentation de la racine de l'application, propre à Eclipse uniquement.
Si vous parcourez la documentation de l'objet HttpServletRequest
, vous remarquerez qu'il contient lui aussi une méthode getRequestDispatcher()
! Toutefois, cette dernière présente une différence notable : elle peut prendre en argument un chemin relatif, alors que sa grande sœur n'accepte qu'un chemin complet. Je vous conseille, afin d'éviter de vous emmêler les crayons, de passer par la méthode que je vous présente ci-dessus. Quand vous serez plus à l'aise avec ces histoires de chemins relatifs et absolus, vous pourrez alors décider d'utiliser l'une ou l'autre de ces méthodes.
Faites à nouveau le test, en essayant cette fois d'appeler l'URL correspondant à votre servlet (souvenez-vous, c'est celle que nous avons mise en place dans le fichier web.xml, à savoir /test/toto) : tout fonctionne, notre requête est bien acheminée jusqu'à notre JSP, et en retour notre navigateur nous affiche bien le contenu de la page !
Voici à la figure suivante ce que nous venons de réaliser.
- Une page JSP ressemble en apparence à une page HTML, mais en réalité elle est bien plus proche d'une servlet : elle contient des balises derrière lesquelles se cache du code Java.
- Une page JSP est exécutée sur le serveur, et la page finale générée et envoyée au client est une simple page HTML : le client ne voit pas le code de la JSP.
- Idéalement dans le modèle MVC, une page JSP est accessible à l'utilisateur à travers une servlet, et non pas directement.
- Le répertoire /WEB-INF cache les fichiers qu'il contient à l'extérieur de l'application.
- La méthode
forward()
de l'objetRequestDispatcher
permet depuis une servlet de rediriger la paire requête/réponse HTTP vers une autre servlet ou vers une page JSP.