MVC et RequestDispatcher¶
Le modèle MVC¶
Le modèle MVC (Modèle Vue Contrôleur) est un modèle d’architecture pour guider la conception d’applications qui nécessitent une interaction de l’utilisateur avec le système. Il définit trois grandes catégories de responsabilité :
- Le modèle
Les classes appartenant à cette catégorie définissent les données applicatives ainsi que les logiques de traitement propres à l’application.
- La vue
Les classes appartenant à cette catégorie gèrent la représentation graphique des données et l’interface utilisateur
- Le contrôleur
Les classes appartenant à cette catégorie gèrent les interactions de l’utilisateur et la mise à jour des vues après la modification des données. Les contrôleurs assurent la cohérence entre le modèle et la vue.
Pour une application Web Java
le modèle peut être un simple objet Java qui encapsule la logique de l’application
la vue est une Java Server Pages (JSP)
le contrôleur est une servlet chargée de valider les paramètres de la requête avant de les transmettre au modèle pour le traitement. Une fois ce traitement terminé, la servlet transmet le résultat à la vue.
L’implémentation du modèle MVC pour une application Java EE suppose que l’on puisse découper le traitement de la requête en plusieurs étapes : d’abord la réception par la servlet qui joue le rôle de contrôleur puis le traitement par le modèle et enfin le rendu de la vue par la JSP. Le RequestDispatcher permet de réaliser ce découpage puisque la servlet va pouvoir transférer le traitement de la requête à une JSP.
Le request dispatcher¶
Le request dispatcher est un objet fourni par le conteneur Web. Il permet d’inclure ou de déléguer des traitements lors de la prise en charge d’une requête HTTP.
Nous avons vu dans un précédent chapitre qu’il existe une classe ServletContext permettant, notamment, de stocker les attributs de portée application. Le ServletContext représente le contexte d’une application Web et est accessible depuis une servlet grâce à la méthode getServletContext().
Une instance de ServletContext permet également de récupérer une instance de RequestDispatcher grâce aux méthodes :
- getResquestDispatcher(String path)
Permet de récupérer une instance de RequestDispatcher pour transferer le traitement à la ressource dont le chemin d’URL est passé en paramètre
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/un/chemin");
Selon la documentation, le chemin passé en paramètre DOIT commencer par
/
. Cependant, le chemin est interprété relativement au contexte racine de l’application.- getNamedDispatcher(String name)
Permet de récupérer une instance de RequestDispatcher pour transférer le traitement à la servlet dont le nom est passé en paramètre
RequestDispatcher dispatcher = getServletContext().getNamedDispatcher("maServlet");
Une servlet peut être nommée dans le fichier web.xml grâce à la balise <servlet-name> ou avec l’annotation @WebServlet grâce à l’attribut name de l’annotation.
Une fois que nous disposons d’une instance d’un RequestDispatcher nous pouvons appeler une de ses deux méthodes disponibles :
- void RequestDispatcher.include(ServletRequest request, ServletResponse response)
Inclut le contenu de la ressource dans le résultat final renvoyé au client. Si le request dispatcher pointe sur un fichier HTML, l’ensemble du fichier sera inséré dans la réponse. Si le request dispatcher pointe sur une servlet, cette dernière est exécutée et sa sortie est insérée dans la réponse.
- void RequestDispatcher.forward(ServletRequest request, ServletResponse response)
Trasfère le traitement de la requête à une nouvelle ressource. La différence avec la méthode
include
est que la servlet qui appelleforward
ne doit pas avoir produit de contenu dans la réponse. C’est cette méthode qui va permettre d’implémenter le modèle MVC dans une application Web Java EE.
Avec le request dispatcher, on voit apparaître la possibilité de créer
une chaîne de traitement pour une requête. Par exemple, une servlet peut
être utilisée pour valider les paramètres transmis par la requête et
effectuer un traitement propre à l’application. Puis, via un
RequestDispatcher, elle peut déléguer à une autre servlet le soin de
générer la réponse en utilisant la méthode forward
. C’est dans ce
modèle de traitement que les attributs de requête vont être utiles (Cf. le
chapitre Les attributs d’une application Web). Chaque servlet impliquée dans le traitement de
la requête exploite et produit des données qu’elle peut récupérer ou stocker
comme attribut dans la requête.
Pour l’implémentation du modèle MVC, le RequestDispatcher est utilisé pour transmettre le traitement de la requête de la servlet à la JSP.
Exemple de MVC (sans validation)¶
À titre d’exemple, imaginons que nous voulons réaliser une application qui permet à l’utilisateur de saisir des informations personnelles : son nom et son âge. L’application se contente de vérifier si la personne est majeure et d’afficher un message en conséquence.
L’interaction entre l’utilisateur et le serveur correspond à l’envoi d’une
requête HTTP. La servlet SaisieDonneesPersonnellesControleur
joue le rôle
de contrôleur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package dev.gayerie;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/donneespersonnelles")
public class SaisieDonneesPersonnellesControleur extends HttpServlet {
private static final String VUE_FORMULAIRE = "/WEB-INF/jsp/saisieDonneesPersonnelles.jsp";
// ...
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
getServletContext().getRequestDispatcher(VUE_FORMULAIRE).forward(req, resp);
}
// ...
}
|
La méthode doGet
traite les requêtes de type GET. Elle n’a pas de traitement
particulier à réaliser sinon de rediriger vers la vue en utilisant le
RequestDispatcher
. La vue correspond à un fichier JSP.
Note
Le chemin du fichier JSP est /WEB-INF/jsp/saisieDonneesPersonnelles.jsp
.
Le fichier se trouve dans la partie privée de l’application puisqu’il est placé
dans un sous-répertoire à /WEB-INF
. Il est possible de transférer
le traitement à une partie privée de l’application. Cela empêche l’accès direct
à la vue depuis un navigateur Web.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <%@page pageEncoding="UTF-8" contentType="text/html" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Java EE - Exemple MVC</title>
</head>
<body>
<form action="" accept-charset="utf-8" method="post">
<label>Nom :</label>
<input type="text" name="nom">
<label>Age :</label>
<input type="number" name="age">
<input type="submit">
</form>
<div>
<a href='<c:url value="/"/>'>Retour à l'accueil</a>
</div>
</body>
</html>
|
Lorsque l’utilisateur remplit le formulaire et soumet les données en cliquant sur le bouton, une nouvelle requête est émise vers la même adresse mais avec la méthode POST.
Astuce
En laissant vide l’attribut action
de l’élément form
, le navigateur
soumettra le formulaire à l’adresse courante.
Nous pouvons compléter notre contrôleur pour gérer le traitement d’une requête POST :
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 | package io.github.spoonless.mvc;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/donneespersonnelles")
public class SaisieDonneesPersonnellesControleur extends HttpServlet {
private static final String VUE_FORMULAIRE = "/WEB-INF/jsp/saisieDonneesPersonnelles.jsp";
private static final String VUE_RESULTAT = "/WEB-INF/jsp/donneesPersonnelles.jsp";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
getServletContext().getRequestDispatcher(VUE_FORMULAIRE).forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String nom = req.getParameter("nom");
String age = req.getParameter("age");
DonneesPersonnelles donneesPersonnelles = new DonneesPersonnelles(nom, age);
req.setAttribute("donneesPersonnelles", donneesPersonnelles);
getServletContext().getRequestDispatcher(VUE_RESULTAT).forward(req, resp);
}
}
|
À la ligne 27, le contrôleur crée un objet de type DonneesPersonnelles
qui
correspond au modèle. À la ligne 28, le contrôleur enregistre cet objet comme
attribut de requête pour le rendre disponible à la vue. Et, à la ligne 29, le
contrôleur utilise le RequestDispatcher
pour transférer la traitement à la vue.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package dev.gayerie;
public class DonneesPersonnelles {
private String nom;
private Integer age;
public DonneesPersonnelles(String nom, String age) {
this.nom = nom;
this.age = Integer.valueOf(age);
}
public String getNom() {
return nom;
}
public Integer getAge() {
return age;
}
public boolean isMajeur() {
return age != null && age >= 18;
}
}
|
Dans notre exemple, le modèle reste très simple. Il a tout de même la responsabilité de transformer les données et de savoir si un utilisateur est, ou non, majeur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <%@page pageEncoding="UTF-8" contentType="text/html" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Java EE - Exemple MVC</title>
</head>
<body>
<div>Vos données personnelles :</div>
<ul>
<li>Nom : <c:out value="${donneesPersonnelles.nom}"/></li>
<li>Age : <fmt:formatNumber value="${donneesPersonnelles.age}"/></li>
</ul>
<c:if test="${donneesPersonnelles.majeur}">
<div>Vous êtes majeur(e) !</div>
</c:if>
<div>
<a href='<c:url value="/"/>'>Retour à l'accueil</a>
</div>
</body>
</html>
|
Exemple de MVC (avec validation)¶
Nous pouvons complexifier l’exemple précédent en ajoutant une étape de validation des données. La plupart du temps, la validation est de la responsabilité du modèle. Le contrôleur se contente d’attendre le traitement du modèle pour savoir s’il faut transférer le traitement à une vue de succès ou à une vue d’échec. La vue d’échec correspond, la plupart du temps, à la vue du formulaire dans laquelle on affiche un ou plusieurs messages d’erreur.
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 | package dev.gayerie;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/donneespersonnelles")
public class SaisieDonneesPersonnellesControleur extends HttpServlet {
private static final String VUE_FORMULAIRE = "/WEB-INF/jsp/saisieDonneesPersonnelles.jsp";
private static final String VUE_RESULTAT = "/WEB-INF/jsp/donneesPersonnelles.jsp";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
getServletContext().getRequestDispatcher(VUE_FORMULAIRE).forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
try {
String nom = req.getParameter("nom");
String age = req.getParameter("age");
DonneesPersonnelles donneesPersonnelles = new DonneesPersonnelles(nom, age);
req.setAttribute("donneesPersonnelles", donneesPersonnelles);
getServletContext().getRequestDispatcher(VUE_RESULTAT).forward(req, resp);
} catch (DonneesInvalidesException e) {
req.setAttribute("message", e.getMessage());
getServletContext().getRequestDispatcher(VUE_FORMULAIRE).forward(req, resp);
}
}
}
|
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 | package dev.gayerie;
public class DonneesPersonnelles {
private String nom;
private Integer age;
public DonneesPersonnelles(String nom, String age) throws DonneesInvalidesException {
if (isBlank(nom) || isBlank(age)) {
throw new DonneesInvalidesException("Certaines donnees ne sont pas renseignees !");
}
this.nom = nom;
try {
this.age = Integer.valueOf(age);
} catch(NumberFormatException e) {
throw new DonneesInvalidesException("L'age n'est pas un nombre !");
}
}
private static boolean isBlank(String valeur) {
return valeur == null || "".equals(valeur);
}
public String getNom() {
return nom;
}
public Integer getAge() {
return age;
}
public boolean isMajeur() {
return age != null && age >= 18;
}
}
|
1 2 3 4 5 6 7 8 9 | package dev.gayerie;
public class DonneesInvalidesException extends Exception {
public DonneesInvalidesException(String message) {
super(message);
}
}
|
Pour la vue de formulaire, on pré-remplie le formulaire avec les données du
l’attribut donneesPersonnelles
. Cet attribut est passé à la vue en cas
d’échec de la validation. Cela permet d’afficher à nouveau à l’utilisateur les
données qu’il a saisies.
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" contentType="text/html" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Java EE - Exemple MVC</title>
</head>
<body>
<div>
<c:out value="${message}"/>
</div>
<form action="" accept-charset="utf-8" method="post">
<label>Nom :</label>
<input type="text" name="nom" value='<c:out value="${donneesPersonnelles.nom}"/>'>
<label>Age :</label>
<input type="number" name="age" value="${donneesPersonnelles.age}">
<input type="submit">
</form>
<div>
<a href='<c:url value="/"/>'>Retour à l'accueil</a>
</div>
</body>
</html>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <%@page pageEncoding="UTF-8" contentType="text/html" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Java EE - Exemple MVC</title>
</head>
<body>
<div>Vos données personnelles :</div>
<ul>
<li>Nom : <c:out value="${donneesPersonnelles.nom}"/></li>
<li>Age : <fmt:formatNumber value="${donneesPersonnelles.age}"/></li>
</ul>
<c:if test="${donneesPersonnelles.majeur}">
<div>Vous êtes majeur(e) !</div>
</c:if>
<div>
<a href='<c:url value="/"/>'>Retour à l'accueil</a>
</div>
</body>
</html>
|
Note
Vous pouvez télécharger le projet complet : exemple-mvc.zip
.
Exercice¶
Formulaire MVC
Objectif
Reprenez l’exemple précédent et ajoutez les fonctionnalités suivantes :
Dans le formulaire, l’utilisateur doit pouvoir préciser sa civilité : Monsieur, Madame, Mademoiselle. Le choix s’affiche sous la forme d’une liste (
select
en HTML). Attention, la liste des civilités ne doit pas être en dur dans la vue mais elle doit être fournie par un modèle. La civilité est affichée dans la vue après le traitement par le serveur.Dans le formulaire, l’utilisateur doit saisir un email. Le format de l’email est validé par le serveur. Pour faire simple, on peut se contenter de vérifier que le caractère
@
est présent dans l’email.Modifier l’implémentation : si l’utilisateur a fait plusieurs erreurs de saisie dans le formulaire, elles doivent être toutes clairement signalées lors de l’affichage du formulaire à côté du champ concerné.
Pour réaliser ces améliorations, vous respecterez une architecture MVC.
- Modèle Maven du projet à télécharger
- Mise en place du projet
Éditer le fichier pom.xml du template et modifier la balise artifactId pour spécifier le nom de votre projet.
- Intégration du projet dans Eclipse
L’intégration du projet dans Eclipse suit la même procédure que celle vue dans Import du projet Maven dans Eclipse.