Dans les exemples développés jusqu'ici, nous avons utilisé un modèle architectural au niveau des applications Web appelé Modèle 1. Dans ce type d'architecture, les requêtes HTTP sont gérés par les composants Web (soit servlet, soit JSP), qui reçoivent ces requêtes, créent les réponses et les retournent aux clients. Un seul composant est donc responsable de la logique d'affichage, de la logique métier et de la manipulation des requêtes.
Il existe une autre architecture, appelée Modèle 2, qui partage les responsabilités. Ce modèle est appelé Modèle - Vue - Contrôleur ou MVC. Dans cette architecture, trois composants permettent de séparer clairement les trois activités.
Dans l'architecture Modèle 1, la logique métier, la logique d'affichage et la manipulation des requêtes sont mélangés dans un même composant. Cette architecture conduit à placer du code Java dans des JSP ou du code HTML dans les servlets. Lorsqu'il s'agit d'une petite application, cela ne pose pas de problème, et le sujet est correctement traité. Mais imaginez une application réelle avec des pages Web très sophistiquées et un traitement complexe des données. Une telle application est d'une maintenance très délicate.
Dans le Modèle 2 ou Modèle MVC, un composant est chargé de recevoir les requêtes, un autre de traiter les données et un troisième de préparer l'affichage. Si les interfaces entre ces trois composants sont clairement définies, il devient facile d'en modifier un sans toucher aux deux autres. Dans ce contexte, il est d'ailleurs possible de prévoir plusieurs affichages, un pour un PC, et un autre pour un PDA.
Les composants d'une application MVC sont donc réparti en trois catégories : le modèle, la vue, le contrôleur.
Si une servlet doit jouer le rôle de contrôleur dans une application MVC, elle doit disposer d'un moyen pour transmettre les requêtes aux composants chargés de l'affichage, car ceux-ci ont pour rôle de renvoyer la réponse au client. Ce moyen est fourni par un objet appelé RequestDispatcher. Grâce à lui, une servlet peut faire suivre une requête à un autre composant Web ou inclure la réponse d'un autre composant Web dans la réponse en cours de traitement. Ces deux opérations sont l'équivalent des actions <jsp:forward> et <jsp:include> que nous avons déjà mis en oeuvre lors de l'étude sur les JSP.
Un RequestDispatcher peut être fourni par les objets :
La signature de la méthode de ServletRequest est la suivante :
RequestDispatcher getRequestDispatcher(String chemin)
Cette méthode retourne un RequestDispatcher pour le composant Web désigné par le chemin utilisé comme argument. Il peut s'agir d'un chemin relatif ou absolu.
Par exemple, le chemin de la page JSP <bienvenue.jsp> de l'étude précédente est - /bienvenue.jsp. Ce chemin commence par une barre oblique ( / ) qui indique qu'il s'agit d'un chemin absolu par rapport au contexte de l'application. Si le contexte est /Messages, /bienvenue.jsp désigne la ressource se trouvant à l'adresse </Message/bienvenue.jsp>.
Si ce chemin ne commence pas par une barre oblique, il s'agit d'un chemin relatif qui est interprété à partir de la position du composant courant. Par exemple, si ce composant est </Messages/creation/AfficheMessage.jsp>, le chemin ImprimeMessage.jsp désigne la ressource </Messages/creation/ImprimeMessage.jsp>.
Si la ressource n'existe pas, la méthode retourne la valeur null.
Il est possible d'obtenir un RequestDispatcher à partir d'un objet ServletContext. La classe GenericServlet définit une méthode permettant d'obtenir une référence à l'objet ServletContext d'une servlet :
ServletContext getServletContext()
Toutes les servlets étant des sous-classes de GenericServlet, cette méthode peut être appelée directement à partir de n'importe quelle servlet. ServletContext représente le contexte de l'application dans lequel la servlet est exécutée. A partir de cet objet, il est possible d'obtenir un RequestDispatcher à l'aide des méthodes suivantes :
RequestDispatcher getNameDispatcher(String nom)
RequestDispatcher getRequestDispatcher(String chemin)
Ces méthodes permettent d'obtenir un RequestDispatcher pour une resource désignée par un nom ou par un chemin d'accès. Si la ressource n'existe pas, la méthode retourne null. L'argument chemin doit commencer par une barre oblique ( / ) et est interprété relativement au contexte de l'application. L'argument nom doit être identique au nom utilisé dans le sous-élément <servlet-name> de l'élément <servlet> du descripteur de déploiement. Soit l'association suivante dans le descripteur de déploiement :
<servlet> <servlet-name>Identification</servlet-name> <jsp-file>/WEB-INF/identification.jsp</jsp-file> </servlet>
Un appel à la méthode getNameDispatcher("Identification") retourne dans ce cas un RequestDispatcher pour la ressource </Personnel/WEB-INF/identification.jsp>. (N'oubliez pas que /Identification est interprété relativement au contexte de l'application).
Un RequestDispatcher peut être employé pour faire suivre la requête à une autre ressource, ou pour inclure la réponse d'une autre ressource dans celle de la ressource courante. Pour faire suivre une requête, utilisez la syntaxe :
void forward(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOEception
Cette méthode confiant la création de la réponse à une autre ressource, la servlet appelante ne doit pas avoir déjà écrit des données dans la réponse. Dans le cas contraire, cette méthode lance une exception. De plus, lorsque la ressource recevant la requête aura terminée son traitement, la réponse sera complète. La servlet appelante ne doit donc pas non plus tenter d'écrire des données dans la réponse après le retour de la méthode forward().
Il est également possible d'appeler une autre ressource et d'inclure la réponse fournie par celle-ci dans la réponse courante, en utilisant la méthode :
void include(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOEception
Dans ce cas, la servlet appelante peut sans problème écrire des données dans la réponse tant avant qu'après l'appel de cette méthode.
Il est parfois nécessaire d'ajouter des données à la requête avant l'appel des méthodes forward() ou include(). L'interface ServletRequest définit plusieurs méthodes permettant d'ajouter, de lire ou de supprimer des données dans la requête :
Object getAttribute(String nom) ;
Enumeration getAttributeNames() ;
void setAttribute(String nom, Object o) ;
void removeAttribute(String nom) ;
La servlet appelante peut ajouter des attributs à la requête à l'aide de la méthode setAttribute(String nom, Object obj). Il faut toutefois faire attention de ne pas employer un nom déjà utilisé par un attribut existant. Dans ce cas, le nouvel attribut remplacerait l'ancien. La servlet appelée peut utiliser la méthode getAttribute(String nom) pour lire l'attribut ajouté.
Comme d'habitude, nous allons reprendre l'application Web précédente à laquelle nous allons mettre en oeuvre cette technologie MVC. Toutefois, pour cet exemple, je n'utilise plus l'applet mais je remets à la place le formulaire HTML classique. Dans ce contexte, nous avons une servlet ServletDémarrage qui sert de contrôleur de requêtes. Effectivement, toutes les requêtes passent systématiquement par cette servlet. D'ailleurs, lorsque nous sollicitons cette application Web, nous sommes redirigés dès le départ vers le contrôleur afin de suivre le scénario prévu (ceci-dit, nous aurions pu commencer par la page <authentifier.jsp>). Grâce à ce contrôleur, nous pouvons ainsi libérer tout le code correspondant au contrôle présent dans la page <bienvenue.jsp>.
Nous allons commencer par mettre en place le descripteur de déploiement. Il s'agit ici d'indiquer que la page d'accueil du site est le contrôleur lui-même. Par ailleurs, nous allons rajouter des noms logiques associées à toutes les pages JSP au moyen des balises <servlet>. En effet, nous allons utiliser le RequestDispatcher par rapport aux contextes de servlet. Puisque nous utilisons cette technique, nous pouvons en profiter pour déplacer les pages JSP afin qu'elles soient situées dans la zone privée de l'application Web, et donc plus du tout accessible directement au niveau du client.
web.xml |
---|
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 5 6 <display-name>Liste des messages personnels</display-name> 7 8 <servlet> 9 <servlet-name>authentifier</servlet-name> 10 <jsp-file>/WEB-INF/authentifier.jsp</jsp-file> 11 </servlet> 12 <servlet> 13 <servlet-name>bienvenue</servlet-name> 14 <jsp-file>/WEB-INF/bienvenue.jsp</jsp-file> 15 </servlet> 16 <servlet> 17 <servlet-name>utilisateur</servlet-name> 18 <jsp-file>/WEB-INF/utilisateur.jsp</jsp-file> 19 </servlet> 20 <servlet> 21 <servlet-name>valider</servlet-name> 22 <jsp-file>/WEB-INF/validerutilisateur.jsp</jsp-file> 23 </servlet> 24 <servlet> 25 <servlet-name>controle</servlet-name> 26 <jsp-file>/WEB-INF/controleidentite.jsp</jsp-file> 27 </servlet> 28 29 <servlet> 30 <servlet-name>ServletDemarrage</servlet-name> 31 <servlet-class>contrôleur.ServletDémarrage</servlet-class> 32 </servlet> 33 34 <servlet-mapping> 35 <servlet-name>ServletDemarrage</servlet-name> 36 <url-pattern>/Demarrage/*</url-pattern> 37 </servlet-mapping> 38 39 <welcome-file-list> 40 <welcome-file>Demarrage/authentifier</welcome-file> 41 </welcome-file-list> 42 <context-param> 43 <param-name>couleurFond</param-name> 44 <param-value>FFFF66</param-value> 45 </context-param> 46 </web-app> 47 |
La servlet ServletDémarrage est donc le contrôleur de notre application Web. Ce contrôleur prend en compte aussi bien les méthodes GET que les méthodes POST du protocole HTTP. Le RequestDispatcher est délivré par le contexte de servlet au moyen de la méthode getServletContext(). Pour cela, Il faut définir toutes les pages JSP à gérer dans le descripteur de déploiement, ce que nous venons justement de mettre en oeuvre. Par la technique du chemin complémentaire, nous récupérons ainsi le nom de la page suivante JSP à activer au moyen de la méthode getInfoPath() de l'objet request.
ServletDémarrage.java |
---|
1 package contrôleur; 2 3 import java.io.*; 4 import java.net.*; 5 6 import javax.servlet.*; 7 import javax.servlet.http.*; 8 import bd.Personne; 9 10 11 public class ServletDémarrage extends HttpServlet { 12 13 protected void doGet(HttpServletRequest request, HttpServletResponse response) 14 throws ServletException, IOException { 15 doPost(request, response); 16 } 17 18 protected void doPost(HttpServletRequest request, HttpServletResponse response) 19 throws ServletException, IOException { 20 21 String liaison = request.getPathInfo(); 22 liaison = liaison.substring(1); // Il faut enlever le slash ( / ) 23 24 if (liaison.equals("choix")) { 25 String allerVers = request.getParameter("authentification"); 26 if (allerVers.equals("anonyme")) { 27 getServletContext().getNamedDispatcher("bienvenue").forward(request, response); 28 } 29 else { 30 getServletContext().getNamedDispatcher("utilisateur").forward(request, response); 31 } 32 } 33 else { 34 getServletContext().getNamedDispatcher(liaison).forward(request, response); 35 } 36 } 37 } 38 |
Précise ce que désire faire l'opérateur par la suite.
authentifier.jsp |
---|
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 2 "http://www.w3.org/TR/html4/loose.dtd"> 3 4 <html> 5 <head><title>Messages</title></head> 6 <body bgcolor="#<%= application.getInitParameter("couleurFond") %>"> 7 <font face="Arial" style="bold"> 8 <h2 align="center">Messages</h2> 9 <hr> 10 <h3><font color="green">Actuellement, vous n"êtes pas encore reconnu !</font></h3> 11 12 <form action="Demarrage/choix" method="post"> 13 <table border="1" cellspacing="3" cellpadding="2" width="90%" align="center"> 14 <tr bgcolor="#FF6600"><th>Inscription</th></tr> 15 <tr><td><b> 16 <p><input type="radio" name="authentification" value="anonyme" checked> 17 Désirez-vous rester anonyme et donc consulter les messages prévus pour tout le monde ?</p> 18 <p><input type="radio" name="authentification" value="nouveau"> 19 Désirez-vous vous inscrire ?</p> 20 <p><input type="radio" name="authentification" value="personnel"> 21 Si vous êtes déjà inscrit, désirez-vous consulter vos propres messages ?</p> 22 </b></td></tr> 23 <tr bgcolor="#FF6600"><td align="center"><input type="submit" value="Valider"></td></tr> 24 </table> 25 </form> 26 27 <%@include file = "/WEB-INF/jspf/pieds.jspf" %> 28 </font> 29 </body> 30 </html> 31 |
Une fois que l'utilisateur est authentifié, la liste des messages personnels lui est proposée, à moins, bien sûr, que ce ne soient les messages adressés à tout le monde si ce dernier désire rester anonyme.
bienvenue.jsp |
---|
<%@ include file = "/WEB-INF/jspf/navigation.jspf" %> <p><table border="1" cellpadding="3" cellspacing="2" width="90%" align="center"> <tr bgcolor="#FF6600"> <th>Sujet</th> <th>Message</th> </tr> <% ListeMessages listeMessages = new ListeMessages(idPersonne); int ligne = 0; while (listeMessages.suivant()) { %> <tr bgcolor="<%= ligne++ % 2 == 0 ? "#FFFF66" : "#FFCC00" %>"> <td><b><%= listeMessages.sujet() %></b></td> <td><%= listeMessages.texte() %></td> </tr> <% } listeMessages.arrêt(); %> </table></p> </font> <%@ include file = "/WEB-INF/jspf/pieds.jspf" %> |
Le code de cette page s'est considérablement réduit. Dans la section précédente, cette page possédait tout le contrôle de l'application. Comme cette partie a été déportée dans une servlet spécialisée, il ne reste plus que le code correspondant à l'affichage. Même les déclarations des variables ont été déportée dans l'en-tête <navigation.jspf>.
Nous retrouvons l'en-tête de page que nous avions mis en oeuvre au tout départ. Nous avons ainsi factorisé tout ce qui se retrouvait systématiquement dans toutes les pages JSP (même la directive page)
navigation.jspf |
---|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <%@ page errorPage = "/WEB-INF/erreur.jsp" import="bd.*" %> <%! int idPersonne = 1; %> <%! String identité = "A tout le monde"; %> <% Personne opérateur = (Personne) session.getAttribute("utilisateur"); if (opérateur != null) { idPersonne = opérateur.identificateur(); identité = opérateur.getPrénom()+" "+opérateur.getNom(); } %> <html> <head><title>Messages</title></head> <body bgcolor="#<%= application.getInitParameter("couleurFond") %>"> <font face="Arial"> <h2 align="center">Messages</h2> <hr> <table bgcolor="1" cellpadding="3" cellspacing="2" width="90%" align="center"> <tr bgcolor="#FF9900"> <th align="left"><a href="bienvenue">Sujets</a></th> <th align="right"> <% if (idPersonne == 1) { %> <a href="utilisateur?authentification=personnel">Identification</a> <a href="utilisateur?authentification=nouveau">Inscription</a> <% } else { %> <a href="#">Nouveau</a> <a href="#">Modifier</a> <a href="#">Enlever</a> <% } %> </th> </tr> <tr> <th align="left"><%= identité %></th> <th align="right"><%= request.getRemoteHost() %></th> </tr> </table> |
Par contre, les liens proposés dans cette page, même si cela n'est pas spécifié, nous renvoient automatiquement vers le contrôleur. En effet, ce sont des liens relatifs, et à ce titre, la référence du lien se rajoute par rapport à l'endroit où nous sommes. Hors, cette page a été sollicitée par le contrôleur où, plus précisément, le contrôleur a redirigée une requête issue d'une autre page comme, par exemple <authentifier.jsp> qui proposait la requête que vous voyez ci-dessous :
Nous avons donc déjà dans l'URL le nom du contrôleur "Demarrage". Ainsi, la référence du lien correspond au chemin complémentaire désiré suivi éventuellement des paramètres de la requête. Pour les liens suivant, nous avons :
Pas de modification majeure dans cette page, si ce n'est à la ligne 16. En effet, une fois que l'utilisateur à saisie ses coordonnées, il est orienté comme auparavant vers la bonne page JSP, mais cette fois-ci, en passant par le contrôleur, toujours en prenant la technique du chemin complémentaire où le nom proposé correspond au nom logique de la page définie dans le descripteur de déploiement.
utilisateur.jsp |
---|
1 <%@ include file = "/WEB-INF/jspf/navigation.jspf" %> 2 3 <% session.removeAttribute("utilisateur"); %> 4 5 <%! boolean nouveau; %> 6 <%! String authentification; %> 7 <% 8 authentification = request.getParameter("authentification"); 9 nouveau = authentification.equals("nouveau"); 10 %> 11 12 <h3 align="center"> 13 <font color="green"><%= (nouveau ? "Demande d"inscription" : "Vos références") %></font> 14 </h3> 15 16 <form action="<%= (nouveau ? "valider": "controle") %>" method="post"> 17 <p><table border="1" cellpadding="3" cellspacing="2" width="90%" align="center"> 18 <tr> 19 <td bgcolor="#FF9900" width="100"><b>Nom</b</td> 20 <td><input type="text" name="nom"></td> 21 </tr> 22 <tr> 23 <td bgcolor="#FF9900" width="100"><b>Prénom</b></td> 24 <td><input type="text" name="prénom"></td> 25 </tr> 26 <tr> 27 <td bgcolor="#FF9900" width="100"><b>Mot de passe</b></td> 28 <td><input type="password" name="motDePasse"></td> 29 </tr> 30 </table></p> 31 <p align="center"><input type="submit" value="Valider"></p> 32 </form> 33 34 <%@ include file = "/WEB-INF/jspf/pieds.jspf" %> |
Dans l'attribut action de la balise <form> vous ne voyez plus apparaître "Demarrage" pour bien spécifier que nous devons passer par le contrôleur. Il faut bien savoir que le nom de l'action que nous prososons dans la balise <form> est, sauf avis contraire, toujours relatif à l'endroit où nous sommes. Hors, cette page a été sollicitée par le contrôleur où plus précisément, le contrôleur a redirigée une requête issue de la page <authentifier.jsp> qui elle-même proposait la requête que vous voyez ci-dessous :
Nous avons donc déjà dans l'URL le nom du contrôleur "Demarrage", il suffit juste alors de préciser dans l'attribut action le chemin complémentaire désiré.
Cette page n'a subit aucune modification sauf pour la première ligne qui a été supprimée. Elle a été déplacée dans le fragment de page <navigation.jspf>. Je rappelle d'ailleurs ce qui été écrit sur cette première ligne :
1 <%@ page errorPage = "/WEB-INF/erreur.jsp" import="bd.*" %>
validerutilisateur.jsp |
---|
2 <%@ include file = "/WEB-INF/jspf/navigation.jspf" %> 3 4 <h3 align="center">Confirmation de votre demande d"inscription</h3> 5 6 <jsp:useBean id="utilisateur" class="bd.Personne" scope="session"> 7 <jsp:setProperty name="utilisateur" property="*" /> 8 9 <p><table border="1" cellpadding="3" cellspacing="2" width="90%" align="center"> 10 <tr> 11 <td bgcolor="#FF9900" width="100"><b>Nom</b</td> 12 <td><jsp:getProperty name="utilisateur" property="nom" /></td> 13 </tr> 14 <tr> 15 <td bgcolor="#FF9900" width="100"><b>Prénom</b></td> 16 <td><jsp:getProperty name="utilisateur" property="prénom" /></td> 17 </tr> 18 <tr> 19 <td bgcolor="#FF9900" width="100"><b>Mot de passe</b></td> 20 <td><jsp:getProperty name="utilisateur" property="motDePasse" /></td> 21 </tr> 22 </table></p> 23 <h3 align="center"> 24 <% if (!utilisateur.enregistrer()) { %> 25 <font color="red">ATTENTION : Utilisateur déja enregistré</font> 26 <% 27 } 28 else { 29 %> 30 <font color="green">Nouvel utilisateur enregistré</font> 31 <% 32 } 33 // utilisateur.arrêt(); 34 %> 35 </h3> 36 </jsp:useBean> 37 38 <%@ include file = "/WEB-INF/jspf/pieds.jspf" %> |
Pour cette page, nous conservons la redirection vers la page d'accueil au moyen de l'action <jsp:forward>, en passant toutefois par le contrôleur. En effet, cette page ne sert pas d'affichage. Elle est juste là pour valider l'identité de l'utilisateur.
controleidentite.jsp |
---|
1 <%@ page errorPage = "/WEB-INF/erreur.jsp" import="bd.*" %> 2 3 <jsp:useBean id="utilisateur" class="bd.Personne" scope="session"> 4 <jsp:setProperty name="utilisateur" property="*" /> 5 </jsp:useBean> 6 7 <% if (!utilisateur.authentifier()) { 8 session.removeAttribute("utilisateur"); 9 } 10 %> 11 12 <jsp:forward page="Demarrage/bienvenue" /> 13 |
Des pages JSP peuvent également proposer des redirections de requêtes, et donc avoir des actions <jsp:forward>.
.