4. Modèle-Vue-Contrôleur (MVC)#
Avant de nous lancer dans un développement, clarifions ce motif de conception (pattern design) dit Modèle-Vue-Contrôleur (MVC). Il est maintenant très répandu et accepté sans contestation comme un de ceux menant, notamment pour la réalisation de sites Web dynamiques, à une organisation satisfaisant le but recherché d’une organisation rigoureuse et logique du code.
Un des objectifs est la séparation des différentes couches constituant une application interactive, de manière à simplifier la gestion de chacune, par exemple en permettant la remise en cause indépendante de l’un des composants de l’architecture globale. Il devrait par exemple toujours être possible de revoir complètement la présentation d’un site sans toucher au code applicatif (ou métier), et, réciproquement, le code métier devrait être réalisé avec le minimum de présupposés sur la présentation. La question de l’évolutivité est elle aussi essentielle. Un logiciel doit être modifiable facilement et sans dégradation des fonctions existantes (régression). Enfin, dans tous les cas, l’organisation du code doit être suffisamment claire pour qu’il soit possible de retrouver très rapidement la partie de l’application à modifier, sans devoir ouvrir des dizaines de fichiers. C’est notamment très utile sur de gros projets impliquant plusieurs personnes: le partage des mêmes principes de développement et de structuration représente à terme un gain de temps considérable.
Ce chapitre présente le MVC dans un contexte pratique, en allant aussi vite que possible à une illustration (simplifiée à l’essentiel) de son application. Pour des raisons de clarté et d’introduction à des concepts parfois complexes, le MVC présenté ici vise à la simplicité et à la légèreté plus qu’à la richesse. L’apprentissage de solutions plus complètes destinées à des développements à grande échelle devrait en être facilité.
S1: Principe général#
Supports complémentaires :
Le MVC est un motif de conception (design pattern) qui propose une solution générale au problème de la structuration d’une application. Le MVC définit des règles qui déterminent dans quelle couche de l’architecture, et dans quelle classe (orientée-objet) de cette couche, doit être intégrée une fonctionnalité spécifique. Une application conforme à ces règles est plus facile à comprendre, à gérer et à modifier. Ces règles sont issues d’un processus d’expérimentation et de mise au point de bonnes pratiques qui a aboutit à une architecture standard.
Cette petite introduction au MVC est volontairement courte afin de dire l’essentiel sans vous surcharger avec toutes les subtilités conceptuelles qui accompagnent le sujet. Pour en savoir plus, vous pouvez partir du site de Wikipedia: le modèle MVC (Modèle-Vue-Contrôleur).
Vue d’ensemble#
L’objectif global du MVC est de séparer les aspects traitement, données et présentation, et de définir les interactions entre ces trois aspects. En simplifiant, les données sont gérées par le modèle, la présentation par la vue, les traitements par des actions et l’ensemble est coordonné par les contrôleurs. La figure Architecture MVC donne un aperçu de l’architecture obtenue, en nous plaçant d’emblée dans le cadre spécifique d’une application Web.
La figure montre une application constituée de plusieurs contrôleurs, chaque contrôleur étant lui-même constitué d’un ensemble d’actions. La première caractéristique de cette organisation est donc de structurer hiérarchiquement une application. Dans les cas simples, un seul contrôleur suffit, contenant l’ensemble des actions qui constituent l’application Web.
Chaque requête HTTP est analysée par le framework qui détermine alors quel sont le contrôleur et l’action concernés. Il existe un contrôleur frontal (intégré au framework et donc transparent pour le programmeur), chargé de recevoir les requêtes HTTP, qui exécute l’action en lui passant les paramètres HTTP. Il se base notamment sur les mappings qui associent des URLs à des servlets, comme nous l’avons vu dans le chapitre Environnement Java.
Note
Souvenez-vous du concept d’inversion de contrôle qui définit les frameworks. Les contrôleurs (avec leurs actions) sont les composants injectés par l’application, et c’est le framework qui décide du composant à appliquer en fonction du contexte. C’est lui aussi qui se charge de transmettre la vue au client web.
Au niveau du déroulement d’une action, les deux autres composants, la vue et le modèle, entrent en jeu. Dans le schéma de la figure Architecture MVC, l’action A1 s’adresse au modèle pour récupérer des données et éventuellement déclencher des traitements spécifiques à ces données. L’action passe ensuite les informations à afficher à la vue qui se charge de créer la présentation. Concrètement, cette présentation est le plus souvent dans notre cas le document HTML qui constitue la réponse HTTP.
Il s’agit d’un schéma général qui peut se raffiner de plusieurs manières, et donne lieu à plusieurs variantes, notamment sur les rôles respectifs des composants. Sans entrer dans des discussions qui dépassent le cadre de ce document, voici quelques détails sur le modèle, la vue et le contrôleur.
Le modèle#
Le modèle implante les fonctionnalités de l’application, indépendamment des aspects interactifs. Le modèle est également responsable de la préservation de l’état d’une application entre deux requêtes HTTP, ainsi que des fonctionnalités qui s’appliquent à cet état. Toute donnée persistante doit être gérée par la couche modèle, mais des objets métiers non persistant implantant une fonctionnalité particulière (un calcul, un service) sont également dans cette couche. Le modèle gère les données de session (le panier dans un site de commerce électronique par exemple) ou les informations contenues dans la base de données (le catalogue des produits en vente, pour rester dans le même exemple). Cela comprend également les règles, contraintes et traitements qui s’appliquent à ces données, souvent désignées collectivement par l’expression logique métier de l’application.
Important
Les composants de la couche modèle doivent pouvoir être utilisés dans des contextes applicatifs différents: application web, back office, application bureau, etc. Ils doivent donc impérativement être totalement indépendants de la gestion des interactions avec les utililsateurs, ou d’un environnement d’excution particulier.
La persistance est définie comme la propriété de l’état d’un objet à être préservé au-delà de la durée d’exécution de l’application en général, et d’une action en particulier. Concrètement, la persistance est obtenue le plus souvent par stockage dans une base de données. La persistance est aspect d’un objet métier; elle ne doit pas (ou très peu) impacter la logique applicative implantée par cet objet. Pour le dire autrement, on devrait pouvoir ajouter/modifier/supprimer le comportement de persistance d’un objet indépendamment des services qu’il fournit. Nous reviendrons bien sûr longuement sur ce critère.
La vue#
La vue est responsable de l’interface, ce qui recouvre essentiellement dans notre cas les fragments HTML qui sont assemblés pour constituer les pages du site. La vue est également responsable de la mise en forme des données (pour formater une date par exemple) et doit d’ailleurs se limiter à cette tâche. Il faut prendre garde à éviter d’y introduire des traitements complexes qui relève de la logique métier, car ces traitements ne seraient alors pas réutilisables dans une autre contexte. En principe la vue ne devrait pas accéder au modèle et obtenir ses données uniquement de l’action (mais il s’agit d’une variante possible du MVC).
La vue est souvent implantée par un moteur de templates, dont les caractéristiques, avantages et inconvénients donnent lieu à de nombreux débats. Une des difficultés est qu’il est souvent nécessaire de prendre des décisions concernant l’affichage dans la vue elle-même (par exemple, si telle donnée a telle valeur, alors on affiche tel fragment). Ces décisions s’implantent avec des instructions impératives comme les tests ou les boucles, ce qui soulève deux problèmes majeurs:
la vue devient lourde et ressemble à de la programmation, ce qu’on voudrait éviter;
et surtout si on permet de coder dans la vue, qu’est-ce qui empêche un programmeur d’y mettre de la logique métier? On risque de perdre la clarté du modèle MVC.
Dans le cas de Java, les outils pour gérer les vues ont beaucoup évolué, depuis les Java Server Pages (JSP) jusqu’à la Java Standard Tag Library (JSTL) que nous présenterons plus loin. Les moteurs de template, dans l’environnement Java, s’appuient sur ces outils.
Contrôleurs et actions#
Le rôle des contrôleurs est de récupérer les données utilisateurs, de les filtrer et de les contrôler, de déclencher le traitement approprié (via le modèle), et finalement de déléguer la production du document de sortie à la vue. Comme nous l’avons indiqué précédemment, l’utilisation de contrôleurs a également pour effet de donner une structure hiérarchique à l’application, ce qui facilite la compréhension du code et l’accès rapide aux parties à modifier. Indirectement, la structuration logique d’une application MVC en contrôleurs et actions induit donc une organisation normalisée.
Les contrôleurs, comme leur nom l’indique, ont pour rôle essentiel de coordonner les séquences d’actions/réactions d’une application web. Ils reçoivent des requêtes, déclenchent une logique à base d’objets métiers, et choisissent la vue qui constituent la réponse à la requête.
Important
Attention à ne pas placer de logique applicative dans un contrôleur, car (comme dans le cas des vues) le code n’est pas réutilisable dans un autre contexte. Il est impératif de placer cette logique, systématiquement, dans la couche métier (le modèle).
Tout cela est un peu abstrait sans doute pour l’instant, mais l’essentiel est dit et nous vous invitons à revenir aux quelques paragraphes qui précèdent chaque fois que c’est nécessaire pour bien vous imprégner des principes fondamentaux du MVC.
S2. En pratique#
Supports complémentaires :
Allons-y pour une première découverte de la structuration d’une application selon les principes du MVC. Nous allons faire très simple et construire un petit convertisseur de température de l’unité Celsius vers l’unité Fahrenheit. Nous utiliserons un seul contrôleur, un seul objet métier, et deux vues:
la première affiche un formulaire permettant de saisir une valeur en Celsius et de déclencher une action;
la seconde vue affiche le résultat de la conversion de la valeur entrée en Fahrenheit.
Le but est de faire tourner ce premier exemple, et de l’approfondir ensuite.
Note
L’exemple qui suit est volontairement simpliste, pour aller à l’essentiel et fournir la base d’une première discussion. De plus, certaines choses vous paraîtront magiques à première vue. Les explications suivent!
Notre première vue: une page JSP#
Pour construire nos composants « vues » nous allons nous baser sur les Java Server Pages ou JSP. Il s’agit d’une technologie maintenant assez ancienne (à l’échelle de l’informatique en tout cas) et qui a subi de profondes évolutions depuis sa première apparition. Essentiellement, l’idée et de créer des pages HTML dans lesquelles on peut insérer du code java de manière à obtenir des fragments produits dynamiquement par du code. À l’usage cette idée s’est révélée utile mais peu élégante, avec un mélange de différents langages conduisant à beaucoup de lourdeurs et de répétitivité.
Les JSP ont progressivement mué pour éliminer ces inconvénients, selon deux directions principales:
les actions les plus fréquentes sont exprimées par des balises (tags) spéciaux qui s’intègrent beaucoup mieux au code HTML,
une forme de programmation par convention est apparue qui revient à considérer implicitement que le programmeur suit toujours des règles standard de codage (notamment pour les noms), la connaissance de ces règles menant à de très grandes simplifications.
Les deux exemples que nous donnons dans cette première réalisation constituent une illustration basique de ces deux tendances. Il vaut la peine de souligner que l’étape ultime de cette évolution est la création de langages entièrement dédiés à l’intégration d’instructions programmatiques dans du HTML, ces langages faisant partie de fameux frameworks que nous allons explorer ensuite. Et il est également bon de se souvenir que quel que soit l’aspect convivial de ces langages, ils sont ultimement transformés en une page JSP, qui elle-même n’est rien d’autre qu’une servlet déguisée. Soyez donc attentifs à ce qui suit, car c’est vraiment le socle sur lequel se sont construits tous les outils de plus haut niveau utilisés couramment.
Voici donc notre page d’accueil pour ce convertisseur, montrant un formulaire. C’est
du HTML presque pur, la première différence (ici) état une instruction <%@
donnant
des directives sur la production du message HTTP. Cette directive en l’occurrence
indique que l’encodage du message est en UTF-8 et que le contenu (ContentType
)
est du HTML.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Convertisseur de température</title>
</head>
<body>
Vous pouvez convertir une température exprimée en
<b>Celsius</b> en une valeur exprimée en
<b>Fahrenheit</b>.
<hr />
<form method="post"
action="${pageContext.request.contextPath}/convertisseur">
Valeur en Celsius: <input type="text" size="20" name="celsius" /> <br />
<input type="submit" value="Convertir" />
</form>
<hr />
</body>
</html>
Pour ce qui concerne le HTML proprement dit, notez que la méthode HTTP choisie
quand on soumet le formulaire est post
. Et notez également qu’un paramètre
HTTP nommé celsius
est alors transmis à l’URL:
${pageContext.request.contextPath}/convertisseur
Cette expression ${pageContext.request.contextPath}
représente l’URL contextuelle
de notre servlet convertisseur
(que nous allons bientôt créer), autrement dit le domaine et
chemin menant à notre application, dans le contexte particulier où elle est déployée. Dans notre
environnement de développement, cette expression prend la valeur http://localhost:8080/nsy135
,
mais lors d’un déploiement éventuel, l’URL contextuelle sera probablement quelque chose
de la forme http://mondomaine.com/convertisseur
. Il serait bien dommage (en fait, inacceptable)
de coder « en dur » ce genre d’information, avec la sanction d’avoir à changer le code à chaque
changement d’environnement. Les JSP nous permettent d’éviter ce genre de désagrément en utilisant
des expressions dynamiques évaluées au moment de l’exécution.
Pour créer cette JSP, dans Eclipse, utilisez l’option New
et le choix JSP page
. Par défaut
Eclipse place les JSP dans la racine du site, à savoir src/main/webapp
. Vous pouvez
garder ce choix pour l’instant. Copiez/collez le code ci-dessus dans une page que vous
pouvez appeler (par exemple) convinput.jsp
.
Ce n’est pas plus compliqué que cela, et de fait cette page est directement accessible à l’adresse:
http://localhost:8080/nsy135/convinput.jsp
Vous devriez obtenir un affichage semblable à celui de la figure Affichage du formulaire du convertisseur..
Rien de particulier à ce stade, l’affichage est celui d’un formulaire HTML.
Note
Ceux parmi vous qui ont déjà intégré fortement les principes du modèle MVC peuvent froncer les sourcils en se disant: « est-il normal d’accéder à une vue sans passer par un contrôleur? » Et ils ont raison, mais patience, tout sera dévoilé en temps et en heure.
Le contrôleur: une servlet#
Notre contrôleur est une servlet, que nous créons selon la procédure habituelle avec notre Eclipse
(faut-il vous la ré-expliquer?). Appelons-la Convertisseur.java
et associons-la à l’URL
/convertisseur
. Si vous ne comprenez pas la phrase qui précède, relisez les explications
pour la création de servlet que nous avons détaillées à la fin du chapitre Environnement Java.
Nous allons définir les deux méthodes doGet
et doPost
de manière à ce que d’une part la
première affiche notre formulaire précédemment créé, et que d’autre part la seconde reçoive le paramètre
de ce formulaire et effectue la conversion de la température saisie. Il s’agit d’une
implantation extrêmement basique (et insatisfaisante) de la notion d”action présentée
au début de ce chapitre, mais elle nous suffira pour l’instant.
Voici donc la méthode doGet
, déclenchée quand on envoie un message GET
à l’URL /convertisseur
.
/**
* Méthode Get: on affiche le formulaire
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String maVue = "/convinput.jsp";
RequestDispatcher dispatcher =
getServletContext().getRequestDispatcher(maVue);
dispatcher.forward(request,response);
}
Cet exemple est très simple et nous permet d’apprendre une nouvelle technique: la
délégation par le contrôleur (la servlet) de tout ce qui concerne l’affichage
à la vue (la JSP). Le code consiste donc à indiquer quelle JSP on veut déclencher
(ici c’est convinput.jsp
, que l’on prend dans la racine du site,
soit src/main/webapp
)
et à transmettre, en fin de méthode, le flot de l’exécution avec la méthode
forward
à cette JSP.
C’est donc la mise en application du principe MVC qui veut que le contrôleur reçoive les requêtes (ici, pour l’instant, on n’en fait rien), applique les fonctionnalités métiers implantés par les modèles (ici, pour l’instant, il n’y en a pas) et finalement transmette à la vue les éléments nécessaires à l’affichage.
On pourrait aller un peu plus loin en adoptant la convention suivante: la vue (JSP) porte le même nom que la servlet, et on lui transfère systématiquement l’exécution. Il n’y aurait plus le moindre code à produire! C’est en fait le comportement par défaut appliqué par certains frameworks, d’où leur aspect parfois magique quand on les découvre.
Important
nous avons maintenant deux manières d’appeler notre page JSP: directement,
ou en passant par la servlet. Dans une approche MVC, seule la seconde est bonne.
Pour interdire la première, il faudrait placer la JSP dans un répertoire
inaccessible par une URL. Un bon candidat pour cela est le répertoire WEB-INF
.
Cette approche (interdiction d’exécuter une vue) est en fait forcée par les frameworks donc nous n’aurons plus à nous poser ce genre de question quand nous y aurons recours.
Le modèle: une classe Temperature
#
Notre modèle est on ne peut plus simple: il consiste en une classe représentant une température, et capable de fournir la valeur de cette température en degrés Celsius ou Fahrenheit.
La classe Temperature
donnée ci-dessous va suffire à nos besoins.
package modeles; /** * Une classe permettant d'obtenir une température en Celsius ou Fahrenheit. * */ public class Temperature { /** * La valeur, exprimée en degrés Celsius */ private double celsius; /** * Le constructeur, prend des Celsius en paramètres */ public Temperature(double valeurCelsius) { celsius = valeurCelsius; } /** * Pour obtenir la valeur en Celsius * * @return Valeur de la température en Celsius */ public double getCelsius() { return celsius; } /** * Pour obtenir la valeur en Fahrenheit * * @return Valeur de la température en Fahrenheit */ public double getFahrenheit() { return (celsius * 9/5) + 32; } }
Remarquez que nous suivons les bonnes pratiques de base de la programmation objet, entre autres:
aucune propriété n’est publique;
des méthodes
set
etget
, au nommage normalisé, servent à lire/écrire les propriétés;les commentaires sont abondants et permettront de produire une
javadoc
pour documenter le projet.
De plus, conformément aux principes du MVC, cette classe est totalement indépendante du contexte Web. Elle peut en fait être utilisée dans n’importe quelle application Java. Il serait évidemment souhaitable de l’enrichir avec quelques méthodes, par exemple pour l’initialiser à partir d’une valeur en Fahrenheit et pas en Celsius, pour ajouter des températures, etc. Je vous laisse ce plaisir.
Pour créer cette classe, vous devriez pouvoir trouver la démarche tout seul maintenant. Utilisez
à nouveau l’option New
du menu contextuel du projet, et prenez le choix Class
dans le menu déroulant.
Nous avons placé notre classe dans le package modeles
, le fichier sera donc sauvegardé dans src/modeles
.
La seconde action: conversion#
Équipés de ce modèle, nous sommes prêts à recevoir une température (en Celsius)
entrée par l’utilisateur, à la convertir et à l’afficher. La conversion correspond à une
seconde action que nous implantons dans la méthode doPost
de notre servlet. La voici:
1 /**
2 * Méthode Post: on affiche la conversion
3 */
4 protected void doPost(HttpServletRequest request, HttpServletResponse response)
5 throws ServletException, IOException {
6 // On devrait récuperer la valeur saisie par l'utilisateur
7 String valCelsius = request.getParameter("celsius");
8
9 if (valCelsius.isEmpty()) {
10 // Pas de valeur: il faudrait afficher un message, etc.
11 valCelsius = "20";
12 }
13
14 // Action: appliquons le convertisseur. Espérons que valCelsius représente
15 // bien un nombre, sinon...
16 Temperature temp = new Temperature(Double.valueOf(valCelsius));
17 // Enregistrons l'objet dans la requête
18 request.setAttribute("temperature", temp);
19
20 // Transfert à la vue
21 String maVue = "/convoutput.jsp";
22 RequestDispatcher dispatcher =
23 getServletContext().getRequestDispatcher(maVue);
24 dispatcher.forward(request,response);
25 }
Décomposons ce code. La première étape consiste à récupérer le paramètre saisi par l’utilisateur. Souvenez-vous
de notre formulaire et de son champ celsius
. Ce champ, quand l’utilisateur valide le formulaire,
devient un paramètre celsius
de la requête HTTP. C’est donc logiquement par l’objet
request
que nous pouvons récupérer la valeur de ce paramètre:
String valCelsius = request.getParameter("celsius");
Deux remarques s’imposent: (i) on récupère des chaînes de caractères, car les paramètres HTTP ne sont pas typés, (ii) rien ne garantit que l’utilisateur a vraiment saisi une valeur avant d’appuyer sur le bouton, et encore moins que cette valeur est convertible en numérique. Il y aurait toute une phase de contrôle à intégrer dans le code, que nous nous épargnons ici.
Passons donc directement à la suite: on instancie un objet de notre classe Temperature
avec la valeur du paramètre:
Temperature temp = new Temperature(Double.valueOf(valCelsius));
C’est ici que le logique « métier » de l’application intervient sous la forme d’un objet du modèle. Pas question de mettre directement dans le code du contrôleur la conversion de Celsius en Fahrenheit, car elle ne serait pas réutilisable dans un autre contexte. Le contrôleur se charge des interactions, de la coordination des composants, et c’est tout.
Nous en arrivons à une partie importante et pas forcément évidente à comprendre dans un premier temps:
comment passer à la vue, qui va se charger de l’affichage, l’information à afficher, à savoir
la température? Le principe est que la servlet et la vue partagent un certain nombre
d’objets formant le contexte de l’exécution d’une requête côté serveur. Parmi ces
objets, on trouve request
et quelques autres (session
par exemple) que nous
découvrirons ensuite.
En conséquence: toute information disponible dans request
sera accessible par la vue JSP.
Par ailleurs, l’objet request
a la capacité d’enregistrer des attributs dont
la valeur peut être n’importe quel objet java. Ici, nous créons donc un nouvel
attribut de request
, nommé temperature
, et nous lui affectons notre objet-modèle:
request.setAttribute("temperature", temp);
C’est le mécanisme général: la servlet place dans request
les informations dont la vue
a besoin, et déclenche ensuite la vue (ici, convoutput.jsp
)
avec la méthode forward
que nous avons déjà rencontrée.
Il reste à voir comment la vue gère cet affichage.
L’autre vue JSP#
La voici.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Résultat de la conversion</title>
</head>
<body>
<p>Vous avez demandé la conversion en Fahrenheit de la valeur en
Celsius ${requestScope.temperature.celsius}</p>
<p>
<b>Et le résultat est: ${requestScope.temperature.fahrenheit}
degrés Fahrenheit </b>!
</p>
</body>
</html>
Toute la nouveauté (et la « magie » de la chose ) tient à ces expressions intégrées au HTML:
${requestScope.temperature.celsius}
Elles offrent une syntaxe extrêmement simple pour accéder aux objets placés dans le
contexte de la vue par la servlet. Le seul de ces objets que nous avons utilisés
pour l’instant est la requête, désignée ici par requestScope
. L’expression
requestScope.temperature
fait donc référence à l’attribut temperature
que
nous avons ajouté à la requête avant l’appel au JSP.
Et finalement que signifie temperature.celsius
? C’est ici qu’un peu de magie basée sur la convention de codage intervient: le
contexte d’exécution « devine » que temperature.celsius
est une valeur obtenue
par la méthode getCelsius()
appliquée à l’objet temperature
. La convention
de nommage (celle dite des java beans, à base de getters et setters) permet
cette substitution.
Nous avons fini ce premier tour, minimaliste mais complet, d’une application MVC
écrite avec JEE. Créez cette page JSP avec Eclipse, ajoutez à la servlet
les deux méthodes doGet
et doPost
vues ci-dessus, et exécutez votre
application qui devrait afficher un résultat de conversion semblable
à celui de la figure Affichage de la seconde JSP.
Il est maintenant probablement profitable pour vous de faire quelques modifications au code, de bien réfléchir aux mécanismes d’échange, au rôle des différents composants, etc. Vous êtes donc invités à effectuer l’exercice suivant.
Exercice: compléter notre convertisseur
Il s’agit de compléter naturellement les classes, controleurs et JSP donnés en exemple précédemment pour permettre la saisie d’une valeur en Celsius OU ou Fahrenheit. L’application doit effectuer la conversion dans l’autre unité et produire l’affichage approprié.
Passez ensuite à la dernière session qui va récapituler ce que nous avons appris et en tirer un bilan.
S3: un embryon MVC#
Supports complémentaires :
Ce chapitre avait pour objectif une introduction la plus concise possible aux principes du MVC. Récapitulons les acquis, les manques, et ce qui reste à faire pour aller jusqu’au bout.
Les principes#
Si on reprend le modèle tel qu’il est décrit dans la section S1: Principe général pour le comparer à notre implantation, un certain nombre de principes ont bien été respectés:
le modèle, sous forme de classes Plain Old Java, est bien indépendant du contexte (une application Web) et se charge de l’implantation de la logique « métier »; les classes du modèle pourraient très bien être développées par un programmeur totalement ignorant de la programmation Web;
le contrôleur est implanté par des servlets qui font parfaitement l’affaire car elles sont justement conçus pour être au centre des échanges HTTP gérés par le serveur; nous avons fait attention à ne pas introduire le moindre texte « en dur », la moindre balise HTML: le contrôleur, c’est de la coordination de composants, point.
enfin, parlons des vues; c’est d’une certaine manière le point le plus délicat car rien dans le langage Java de base ne correspond au besoin d’intégrer des éléments dynamiques dans des fragments HTML; nous avons intégré directement les tag libraries dans les JSP pour obtenir une syntaxe moderne, assez propre, qui n’inclut aucune programmation java.
Tout cela est donc relativement satisfaisant, mais il est bien entendu nécessaire d’aller plus loin. Voici une liste de ce que nous allons découvrir dans la suite pour enrichir cette première approche.
Contrôleurs, actions, sessions#
Rappelez-vous, dans la section S1: Principe général, nous avions indiqué qu’un contrôleur se décomposait en actions, ce qui permet de donner une structure hiérarchique à notre application MVC. Un contrôleur dans cette optique se charge d’une partie bien identifiée des fonctionnalités (par exemple la gestion des utilisateurs), et est lui-même constitué d’action correspondant aux tâches élémentaires, par exemple le formulaire d’identification, le formulaire de création d’un compte utilisateur, l’édition d’un compte, etc.
Les servlets ne sont pas nativement équipées pour une décomposition en actions, et cela
constitue une sévère limite pour les considérer comme des candidats totalement appropriés
à l’implantation des contrôleurs. Nous avons contourné cette limitation dans notre application
basique en utilisant doGet
et doPost
pour nos deux actions, mais cela ne se généralise
évidemment pas à un nombre quelconque d’actions.
Par ailleurs, nous n’avons que superficiellement abordé la gestion des échanges HTTP: paramétrage de la requête, de la réponse, et surtout gestion des sessions.
Améliorez votre vue#
Les premiers pas avec les JSTP (JSP étendues aux tag libraries) sont vraiment élémentaires. Nous ne savons pas traiter les éléments d’un tableau par exemple, ou effectuer un test (« j’affiche ce fragment HTML si et seulement si cette variable vaut xxx »). Il serait bon également de pouvoir combiner des fragments HTML, par exemple pour utiliser un template général pour notre site, que nous appliquerions à chaque contrôleur.
Modèle: et nos bases de données?#
Nous ne savons pas rendre les instances d’un modèle persistantes dans une base de données. La persistance est la capacité d’une information à survivre à l’interruption de l’application ou à l’arrêt de la machine. En pratique, elle est conditionnée par le stockage sur un support non volatile comme un disque magnétique ou SSD. Ce stockage est assuré par une base de données.
Notre convertisseur n’a pas besoin de persistance car la règle de conversion est immuable. Dans la majorité des applications, le recours à une base de données est indispensable et constitue un des points les plus délicats de la conception et de la réalisation. Nous allons y venir, et de manière très détaillée.
La persistance est une des propriétés des objets du modèle. Elle est optionnelle; l’état de certains objets doit être persistant, pour d’autres ce n’est pas nécessaire. Selon le principe de séparation des tâches du MVC, les fonctions liées à la persistance doivent apparaître comme un des services fournis par les objets et utilisés par les contrôleurs. Mais en aucun cas ces derniers ne doivent avoir à se soucier des détails de la fonction de persistance. Pour les vues c’est encore plus radical: elles ne doivent rien savoir de la persistance.
L’arme ultime: un framework#
Comment répondre aux besoins mentionnés ci-dessus à partir des bases technologiques que nous venons d’étudier? La réponse est: frameworks. Un framework est une boîte à outil, construite sur des technologies standard, qui offre aux programmeurs un moyen d’appliquer facilement les bonnes pratiques (par exemple la décomposition d’un contrôleur en actions) et même force le recours à ces pratiques, avec divers avantages déjà cités (entre autres une forme de normalisation du développement favorable au travail collaboratif).
Les frameworks sont construits sur des outils existant, et il est donc toujours intéressant de connaître ces derniers. C’est pourquoi il est utile de consacrer un peu de temps à étudier l’environnement JEE pour les applications Web, car tous les frameworks sont des couches (parfois empilées) au-dessus de cet environnement. Sans ces connaissances de base, un framework apparaît comme un monstre abstrait qu’il est bien difficile de maîtriser et dont la courbe d’apprentissage est longue et pénible.
Notre framework MVC minimal#
Nous n’irons pas beaucoup plus loin dans ce cours dans l’apprentissage des frameworks MVC car nous avons presque toutes les connaissances nécessaires pour étudier comment combiner une application Web et un framework de persistance (qui, rappelons-le, est notre sujet principal). À ce stade vous avez plusieurs choix:
vous connaissez déjà un framework MVC comme Spring (ou, d’ailleurs, un autre type de framework comme Java Server Faces) et vous l’utiliserez par la suite;
vous avez décidé d’apprendre un framework MVC, à vous de jouer,
vous vous en tenez là pour l’instant (recommandé) et nous allons nous contenter pour la suite de régler quelques-uns des problèmes mentionnés ci-dessus en adoptant quelques conventions de développement.
Voici donc quelques règles que nous allons suivre à partir de maintenant pour organiser notre code.
Répertoires#
Nous créons deux packages:
controleurs
contiendrait tous les contrôleurs, chacun implanté par une servlet;
modeles
contiendra toutes les classes du modèle.
Nous créons également un répertoire vues
dans src/main/webapp
. Dans ce répertoire nous
créerons un répertoire par contrôleur, contenant toutes les vues du contrôleur. Par convention,
le nom du sous-répertoire sera celui du contrôleur, sans majuscules.
Le répertoire des vues d’un contrôleur contiendra au moins une vue, nommée index.jsp
, qui
présentera le contrôleur et ses actions.
Contrôleurs et actions#
Nous passerons un paramètre HTTP action
, en mode GET
, au contrôleur. Ce dernier, si ce
paramètre est absent ou s’il ne correspond pas à une action connue, affichera la vue index.jsp
.
Sinon, le contrôleur déclenchera l’action dont le nom est donné par le paramètre.
Voici donc le squelette d’un contrôleur codé en respectant ces règles et convention. Nous prenons l’exemple de notre convertisseur.
package controleurs;
/**
* Servlet implementation class Convertisseur
*/
@WebServlet("/convertisseur")
public class Convertisseur extends HttpServlet {
private static final String VUES="/vues/convertisseur/", DEFVUE="index.jsp";
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// On devrait récupérer l'action requise par l'utilisateur
String action = request.getParameter("action");
// La vue par défaut
String maVue = VUES + DEFVUE;
try {
if (action == null) {
// Rien à faire, la vue est celle par défaut
} else if (action.equals("formulaire")) {
// Action + vue formulaire de saisie
maVue = VUES + "formulaire.jsp";
} else if (action.equals("conversion")) {
maVue = VUES + "conversion.jsp";
} else if (action.equals(...)) {
// Etc.
}
} catch (Exception e) {
maVue = VUES + "exception.jsp";
request.setAttribute("message", e.getMessage());
}
// On transmet à la vue
RequestDispatcher dispatcher = getServletContext()
.getRequestDispatcher(maVue);
dispatcher.forward(request, response);
}
}
On pourrait faire mieux encore, mais il faut savoir s’arrêter (si vous avez des idées d’amélioration n’hésitez pas à les appliquer). Avec ces règles nous avons déjà gagné un organisation du code plus claire, et nous gérons la notion d’action d’une manière un peu simpliste et laborieuse (il faut passer un paramètre à chaque requête). Notez également que nous gérons les exceptions au niveau de chaque contrôleur par une page JSP spéciale.
À vous de mettre en pratique ces règles pour une premier contrôleur complet.
Exercice: le contrôleur de conversion
Implantez un contrôleur de conversion de températures qui propose, dans
la page d’accueil (index.jsp
) de convertir de Celsius en Fahrenheit ou
l’inverse, donne accès aux formulaires de conversion correspondants à ces choix, et affiche le
ensuite résultat. Décomposez en actions, et suivez les règles énoncées ci-dessus.
Correction
Vous trouverez dans cette archive le code correspondant à une correction possible de cet exercice.
Résumé: savoir et retenir#
Vous devriez à l’issue de ce chapitre avoir acquis une compréhension générale des principes des frameworks MVC, par combinaison des concepts et de la mise en pratique que nous proposons. Il est important que vous soyez à l’aise à partir de maintenant pour créer des contrôleurs, des actions et des vues.
Les points suivants devraient être clairs:
les contrôleurs sont les composants qui réagissent aux requêtes HTTP, ils ne contiennent ni code métier, ni aucun élément de présentation;
le modèle implante les fonctionnalités métiers; une classe du modèle doit être totalement indépendante d’un contexte d’exécution (application web ou autre) ou de critères de présentation;
les vues se chargent de l’affichage, et c’est tout.
Pour ces dernières, le chapitre qui suit donnera des éléments complémentaires pour bien contrôler la présentation des données. Nos aborderons ensuite, pour le restant du cours, la gestion des accès aux bases de données pour finir de régler la liste des problèmes évoqués dans la dernière session.