Les bases du développement web MVC en Java - par l'exemple

-

serge.tahe@istia.univ-angers.fr mai 2006

Les bases du développement web MVC en Java, par l'exemple

1/264

1 Introduction
On se propose ici de découvrir les bases de la programmation web MVC en Java avec les servlets et les pages JSP. Après avoir lu ce document et en avoir testé les exemples, le lecteur devrait avoir acquis les concepts de base de la programmation web en Java. Afin d'avoir la compréhension des exercices proposés, l'usage du document [ref1] : "Introduction à la programmation web en Java" (http://tahe.developpez.com/java/web/) peut être utile. Les conseils de lecture qui sont donnés au début de certaines sections référencent cet article. Ce document peut être exploité de diverses manières : 1. 2. on installe les outils, on télécharge les codes sur le site de ce document et on fait les tests proposés. Un débutant ne retirera rien d'une telle méthode. Un développeur expérimenté, lui, peut le faire, s'il est seulement intéressé par tester l'environnement de développement Eclipse / WTP. on installe les outils, on suit le document et on fait les tests proposés en faisant du copier / coller à partir de ce document. On ne lit pas le document [ref1]. En procédant de cette façon, on va commencer à acquérir les bases du développement web mais certains points vont rester obscurs ou magiques. C'est une méthode possible si on veut aller vite avec l'intention d'approfondir seulement plus tard, lors de l'écriture d'une application personnelle et peut-être avec un autre document de référence que [ref1]. on fait la même chose qu'en 2, mais on lit [ref1] quand c'est conseillé. Cette méthode plus lente va éliminer certains des points obscurs et magiques de la méthode 2 mais ne prépare pas bien à un envol personnel parce que les codes obtenus par copier / coller ne seront pas forcément bien compris. on fait la même chose qu'en 3, mais on tape soi-même tous les codes. C'est évidemment plus long et plus fastidieux mais très efficace. Pour taper les codes, il faut les lire ce qui induit une attention au code et par-là même un début de compréhension. Cette recopie manuelle se fera rarement sans erreurs de recopie. Ces erreurs, qui seront signalées par les différents outils d'Eclipse, vont amener le lecteur à s'interroger sur le code qu'il a écrit et ainsi à mieux le comprendre.

3. 4.

Ce document reprend une bonne partie d'un article paru en janvier 2005 intitulé " Développement web en Java avec Eclipse et Tomcat " et disponible à l'url [http://tahe.developpez.com/java/eclipse/]. Les apports vis à vis de ce document sont les suivants :
• • •

l'utilisation du plugin WTP d'Eclipse pour le développement d'applications web, une réorganisation du document pour mettre l'accent sur l'architecture MVC 3tier qui était à peine abordée dans le document initial, un exemple d'application web MVC 3-tier utilisant les données d'un SGBD.

Une application web 3tier a la structure suivante :

utilisateur

Couche web [web]

Couche métier [metier]

Couche d'accès aux données [dao]

Données

• •

la couche [dao] s'occupe de l'accès aux données, le plus souvent des données persistantes au sein d'un SGBD. Mais cela peut être aussi des données qui proviennent de capteurs, du réseau, ... la couche [metier] implémente les algorithmes " métier " de l'application. Cette couche est indépendante de toute forme d'interface avec l'utilisateur. Ainsi elle doit être utilisable aussi bien avec une interface console, une interface web, une interface de client riche. Elle doit ainsi pouvoir être testée en-dehors de l'interface web et notamment avec une interface console. C'est généralement la couche la plus stable de l'architecture. Elle ne change pas si on change l'interface utilisateur ou la façon d'accéder aux données nécessaires au fonctionnement de l'application. la couche [web] qui est l'interface web qui permet à l'utilisateur de piloter l'application et d'en recevoir des informations.

L'article [http://tahe.developpez.com/java/eclipse/] ne donne que des exemples d'applications web se limitant à la seule couche [web]. Ici on donne un exemple utilisant les trois couches.

Les bases du développement web MVC en Java, par l'exemple

2/264

2 Les outils utilisés dans le document
Nous utiliserons dans ce document les outils suivants : - un JDK Java 1.5 - le serveur web TOMCAT (http://tomcat.apache.org/), - l'environnement de développement ECLIPSE (http://www.eclipse.org/) avec le plugin WTP (Web Tools Package). - un navigateur (IE, NETSCAPE, MOZILLA Firefox, OPERA, ...). Ce sont des outils gratuits. De façon générale, de nombreux outils libres peuvent être utilisés dans le développement Web :
Jbuilder Foundation Eclipse Struts Bibliothèques JAVA Spring MySQL Postgres Firebird SGBD Hypersonic SQL Server Express 2005 Oracle Express Tomcat Conteneurs de servlets Resin Jetty Navigateurs Netscape Mozilla

http://www.borland.com/jbuilder/foundation/index.html http://www.eclipse.org/ http://struts.apache.org/ http://www.springframework.org http://www.mysql.com/ http://www.postgresql.org/ http://firebird.sourceforge.net/ http://hsqldb.sourceforge.net/ http://msdn.microsoft.com/vstudio/express/sql/ http://www.oracle.com/database/index.html http://tomcat.apache.org/ http://www.caucho.com/ http://jetty.mortbay.org/jetty/ http://www.netscape.com/ http://www.mozilla.org

IDE JAVA

2.1

Java 1.5

Le conteneur de servlets Tomcat 5.x nécessite une machine virtuelle Java 1.5. Il faut donc tout d'abord installer cette version de Java qu'on trouvera chez Sun à l'url [http://www.sun.com] -> [http://java.sun.com/j2se/1.5.0/download.jsp] (mai 2006) : étape 1 :

étape 2 :

Les bases du développement web MVC en Java, par l'exemple

3/264

étape 3 : Lancer l'installation du JDK 1.5 à partir du fichier téléchargé.

2.2

Le conteneur de servlets Tomcat 5

Pour exécuter des servlets, il nous faut un conteneur de servlets. Nous présentons ici l'un d'eux, Tomcat 5.x, disponible à l'url http://tomcat.apache.org/. Nous indiquons la démarche (mai 2006) pour l'installer. Si une précédente version de Tomcat est déjà installée, il est préférable de la supprimer auparavant.

Pour télécharger le produit, on suivra le lien [Tomcat 5.x] ci-dessus :

On pourra prendre le .exe destiné à la plate-forme windows. Une fois celui-ci téléchargé, on lance l'installation de Tomcat : Faire [next] ->

Les bases du développement web MVC en Java, par l'exemple

4/264

On accepte les conditions de la licence ->

Faire [next] ->

Accepter le dossier d'installation proposé ou le changer avec [Browse] ->
Les bases du développement web MVC en Java, par l'exemple

5/264

x a besoin d'un JRE 1. Ceci fait.5 téléchargé au paragraphe 2. utilisez le bouton [Install] pour installer Tomcat 5. Ici on a mis [admin / admin] -> 1 Tomcat 5.5. Si aucun JRE n'est trouvé.Fixer le login / mot de passe de l'administrateur du serveur Tomcat. Il doit normalement trouver celui qui est installé sur votre machine. Ci-dessus.x -> Les bases du développement web MVC en Java.1. par l'exemple 6/264 . le chemin désigné est celui du JRE 1. désignez sa racine en utilisant le bouton [1].

que nous appellerons désormais <tomcat>. par l'exemple 7/264 . Cette dernière peut être enlevée de la barre des tâches : L'installation de Tomcat s'est faite dans le dossier choisi par l'utilisateur.Le bouton [Finish] termine l'installation. Nous utilisons le lien [Monitor] cidessous pour lancer l'outil d'arrêt/démarrage de Tomcat : Nous retrouvons alors l'icône présentée précédemment : Les bases du développement web MVC en Java. L'arborescence de ce dossier pour la version Tomcat 5.5.17 téléchargée est la suivante : L'installation de Tomcat a amené un certain nombre de raccourcis dans le menu [Démarrer]. La présence de Tomcat est signalée par une icône à droite dans la barre des tâches de windows : Un clic droit sur cette icône donne accès aux commandes Marche – Arrêt du serveur : Nous utilisons l'option [Stop service] pour arrêter maintenant le serveur web : On notera le changement d'état de l'icône.

Nous devons obtenir une page analogue à la suivante : On pourra suivre les liens ci-dessous pour vérifier la correcte installation de Tomcat : Tous les liens de la page [http://localhost:8080] présentent un intérêt et le lecteur est invité à les explorer.Pause] . par l'exemple 8/264 . Nous lançons le serveur par [Start] puis.Le moniteur de Tomcat peut être activé par un double-clic sur cette icône : Les boutons [Start .arrêter . avec un navigateur nous demandons l'url http://localhost:8080. Nous aurons l'occasion de revenir sur les liens permettant de gérer les applications web déployées au sein du serveur : Les bases du développement web MVC en Java.Stop .relancer le serveur.Restart nous permettent de lancer .

Le dossier [vues] contient un fichier HTML statique : dont le contenu est le suivant : <html> <head> <title>Application exemple</title> </head> <body> Application exemple active ..3 Déploiement d'une application web au sein du serveur Tomcat Lectures [ref1] : chapitre 1. par l'exemple 9/264 ..3. ressources (.2. Construisons l'application web dont l'arborescence est la suivante : On construira l'arborescence ci-dessus avec l'explorateur windows.. . Soit <webapp> le dossier d'une application web.3.. 2. Nous voulons maintenant qu'elle soit disponible via le serveur web Tomcat..1 Déploiement Une application web doit suivre certaines règles pour être déployée au sein d'un conteneur de servlets. Revenons sur l'arborescence du répertoire <tomcat> : La configuration des applications web déployées au sein du serveur Tomcat se fait à l'aide de fichiers XML placés dans le dossier [<tomcat>\conf\Catalina\localhost] : Les bases du développement web MVC en Java.3.1.jsp. on obtient la page suivante : L'URL affichée par le navigateur montre que la page n'a pas été délivrée par un serveur web mais directement chargée par le navigateur. Une application web est composée de : classes archives java vues.3. </body> </html> Si on charge ce fichier dans un navigateur.3 2. chapitre2 : 2.) dans le dossier <webapp>\WEB-INF\classes dans le dossier <webapp>\WEB-INF\lib dans le dossier <webapp> ou des sous-dossiers L'application web est configurée par un fichier XML : <webapp>\WEB-INF\web. . Les dossiers [classes] et [lib] sont ici vides.xml. 2.2.html.

Plutôt que d'adopter cette démarche. Son contenu est le suivant : Le dossier [admin] doit être copié dans [<tomcat>\server\webapps] où <tomcat> est le dossier où a été installé tomcat 5.2 Administration de Tomcat Sur sa page d'entrée http://localhost:8080. par l'exemple 10/264 .x : Les bases du développement web MVC en Java. nous allons utiliser les outils web que nous offre Tomcat.x nécessite un paquetage particulier appelé " admin ".Ces fichiers XML peuvent être créés à la main car leur structure est simple. 2.3. le serveur nous offre des liens pour l'administrer : Le lien [Tomcat Administration] nous offre la possibilité de configurer les ressources mises par Tomcat à la disposition des applications web déployées en son sein. par exemple un pool de connexions à une base de données. Retournons sur le site de Tomcat : Téléchargeons le zip libellé [Administration Web Application] puis décompressons-le. Suivons le lien : La page obtenue nous indique que l'administration de Tomcat 5.

il faut redonner les informations que nous avons données au cours de l'installation de Tomcat. j'ai du d'abord demander manuellement l'url [http://localhost:8080/admin/index.Le dossier [localhost] contient un fichier [admin. redemandons la page d'entrée du serveur web : Suivons le lien [Tomcat Administration]. pour obtenir la page ci-dessous. Le bouton [Login] nous amène à la page suivante : Les bases du développement web MVC en Java. Dans notre cas. Ici. J'ignore s'il s'agit ou non d'une erreur de procédure de ma part. nous donnons le couple admin / admin. Puis avec un navigateur.xml] qui doit être copié dans [<tomcat>\conf\Catalina\localhost] : Arrêtons puis relançons Tomcat s'il était actif.jsp]. par l'exemple 11/264 . Ce n'est qu'ensuite que le lien [Tomcat Administration] ci-dessus a fonctionné. Nous obtenons une page d'identification : Note : en fait.

de définir les caractéristiques des applications web déployées par le serveur (Service Catalina) Suivons le lien [Roles] ci-dessus : Un rôle permet de définir ce que peut faire ou ne pas faire un utilisateur ou un groupe d'utilisateurs. Le rôle [manager] cidessous donne le droit de gérer les applications web déployées dans Tomcat (déploiement. déchargement). Nous allons créer un utilisateur [manager] qu'on associera au rôle [manager] afin de lui permettre de gérer les applications de Tomcat. arrêt. Nous utilisons l'option [Create New User] pour créer un nouvel utilisateur : Les bases du développement web MVC en Java. Pour cela. par l'exemple 12/264 . démarrage. de gérer les utilisateurs/administrateurs de Tomcat (Users). des données d'environnement accessibles à toutes les applications (Environment Entries). les informations nécessaires à l'envoi de courrier (Mail Sessions). de gérer des groupes d'utilisateurs (Groups). On associe à un rôle certains droits. nous suivons le lien [Users] de la page d'administration : Nous voyons qu'un certain nombre d'utilisateurs existent déjà. de définir des rôles (= ce que peut faire ou non un utilisateur).Cette page permet à l'administrateur de Tomcat de définir        des sources de données (Data Sources). Chaque utilisateur est associé à un ou plusieurs rôles et dispose des droits de ceux-ci.

Le nouvel utilisateur apparaît dans la liste des utilisateurs : Ce nouvel utilisateur va être ajouté dans le fichier [<tomcat>\conf\tomcat-users. <role rolename="role1"/> 5. <user username="role1" password="tomcat" roles="role1"/> 9. C'est notamment ainsi qu'il faut procéder si. <tomcat-users> 3. <user username="both" password="tomcat" roles="tomcat.xml] : dont le contenu est le suivant : 1. <user username="tomcat" password="tomcat" roles="tomcat"/> 8. Les bases du développement web MVC en Java. par l'exemple 13/264 .manager"/> 12. </tomcat-users> • ligne 10 : l'utilisateur [manager] qui a été créé Une autre façon d'ajouter des utilisateurs est de modifier directement ce fichier. on a oublié le mot de passe de l'administrateur admin ou de manager.Nous donnons à l'utilisateur manager le mot de passe manager et nous lui attribuons le rôle manager. d'aventure. <?xml version='1. <user username="admin" password="admin" roles="admin. <role rolename="admin"/> 7.0' encoding='utf-8'?> 2. <role rolename="manager"/> 6. <user username="manager" password="manager" fullName="" roles="manager"/> 11. <role rolename="tomcat"/> 4.role1"/> 10. Nous utilisons le bouton [Save] pour valider cet ajout.

c. En effet. Nous obtenons une page listant les applications actuellement déployées dans Tomcat : Nous pouvons ajouter une nouvelle application grâce à des formulaires placés en bas de la page : Les bases du développement web MVC en Java. Nous nous identifions comme manager / manager.2. nous voyons que l'utilisateur [admin] a également le rôle [manager].a. l'utilisateur de rôle [manager] que nous venons de créer.d.3 Gestion des applications web déployées Revenons maintenant à la page d'entrée [http://localhost:8080] et suivons le lien [Tomcat Manager] : Nous obtenons alors une page d'authentification. par l'exemple 14/264 . Nous pourrions donc utiliser également l'authentification [admin / admin]. seul un utilisateur ayant ce rôle peut utiliser ce lien.3.xml]. Ligne 11 de [tomcatusers.

nous demanderons à Tomcat l'URL [http://localhost:8080/exemple/vues/exemple. Le contexte sert donc à donner un nom à la racine de l'arborescence de l'application web déployée. par l'exemple 15/264 .html]. Nous le faisons de la façon suivante : Context Path Directory URL /exemple C:\data\2005-2006\eclipse\dvp-eclipse-tomcat\exemple le nom utilisé pour désigner l'application web à déployer le dossier de l'application web Pour obtenir le fichier [C:\data\2005-2006\eclipse\dvp-eclipse-tomcat\exemple\vues\exemple. nous obtenons la page réponse suivante : et la nouvelle application apparaît dans la liste des applications déployées : Commentons la ligne du contexte /exemple ci-dessus : Les bases du développement web MVC en Java. Si tout se passe bien. nous voulons déployer au sein de Tomcat. Nous utilisons le bouton [Deploy] pour effectuer le déploiement de l'application.html]. l'application exemple que nous avons construite précédemment.Ici.

Nous demandons la page [exemple.xml placé dans le dossier [<tomcat>\conf\Catalina\localhost]. par l'exemple 16/264 . Revenons à l'interface d'administration de Tomcat : Supprimons l'application [/exemple] avec son lien [Undeploy] : L'application [/exemple] ne fait plus partie de la liste des applications actives.html] : Une autre façon de déployer une application web au sein du serveur Tomcat est de donner les renseignements que nous avons donnés via l'interface web./exemple Démarrer Arrêter Recharger Undeploy lien sur http://localhost:8080/exemple permet de démarrer l'application permet d'arrêter l'application permet de recharger l'application. suppression du contexte [/exemple]. puis visualisons la liste des applications actives avec l'administrateur de Tomcat : Les bases du développement web MVC en Java. L'application disparaît de la liste des applications disponibles. C'est nécessaire par exemple lorsqu'on a ajouté. Maintenant définissons le fichier [exemple. Maintenant que notre application /exemple est déployée. 2. Plaçons ce fichier dans <tomcat>\conf\Catalina\localhost : Arrêtons et relançons Tomcat si besoin est.html] via l'url [http://localhost:8080/exemple/vues/exemple. dans un fichier [contexte]. nous pouvons faire quelques tests.xml] suivant : 1. <Context docBase="C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/exemple"> </Context> Le fichier XML est constitué d'une unique balise <Context> dont l'attribut docBase définit le dossier contenant l'application web à déployer. modifié ou supprimé certaines classes de l'application. où [contexte] est le nom de l'application web.

Avec des versions précédentes. où <exemple> est le dossier physique de l'application web [/exemple]. Ce fichier est le suivant : Les bases du développement web MVC en Java. on peut également définir son contexte dans le fichier [<tomcat>\conf\server.xml] est automatiquement supprimé du dossier [<tomcat>\conf\Catalina\localhost]. de la même façon que précédemment.html] : Une application web ainsi déployée peut être supprimée de la liste des applications déployées.4 Application web avec page d'accueil Lorsque nous demandons l'url [http://localhost:8080/exemple/]. lorsque le contexte est demandé. l'url : [http://localhost:8080/exemple/vues/exemple. avec un navigateur. Demandons. nous obtenons la réponse suivante : Ce résultat dépend de la configuration de Tomcat. pour déployer une application web au sein de Tomcat. par l'exemple 17/264 . Tomcat empêche cet affichage. On peut faire en sorte que. C'est une bonne chose que désormais par défaut. nous aurions obtenu le contenu du dossier physique de l'application [/exemple].3. Pour cela. Nous ne développerons pas ce point ici. le fichier [exemple. nous créons un fichier [web. Enfin. 2. avec le lien [Undeploy] : Dans ce cas.xml] que nous plaçons dans le dossier <exemple>\WEB-INF.L'application [/exemple] est bien présente. on affiche une page dite d'accueil.xml].

Sauvegardons ce fichier [web.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java. il est possible de le forcer à recharger l'application web [/exemple] avec le lien [Recharger] : Lors de cette opération de " rechargement ". 6. ligne 7 : le nom d'affichage de l'application web. demandons l'URL [http://localhost:8080/exemple/] : Le mécanisme des fichiers d'accueil a fonctionné.4"> • • lignes 2-5 : la balise racine <web-app> avec des attributs obtenus par copier / coller du fichier [web.com/xml/ns/j2ee/webapp_2_4.sun. 2.1. Avec un navigateur.com/xml/ns/j2ee" xmlns:xsi="http://www. Ce nom est affiché par exemple par l'administrateur Tomcat : • • ligne 8 : description de l'application web.sun. 5. 4.w3. lorsqu'un client demandera l'url [/exemple].xml] de l'application [/admin] de Tomcat (<tomcat>/server/webapps/admin/WEB-INF/web. ce qui n'est pas possible avec le nom de contexte. <display-name>Application Exemple</display-name> 8. Il peut y avoir plusieurs vues. Ici nous n'en avons qu'une : [/vues/exemple. La première trouvée est présentée au client.xml] dans <exemple>\WEB-INF : Si Tomcat est toujours actif. ce sera en fait l'url [/exemple/vues/exemple. Les bases du développement web MVC en Java.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.html]. <description>Application web minimale</description> 9. 7. le relancer.sun. La balise <welcome-file-list> sert à définir la liste des vues à présenter lorsqu'un client demande le contexte de l'application. </web-app> <?xml version="1. Tomcat relit le fichier [web.xml] contenu dans [<exemple>\WEB-INF] s'il existe.com/xml/ns/j2ee http://java. lignes 9-11 : la liste des fichiers d'accueil. Ce texte peut ensuite être obtenu par programmation. Si Tomcat était arrêté.xml). C'est un nom libre qui a moins de contraintes que le nom de contexte de l'application. On peut y mettre des espaces par exemple. par l'exemple 18/264 .html] qui lui sera délivrée.xsd" version="2. </welcome-file-list> 12. 3. Ainsi. <welcome-file-list> 10. <welcome-file>/vues/exemple. Ce sera le cas ici.html</welcome-file> 11.

eclipse. Le fichier zippé obtenu a le contenu suivant : Il suffit de décompresser ce contenu dans un dossier. Il existe un certain nombre de plugins pour cela. Il existe un grand nombre de plugins et c'est ce qui fait la force d'Eclipse.org/downloads/] : Nous souhaitons utiliser Eclipse pour faire du développement web en Java.org/webtools/downloads/] : Suivons le lien [1. appelé Web Tools Package (WTP).2. Ce paquetage est disponible sur le site d'Eclipse (mai 2006) à l'url [http://download. Aussi le site d'Eclipse propose-t-il un paquetage comprenant la plate-forme de développement Eclipse et le plugin WTP avec tous les autres plugins dont celui-ci a besoin. installer Eclipse installer les plugins dont on a besoin Le plugin WTP nécessite lui-même d'autres plugins ce qui fait que son installation est assez complexe.4 Installation d'Eclipse Eclipse est un environnement de développement multi-langages..eclipse. et permettent de tester une application web à l'intérieur d'Eclipse. Il est beaucoup utilisé dans le développement Java. par l'exemple 19/264 .2] ci-dessus : Nous téléchargeons le paquetage [wtp] avec le lien ci-dessus. des fichiers XML. 2. Nous utiliserons l'un de ces plugins.0.. La démarche normale d'installation d'Eclipse est normalement la suivante : 1. Ils aident à vérifier la syntaxe des pages JSP. . Son contenu est le suivant : Les bases du développement web MVC en Java. C'est un outil extensible par adjonction d'outils appelés plugins. Nous appellerons désormais ce dossier <eclipse>. Eclipse est disponible à l'url [http://www.

3. Par défaut. dans le PATH de l'OS dans le dossier <JAVA_HOME>/jre/bin où JAVA_HOME est une variable système définissant le dossier racine d'un JDK. 2. Ces arguments sont passés à la JVM qui va exécuter Eclipse. 512 Mo est préférable.[eclipse. -Xms40m : ? -Xmx256m : fixe la taille mémoire en Mo allouée à la machine virtuelle Java (JVM) qui exécute Eclipse.0_06\bin\javaw.exe -vm "C:\Program Files\Java\jre1. En effet. par l'exemple 20/264 . Eclipse est une application Java. 3. soit changer la variable JAVA_HOME. Si la machine le permet.5.ini en créant un raccourci qui lancerait Eclipse avec ces mêmes arguments.exe -vmargs -Xms40m -Xmx256m On arrive au même résultat obtenu avec le fichier . Regardons le contenu de celui-ci : 1. 2. Nous créons donc le raccourci suivant : cible Démarrer dans <eclipse>\eclipse.exe]. lançons Eclipse via ce raccourci.exe] est l'exécutable et [eclipse.exe Cette dernière solution est préférable car les deux autres sont sujettes aux aléas des installations ultérieures d'applications qui peuvent soit changer le PATH de l'OS. -vmargs -Xms40m -Xmx256m Ces arguments sont utilisés lors du lancement d'Eclipse de la façon suivante : eclipse.ini] le fichier de configuration de celui-ci.exe] ou [javaw. cette taille est de 256 Mo comme montré ici. Explicitons ceux-ci : • • • -vmargs : indique que les arguments qui suivent sont destinés à la machine virtuelle Java qui va exécuter Eclipse. La JVM est représentée par un fichier [java. à un emplacement passé en argument à Eclipse sous la forme -vm <chemin>\javaw.exe" -vmargs -Xms40m -Xmx512m dossier <eclipse> d'installation d'Eclipse Ceci fait. Comment celui-ci est-il trouvé ? En fait. On obtient une première boîte de dialogue : Les bases du développement web MVC en Java. il est cherché de différentes façons : 1.

Aussi la réponse donnée à cette boîte de dialogue n'est-elle pas importante. Un double clic dessus donne accès à ses propriétés : Les bases du développement web MVC en Java. C'est un bon moyen de savoir où on en est dans les JREs qu'on installe puis qu'on oublie de désinstaller lorsqu'on passe à une version plus récente. Il faut alors désigner la racine du JRE. Pour cela. l'environnement de développement Eclipse est affiché : Nous fermons la vue [Welcome] comme suggéré ci-dessus : Avant de créer un projet Java. Par défaut. Le bouton [Search] va lui. Le JRE qui sera utilisé dans nos exemples est celui qui a été installé au paragraphe 2. le JRE (Java Runtime Environment) qui a été utilisé pour lancer Eclipse lui-même doit être présent dans la liste des JRE. les projets Eclipse créés le seront dans le dossier <workspace> spécifié dans cette boîte de dialogue. Ci-dessus.1 et qui a également servi à lancer Eclipse. lancer une recherche de JREs sur le disque. le JRE coché est celui qui sera utilisé pour compiler et exécuter les projets Java. Il y a moyen de contourner ce comportement. nous prenons l'option [Window / Preferences / Java / Installed JREs ] : Normalement. Ce sera le seul normalement. Passée cette étape. Il est possible d'ajouter des JRE avec le bouton [Add]. nous allons configurer Eclipse pour indiquer le JDK à utiliser pour compiler les projets Java. Acceptons les valeurs par défaut proposées. C'est ce que nous ferons systématiquement. par l'exemple 21/264 .Un [workspace] est un espace de travail.

Maintenant. créons un projet Java [File / New / Project] : Choisir [Java Project]. Dans [1]. Nous avons alors un squelette de projet Java : Cliquons droit sur le projet [test1] pour créer une classe Java : Les bases du développement web MVC en Java. Cela revient à accepter les valeurs par défaut proposées par les pages suivantes de l'assistant. on utilise le bouton [Finish] pour terminer l'assistant de création. Ceci fait. nous indiquons un dossier vide dans lequel sera installé le projet Java. nous donnons un nom au projet. puis [Next] -> 1 2 Dans [2]. par l'exemple 22/264 . Il n'a pas à porter le nom de son dossier comme pourrait le laisser croire l'exemple ci-dessus.

Le projet est alors enrichi d'une classe : Eclipse a généré le squelette de la classe.1 2 3 4 dans [1]. par l'exemple 23/264 . Celui-ci est obtenu en double-cliquant sur [Test1. le nom de la classe dans [4].java] ci-dessus : Nous modifions le code ci-dessus de la façon suivante : Les bases du développement web MVC en Java. nous demandons à ce que la méthode statique [main] soit générée • • • • Nous validons l'assistant par [Finish]. le dossier où sera créée la classe. Eclipse propose par défaut le dossier du projet courant. dans [2]. le paquetage dans lequel sera placée la classe dans [3].

Nous exécutons le programme [Test1. Nous obtenons alors l'assistant suivant : Nous choisissons de créer un nouveau serveur.5 Intégration Tomcat . nous prenons l'option [File / New / Other]. Pour cela. Nous sélectionnons l'icône [Server] ci-dessus puis faisons [Next] : Les bases du développement web MVC en Java. il nous faut déclarer ce serveur dans la configuration d'Eclipse.java -> Run As -> Java Application] Le résultat de l'exécution est obtenu dans la fenêtre [Console] : 2.java] : [clic droit sur Test1. par l'exemple 24/264 .Eclipse Afin de travailler avec Tomcat tout en restant au sein d'Eclipse.

le champ [Name] est libre puis [Next] -> [Finish] -> Nous choisissons le serveur Tomcat 5.5 puis [Next] -> L'ajout du serveur se concrétise par celui d'un dossier dans l'explorateur de projets d'Eclipse : Pour gérer Tomcat depuis Eclipse. La vue [Servers] apparaît alors : Apparaîssent dans cette vue.avec le bouton [Browse]. nous faisons apparaître la vue appelée [Servers] avec l'option [Window -> Show View -> Other -> Server] : Faire [OK].avec le bouton [Installed JREs] nous désignons le JRE à utiliser . par l'exemple 25/264 . tous les serveurs déclarés. nous désignons le dossier d'installation de Tomcat 5. ici le serveur Tomcat 5.5 ..5 que nous venons d'enregistrer. Un clic droit dessus donne accès aux commandes permettant de démarrer – arrêter – relancer le serveur : Les bases du développement web MVC en Java.

apache.path: C:\Program Files\Java\jre1. 2.certaines classes à charger n'ont pas été trouvées.Ci-dessus. 11 mai 2006 15:31:16 org. ces ressources vont être des projets web. Pour le moment.. Nous ne nous apesantirons pas dessus pour le moment.C:\WINDOWS\system32.http11. Elles sont cherchées dans [WEB-INF/classes] et [WEBINF/lib].xml] est syntaxiquement incorrect. 4. 3. le serveur Tomcat / Eclipse va chercher à charger le contexte des applications qu'il gère.. INFO: Server startup in 1641 ms La compréhension de ces logs demande une certaine habitude..catalina. un certain nombre de logs sont écrits dans la vue [Console] : 1.2. Avec le serveur Tomcat intégré à Eclipse. Lors de son démarrage. nous lançons le serveur.apache.Http11BaseProtocol init INFO: Initialisation de Coyote HTTP/1. Il faut en général vérifier la présence des classes nécessaires et l'orthographe de celles déclarées dans le fichier [web..AprLifecycleListener lifecycleEvent INFO: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java. lorsqu'il est lancé.xml] et de charger une ou plusieurs classes l'initialisant. demandons l'url [http://localhost:8080] avec un navigateur : Cette réponse n'indique pas que le serveur ne fonctionne pas mais que la ressource / qui lui est demandée n'est pas disponible. 6. 11 mai 2006 15:31:16 org. Il est cependant important de vérifier qu'ils ne signalent pas d'erreurs de chargement de contextes. Pour nous en assurer. Il est conseillé d'utiliser un outil capable de vérifier la validité d'un document XML lors de sa construction. Nous le verrons ultérieurement.xml]. . Le serveur lancé à partir d'Eclipse n'a pas la même configuration que celui installé au paragraphe 2. par l'exemple 26/264 . C'est l'erreur la plus fréquente. Plusieurs types d'erreurs peuvent alors se produire : .library.coyote. Charger le contexte d'une application implique d'exploiter son fichier [web.1 sur http-8080 .. arrêtons Tomcat : Les bases du développement web MVC en Java. page 4. En effet.le fichier [web.0_06\bin.core.5... 5.

on ne dispose pas des applications définies par défaut au sein du serveur Tomcat : [admin] et [manager] qui sont deux applications utiles. Aussi. Revenons dans la vue [Servers] et double-cliquons sur le serveur Tomcat pour avoir accès à ses propriétés : 1 La case à cocher [1] est responsable du fonctionnement précédent. Ce faisant. les applications web développées sous Eclipse ne sont pas déclarées dans les fichiers de configuration du serveur Tomcat associé mais dans des fichiers de configuration séparés. demandons l'url [http://localhost:8080] avec un navigateur : Les bases du développement web MVC en Java. allons-nous décocher [1] et relancer Tomcat : Ceci fait.Le mode de fonctionnement précédent peut être changé. Lorsqu'elle est cochée. par l'exemple 27/264 .

par l'exemple 28/264 . On peut également utiliser un navigateur interne à Eclipse : Nous sélectionnons ci-dessus. Nous avons. page 14.Nous retrouvons le fonctionnement décrit au paragraphe 2. le navigateur interne.3.3. utilisé un navigateur extérieur à Eclipse. Pour le lancer à partir d'Eclipse on peut utiliser l'icône suivante : Le navigateur réellement lancé sera celui sélectionné par l'option [Window -> Web Browser]. Ici. nous obtenons le navigateur interne : 1 Lançons si besoin est Tomcat à partir d'Eclipse et demandons dans [1] l'url [http://localhost:8080] : Les bases du développement web MVC en Java. dans nos exemples précédents.

Suivons le lien [Tomcat Manager] : Le couple [login / mot de passe] nécessaire pour accéder à l'application [manager] est demandé.d. D'après la configuration de Tomcat que nous avons faite précédemment. Nous reprenons une démarche analogue à celle utilisée pour créer une application web sans Eclipse. 3 Les bases du développement web en Java Nous abordons maintenant le développement d'applications web dynamiques. 3. Le lien [Recharger] associé nous sera utile ultérieurement. par l'exemple 29/264 . c.a. nous créons un nouveau projet : que nous définissons comme un projet web dynamique : Les bases du développement web MVC en Java. on peut taper [admin / admin] ou [manager / manager]. On obtient alors la liste des applications déployées : Nous voyons l'application [personne] que nous avons créée. des applications dans lesquelles les pages HTML envoyées à l'utilisateur sont générées par des programmes. Eclipse lancé.1 Création d'un projet web sous Eclipse Nous allons développer une première application web avec Eclipse/Tomcat.

com] pour récupérer certains documents qu'il veut mettre en cache afin d'éviter des accès réseau inutiles. Eclipse se connecte au site [http://java. Une demande d'agrément de licence est alors demandée : Les bases du développement web MVC en Java.Dans la première page de l'assistant de création. nous précisons le nom du projet [1] et son emplacement [2] : 1 2 Dans la seconde page de l'assistant nous acceptons les valeurs par défaut : La dernière page de l'assistant nous demande de définir le contexte [3] de l'application : 3 Une fois l'assistant validé par [Finish]. par l'exemple 30/264 .sun.

On place dans le dossier src les fichiers "ressource" sachant qu'Eclipse les recopiera automatiquement dans build/classes. Eclipse crée le projet web.On l'accepte. Pour l'obtenir. WebContent : contiendra les ressources de l'application web qui n'ont pas à être dans le dans le Classpath de l'application. appelé perspective. différent de celui utilisé pour un projet Java classique : La perspective associée à un projet web est la perspective J2EE.java placés dans src. Pour l'afficher. il utilise un environnement. build/classes (non représenté) : contiendra les .a. l'ensemble des dossiers qui sont explorés par la JVM lorsque l'application fait référence à une classe.. Examinons le contenu du fichier [WEB-INF/web. la perspective Java est suffisante. Une application web utilise fréquemment des fichiers dits fichiers "ressource" qui doivent être dans le Classpath de l'application. WEB-INF/lib : contiendra les archives . Nous l'acceptons pour voir. Le résultat obtenu est le suivant : La perspective J2EE est en fait inutilement complexe pour les projets web simples. Eclipse fait en sorte que le dossier build/classes fasse partie du c web.d.class des classes compilées ainsi qu'une copie de tous les fichiers autres que . par l'exemple 31/264 . Dans ce cas.. c. nous utilisons l'option [Window -> Open perspective -> Java] : src : contiendra le code Java des classes de l'application ainsi que les fichiers qui doivent être dans le Classpath de l'application.xml] qui configure l'application [personne] : Les bases du développement web MVC en Java.jar dont a besoin l'application web. soit pendant la compilation soit à l'exécution.

Ce fichier est ici [http://java. <welcome-file>default.com/xml/ns/j2ee/web-app_2_4. <welcome-file>index.html</welcome-file> 6. C'est un fichier XML qu'on peut demander directement avec un navigateur.sun.xsd précisé dans l'attribut [xsi:schemaLocation] de la balise d'ouverture <web-app>. 4.htm</welcome-file> 7.jsp</welcome-file> 8.1. <display-name>personne</display-name> 4.sun. Si votre ordinateur est sur un réseau privé.sun.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun. <welcome-file>default. Le fichier [web.4" xmlns="http://java. Il va pour cela faire un accès réseau.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. page 17. Ce fichier ne fait rien d'autre que de définir une série de pages d'accueil. <welcome-file-list> 8. </web-app> Nous avons déjà rencontré de type de configuration lorsque nous avons étudié la création de pages d'accueil au paragraphe 2.sun. <welcome-file>default. il saura afficher un fichier XML : Eclipse va chercher à vérifier la validité du document XML à l'aide du fichier . 3. Cela se fait avec l'option [Window -> Preferences -> Internet] : Les bases du développement web MVC en Java.jsp</welcome-file> 11.4" xmlns="http://java. </welcome-file-list> 12.xsd"> 3.com/xml/ns/j2ee" xmlns:xsi="http://www. <welcome-file-list> 5. appelée proxy HTTP. <welcome-file>index.sun.xml] devient le suivant : 1.w3.htm</welcome-file> 10.com/xml/ns/j2ee http://java.html</welcome-file> 9. 5. Si celui-ci est suffisamment récent. <welcome-file>index.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.com/xml/ns/j2ee/webapp_2_4.com/xml/ns/j2ee" xmlns:xsi="http://www. </welcome-file-list> 10.xsd"> 6.html</welcome-file> 9. <?xml version="1.com/xml/ns/j2ee/webapp_2_4.3. il faut alors indiquer à Eclipse la machine à utiliser pour sortir du réseau privé. par l'exemple 32/264 . 2. <?xml version="1. <display-name>personne</display-name> 7.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.w3.sun. <welcome-file>index.com/xml/ns/j2ee http://java.xsd]. 2.4. </web-app> Le contenu du fichier XML ci-dessus doit obéir aux règles de syntaxe définies dans le fichier désigné par l'attribut [xsi:schemaLocation] de la balise d'ouverture <web-app>. Nous ne gardons que la première.

3. nous passons à la page suivante de l'assistant : 1 2 Les bases du développement web MVC en Java. Enfin dans (4).1 2 3 4 On coche (1) si on est sur un réseau privé. Nous allons créer maintenant le fichier [index. Dans (2). on indique les machines pour lesquelles il ne faut pas passer par le proxy. on indique le nom de la machine qui supporte le proxy HTTP et dans (3) le port d'écoute de celui-ci.2 Création d'une page d'accueil Nous cliquons droit sur le dossier [WebContent] puis prenons l'option [New -> Other] : Nous choisissons le type [HTML] et faisons [Next] -> 2 1 3 Ci-dessus. Ceci fait. nous sélectionnons le dossier parent [WebContent] en (1) ou en (2) puis nous précisons en (3) le nom du fichier à créer. par l'exemple 33/264 . des machines qui sont sur le même réseau privé que la machine avec laquelle on travaille.html] de la page d'accueil.

Le fichier [index.html] est alors créé : avec le contenu suivant : 1. nous pouvons générer un fichier HTML pré-rempli par (2).01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html.. 9. Nous gardons la coche (1) afin de bénéficier d'un squelette de code. 10. 8.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html. faisons apparaître la vue [Servers] avec l'option [Window . 4. charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> </body> </html> Nous modifions ce fichier de la façon suivante : <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. <br> <br> Vous êtes sur la page d'accueil </body> </html> 3. 3.Avec (1). 5. 7.3 Test de la page d'accueil Si elle n'est pas présente.5 : Les bases du développement web MVC en Java.> Show View -> Other -> Servers] puis cliquons droit sur le serveur Tomcat 5. Si on décoche (1). Nous terminons l'assistant par [Finish]. charset=ISO-8859-1"> <title>Application personne</title> </head> <body> Application personne active. on génère un fichier HTML vide. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.. 2. 6. par l'exemple 34/264 .

par l'exemple 35/264 .L'option [Add and Remove Objects] ci-dessus permet d'ajouter / retirer des applications web au serveur Tomcat : 2 1 3 4 Les projets web connus d'Eclipse sont présentés en (1). On peut les désenregistrer avec (3). La vue [Servers] montre que le projet [personne] a été enregistré sur Tomcat : Maintenant. lançons le serveur Tomcat : Les bases du développement web MVC en Java. Enregistrons le projet [personne] : puis terminons l'assistant d'enregistrement par [Finish]. On peut les enregistrer sur le serveur Tomcat par (2). Les applications web enregistrées auprès du serveur Tomcat apparaissent en (4).

Ici la page d'accueil existe. C'est le fichier [index. Cette url est celle de la racine de l'application web.4 Création d'un formulaire HTML Nous créons maintenant un document HTML statique [formulaire. Maintenant. Si elle n'existe pas. c'est la page d'accueil de l'application qui est affichée. par l'exemple 36/264 . Le résultat obtenu est le suivant : Il est conforme à ce qui était attendu. une erreur est signalée. 3.html] dans le dossier [personne] : Les bases du développement web MVC en Java. Aucun document n'est demandé. Dans ce cas.Lançons le navigateur web : puis demandons l'url [http://localhost:8080/personne].html] que nous avons créé précédemment. prenons un navigateur externe à Eclipse et demandons la même url : L'application web [personne] est donc connue également à l'extérieur d'Eclipse.

18. 8. par l'exemple 37/264 .01 Transitional//EN"> <html> <head> <title>Personne . 9.formulaire</title> </head> <body> <center> <h2>Personne . 25. 24. 16.2. 2. 11. 29.formulaire</h2> <hr> <form action="" method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="" type="text" size="3"></td> </tr> </table> <table> <tr> <td><input type="submit" value="Envoyer"></td> <td><input type="reset" value="Retablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table> </form> </center> </body> </html> Le code HTML ci-dessus correspond au formulaire ci-dessous : 1 2 3 n° 1 2 3 4 5 4 5 rôle type HTML <input type= "text "> <input type= "text "> <input type= "submit "> <input type= "reset "> <input type= "button "> nom txtNom txtAge code HTML ligne 14 ligne 18 ligne 23 ligne 24 ligne 25 saisie du nom saisie de l'âge envoi des valeurs saisies au serveur à l'url /personne1/main pour remettre la page dans l'état où elle a été reçue initialement par le navigateur pour effacer les contenu des champs de saisie [1] et [2] Sauvegardons le document dans le dossier <personne>/WebContent. 6. 20. 5. 28. 26. 23. 10. on suivra la procédure décrite au paragraphe 3.html : Les bases du développement web MVC en Java.Pour le créer. 4. 14. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. Lançons Tomcat si besoin est. 3. 17. 22. 30. 31. 21. 27. 12. 15. Avec un navigateur demandons l'URL http://localhost:8080/personne/formulaire. Son contenu sera le suivant : 1. 7. 19. 13. page 33.

2. la page HTML affichée par le navigateur client. Celle-ci sert de modèle. 2. Au final.html Le serveur web est entre l'utilisateur et l'application web et n'a pas été représenté ici. nous cliquons droit sur le dossier [WebContent] puis prenons l'option [New -> Other] : Les bases du développement web MVC en Java.3.jsp Une page JSP est une forme de page HTML paramétrée.1. Pour créer une page JSP. La programmation web vise à générer du contenu adapté à la requête du client. Ces valeurs sont calculées par programme. c'est toujours un document HTML que reçoit le navigateur. Ses éléments dynamiques sont remplacés par leurs valeurs effectives au moment de la génération du document HTML. par l'exemple 38/264 .L'architecture client / serveur de cette application basique est la suivante : Application web Utilisateur formulaire. Une première solution est d'utiliser une page JSP (Java Server Page) à la place du fichier HTML statique. Ce contenu est alors généré par programme. Nous appelons ici réponse. C'est ce que nous voyons maintenant.2. Certains éléments de la page ne reçoivent leur valeur qu'au moment de l'exécution. chapitre 2 : 2.2.2.2.5 Création d'une page JSP Lectures [ref1] : chapitre 1. 2.4 L'architecture client / serveur précédente est transformée comme suit : Application web Utilisateur formulaire. Ce document HTML est généré par le serveur web à partir de la page JSP. [formulaire.2. 2.html] est un document statique qui délivre à chaque requête du client le même contenu.2. 3. On a donc une page dynamique : des requêtes successives sur la page peuvent amener des réponses différentes.

on génère un fichier JSP vide. Ceci fait.Nous choisissons le type [JSP] et faisons [Next] -> 2 1 3 Ci-dessus. nous sélectionnons le dossier parent [WebContent] en (1) ou en (2) puis nous précisons en (3) le nom du fichier à créer. par l'exemple 39/264 . Nous terminons l'assistant par [Finish]. Nous gardons la coche (1) afin de bénéficier d'un squelette de code.jsp] est alors créé : Les bases du développement web MVC en Java. Si on décoche (1). nous pouvons générer un fichier JSP pré-rempli par (2). Le fichier [formulaire. nous passons à la page suivante de l'assistant : 1 2 Avec (1).

2. 13. 7. String age=request. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 11. 3. 24. 19. 8.getParameter("txtNom"). 39. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 16. 31. charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> </body> </html> La ligne 1 indique que nous avons affaire à une page JSP. 21. 23.formulaire</title> </head> <body> <center> <h2>Personne . 2. 30. 32. %> <html> <head> <meta http-equiv="Content-Type" content="text/html. 38. 12. 11. <%@ page language="java" contentType="text/html. 9. 4. 22. 41. 25.avec le contenu suivant : 1. par l'exemple 40/264 .getParameter("txtAge"). 5. Nous transformons le texte ci-dessus de la façon suivante : 1. 34. 9.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html. 15. 40. 18. 42. 27. 37.01 Transitional//EN"> <% // on récupère les paramètres String nom=request. if(nom==null) nom="inconnu". if(age==null) age="xxx". 4. 14. <%@ page language="java" contentType="text/html. 8. 6. 36. 10. 33. 10. 6. 7. 26. 17. 3.formulaire</h2> <hr> <form action="" method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> </tr> </table> <table> <tr> <td><input type="submit" value="Envoyer"></td> <td><input type="reset" value="Rétablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table> </form> </center> </body> </html> Les bases du développement web MVC en Java. 29. charset=ISO-8859-1"> <title>Personne . 5. 35. 28. 20.

Le document initialement statique est maintenant devenu dynamique par introduction de code Java. Lançons Tomcat http://localhost:8080/personne/formulaire.1. Il les a donc affichés. deux paramètres appelés [txtNom] et [txtAge] et met leurs valeurs dans les variables [nom] (ligne 6) et [age] (ligne 8). C'est le résultat de cette compilation qui au final traite la requête du client.6 Création d'une servlet Lectures [ref1] : chapitre 1. Ici. Dans les deux cas. il donne aux variables associées des valeurs par défaut. les paramètres passés se retrouvent dans l'objet prédéfini request. 2.1. On sait qu'il y a deux méthodes pour passer des paramètres à un document web : GET et POST.Servlet] : Les bases du développement web MVC en Java. La classe générée à partir de la page JSP est une servlet parce qu'elle implémente l'interface [javax. ils ont été passés par la méthode GET. crée une classe Java à partir de cette page et compile celle-ci. Il se contentera le plus souvent d'afficher des variables calculées auparavant dans le code Java au moyen de balises <%= variable %>. la requête du client était traitée par une page JSP.3.jsp : si besoin est. Faisons un premier test. On notera ici que le signe = est collé au signe %. Maintenant demandons l'URL http://localhost:8080/personne/formulaire. Pour ce type de document.1 Dans la version précédente.1. nous avons passé au document formulaire. les paramètres txtNom et txtAge qu'il attend.jsp. puis avec un navigateur. Celle-ci peut passer au travers de plusieurs servlets et pages JSP qui ont pu l'enrichir. Que fait le document dynamique précédent ?  lignes 6-9 : il récupère dans la requête.jsp?txtNom=martin&txtAge=14 : Cette fois-ci.jsp a été appelé sans passage de paramètres.  il affiche la valeur des deux variables [nom. 3. Ceux-ci seront souvent dans l'objet request. Lors du premier appel à celle-ci.  le code HTML se trouve à la suite. chapitre 2 : 2. ici Tomcat. Cet objet représente la requête du client. demandons l'URL Le document formulaire. 2. par l'exemple 41/264 . Les valeurs par défaut ont donc été affichées. S'il ne trouve pas les paramètres.1.2. C'est une cause fréquente d'erreurs. elle nous arrivera directement du navigateur. 2. age] dans le code HTML qui suit (lignes 25 et 29). le serveur web. nous procèderons toujours comme suit :  nous mettons du code Java dès le début du document pour récupérer les paramètres nécessaires au document pour s'afficher. Ici.

cliquons droit sur le dossier [src] et prenons l'option de création d'une classe : puis définissons les caractéristiques de la classe à créer : 1 2 4 3 Dans (1). Il est inutile de taper soi-même le nom complet de celle-ci. L'architecture client / serveur précédente est transformée comme suit : Application web Utilisateur ServletFormulaire.La requête du client peut être traitée par toute classe implémentant cette interface.java Avec l'architecture à base de page JSP.6. 3. le document HTML envoyé au client était généré par le serveur web à partir de la page JSP qui servait de modèle. dans (2) le nom de la classe à créer. on met un nom de paquetage. Le bouton (4) permet l'accès aux classes actuellement dans le Classpath de l'application web : Les bases du développement web MVC en Java. Nous construisons maintenant une telle classe : ServletFormulaire. Ici le document HTML envoyé au client sera entièrement généré par la servlet. Celle-ci doit dériver de la classe indiquée en (3).1 Création de la servlet Sous Eclipse. par l'exemple 42/264 .

Cliquons sur l'icône (lampe) signalant ce [warning] : 1 2 3 Après avoir cliqué sur (1).class en mémoire doit être transformé en une suite de bits envoyés séquentiellement dans un flux d'écriture ou l'inverse lorsqu'un objet Java . Ceux-ci ne signalent pas des erreurs qui pourraient empêcher la compilation de la classe. le projet web [personne] est modifié de la façon suivante : La classe [ServletFormulaire] a été créée avec un squelette de code : La copie d'écran ci-dessus montre qu'Eclipse signale un [warning] sur la ligne déclarant la classe. Celui-ci est utilisé pour la sérialisation / désérialisation des objets. Tout cela est fort éloigné de nos préoccupations actuelles.. La sélection de l'une d'elles provoque l'apparition dans (3) de la modification de code que son choix va amener. des solutions pour supprimer le [warning] nous sont proposées dans (2). c. Après validation de l'assistant de création. Java 1. lorsqu'un objet Java . Ils sont là pour attirer l'attention du développeur sur des points de code qui pourraient être améliorés. On obtient en (2) les classes du Classpath dont le nom contient la chaîne tapée en (1).a.d.5 a amené des modifications au langage Java et ce qui était correct dans une version antérieure peut désormais faire l'objet de [warnings].1 2 En (1) on tape le nom de la classe recherchée.. Le code devient alors le suivant : Les bases du développement web MVC en Java.]. par l'exemple 43/264 . Aussi allons-nous demander au compilateur d'ignorer ce warning en choisissant la solution [Add @SuppressWarnings . Le [warning] présent indique qu'une classe devrait avoir un numéro de version.class en mémoire doit être créé à partir d'une suite de bits lus séquentiellement dans un flux de lecture.

6.jar qui font partie du Classpath de l'application.5.jar explorées lorsque le compilateur la compile ou lorsque la JVM l'exécute. Ces deux Classpath ne sont pas forcément identiques.2 Classpath d'un projet Eclipse Le Classpath d'une application Java est l'ensemble des dossiers et archives. par l'exemple 44/264 . Le bouton [2] permet de désigner des archives présentes dans les dossiers des projets gérés par Eclipse alors que le bouton [3] permet de désigner toute archive du système de fichiers de l'ordinateur. 3. Le compilateur Java aussi bien que la JVM ont un argument qui permet de préciser le Classpath de l'application à compiler ou exécuter. Comment peut-on connaître les éléments du Classpath d'un projet Eclipse ? Avec l'option [<projet> / Build Path / Configure Build Path] : Nous obtenons alors l'assistant de configuration suivant : 1 2 3 L'onglet (1) [Libraries] permet de définir la liste des archives . Elles sont donc explorées par la JVM lorsque l'application demande une classe. Ci-dessus on voit apparaître trois bibliothèques (Libraries) : • [JRE System Library] : librairie de base pour les projets Java d'Eclipse : Les bases du développement web MVC en Java. certaines classes n'étant utiles qu'à l'exécution et pas à la compilation. La ligne ajoutée s'appelle une " annotation ". Eclipse assure la construction et le passage de cet argument à la JVM. Les boutons [2] et [3] permettent d'ajouter des archives au Classpath.Il n'y a plus de [warning]. une notion apparue avec Java 1. De façon plus ou moins transparente pour l'utilisateur. Nous complèterons ce code ultérieurement.

par l'exemple 45/264 . on veut référencer une classe parent et que celle-ci n'est pas proposée. Elle comporte les classes nécessaires au développement web.jar] qui contient la classe [javax.http. Par exemple. pour le projet web [personne] : L'explorateur de projets nous donne accès au contenu de ces archives : Les bases du développement web MVC en Java.servlet. soit on se trompe dans le nom de cette classe. c'est que. C'est parce que cette archive est dans le Classpath de l'application qu'elle a pu être proposée comme classe parent dans l'assistant rappelé ci-dessous.5 runtime] : librairie apportée par le serveur Tomcat. soit l'archive qui la contient n'est pas dans le Classpath de l'application. elle ne serait pas apparue dans les propositions de [2].• [Tomcat v5. • [Web App Libraries] rassemble les archives présentes dans le dossier [WEB-INF/lib] du projet.HttpServlet]. classe parent de la classe [ServletFormulaire] que nous sommes en train de créer. Si donc dans cet assistant. 1 2 Si ce n'avait pas été le cas. Cette librairie est incluse dans tout projet web d'Eclipse ayant été associé au serveur Tomcat. Ici il est vide : Les archives du Classpath du projet Eclipse sont présentes dans l'explorateur de projets. C'est l'archive [servlet-api.

3.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. <welcome-file>index. 5. pour le projet [personne] est actuellement le suivant (cf page 32) : 1.3 Configuration de la servlet Lectures [ref1] : chapitre 2 : 2.http. 2.3. 4.6.4" xmlns="http://java.3.HttpServlet]. <?xml version="1.2.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.3.3.jar] qui contient la classe [javax.xml] sert à configurer l'application web : Ce fichier.sun.sun. par l'exemple 46/264 . 2. </welcome-file-list> 10. 3.com/xml/ns/j2ee" xmlns:xsi="http://www. </web-app> Il indique seulement l'existence d'un fichier d'accueil (ligne 8). <welcome-file-list> 8.com/xml/ns/j2ee http://java.com/xml/ns/j2ee/webapp_2_4. <display-name>personne</display-name> 7.html</welcome-file> 9.sun. 2.servlet. Nous le faisons évoluer pour déclarer : Les bases du développement web MVC en Java.1.3. 2. 2.w3.4 Le fichier [WEB-INF/web.xsd"> 6.Ainsi ci-dessus. on peut voir que c'est l'archive [servlet-api.3.

27. 18. javax.</servlet>.xsd"> <display-name>personne</display-name> <servlet> <servlet-name>formulairepersonne</servlet-name> <servlet-class> istia. 25.servlets. 3.html</welcome-file> </welcome-file-list> </web-app> Les points principaux de ce fichier de configuration sont les suivants :     les lignes 7-24 sont liées à la présence de la servlet [ServletFormulaire] lignes 7-20 : la configuration d'une servlet se fait entre les balises <servlet> et </servlet>."XXX"] lignes 21-24 : la balise <servlet-mapping> sert à associer une servlet (servlet-name) à un modèle d'URL (urlpattern).io. 24. ligne 8 : la balise <servlet-name> fixe un nom à la servlet . 9. Ici le modèle est simple. 4. 11. 10. 10.peut être quelconque lignes 9-11 : la balise <servlet-class> donne le nom complet de la classe correspondant à la servlet. 3. Il la trouvera dans [build/classes] :     lignes 12-15 : la balise <init-param> sert à passer des paramètres de configuration à la servlet. javax. <?xml version="1.servlets.st.ServletConfig.xml de notre application personne sera le suivant : 1. 12.http.personne. 23.w3. 14. 26.4" xmlns="http://java.com/xml/ns/j2ee/webapp_2_4.ServletFormulaire] (lignes 8-11). 2.http. 11. les lignes 12-15 définissent un paramètre [defaultNom. 17. javax. import import import import import javax. 15.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. 6."inconnu"] et les lignes 16-19 un paramètre [defaultAge.4 1. c. 19. 8.sun.servlet.servlet. 4.PrintWriter. 9.personne. javax. 2.ServletException. 7.• • • l'existence de la servlet [ServletFormulaire] les URL traitées par cette servlet des paramètres d'initialisation de la servlet Le fichier web.servlets. 6.com/xml/ns/j2ee" xmlns:xsi="http://www.servlet.sun. lignes 13-14 : la balise <param-name> fixe le nom du paramètre et <param-value> sa valeur. 5. import java. la classe [istia. il faudra utiliser la servlet formulairepersonne.st. par l'exemple 47/264 .sun.com/xml/ns/j2ee http://java. 5... 3. 22.6. Il dit qu'à chaque fois qu'une URL aura la forme /formulaire. 8. Une application peut comporter plusieurs servlets et donc autant de sections de configuration <servlet>. La servlet [ServletFormulaire] aura le code suivant : Les bases du développement web MVC en Java. 16. 20.a.IOException. 13.d.io.st.servlet. import java.http. 7. Le code de la servlet [ServletFormulaire] package istia. Il n'y a donc qu'une url acceptée par la servlet [formulairepersonne]. Ceux-ci sont généralement lus dans la méthode init de la servlet car les paramètres de configuration de celle-ci doivent être connus dès son premier chargement. 28.ServletFormulaire </servlet-class> <init-param> <param-name>defaultNom</param-name> <param-value>inconnu</param-value> </init-param> <init-param> <param-name>defaultAge</param-name> <param-value>XXX</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>formulairepersonne</servlet-name> <url-pattern>/formulaire</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.HttpServletResponse.HttpServlet. Tomcat ira chercher cette classe dans le Classpath du projet web [personne]. 21.servlet.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.HttpServletRequest.

response. 24. if (nom == null) { 38. "</tr>"+ 66. "<table>"+ 58. private String defaultAge = null. 31. 47. "</tr>"+ 62. } 30. // on affiche le formulaire 45. 23.println( 48. age = defaultAge. C'est le seul cas où elle est appelée. doGet(request. "<body>"+ 53. // on passe la main au GET 85.getInitParameter("defaultAge"). 46.formulaire</title>"+ 51. // paramètres d'instance 16. "</table>"+ 67. 39. "<td>Age</td>"+ 64. sa méthode init (ligne 20) est appelée. 17. 41. ). "</head>"+ 52. "</table>"+ 74. "<td><input name='txtAge' value='"+ age +"' type='text' size='3'></td>"+ 65. throws IOException. defaultNom = config.getParameter("txtAge"). ServletException { 84. } 44. public void init() { 21. "<td><input type='submit' value='Envoyer'></td>"+ 70. 27. on peut constater qu'elle est beaucoup plus complexe que la page JSP correspondante. // on récupère les paramètres du formulaire 36. response). Nous aurons l'occasion d'y revenir. 35. "<td>Nom</td>"+ 60. "<tr>"+ 69. "<h2>Personne . "<tr>"+ 59. "<td><input type='button' value='Effacer'></td>"+ 72. "<title>Personne . } 80. PrintWriter out = response.formulaire</h2>"+ 55. C'est une généralité : une servlet n'est pas adaptée pour générer du code HTML. nom = defaultNom. par l'exemple 48/264 . if (defaultNom == null) 25. 37. String nom = request. "<hr>"+ 56. // GET 32.getInitParameter("defaultNom"). HttpServletResponse response) 33. // POST 82. if (defaultAge == null) 28.getWriter(). 19.setContentType("text/html"). 26. // init 20. HttpServletResponse response) 83. // on récupère les paramètres d'initialisation de la servlet 22. Ce sont les pages JSP qui sont faites pour cela. out. "<form action='' method='post'>"+ 57. "<center>"+ 54. if (age == null) { 42. 86. "<td><input type='reset' value='Rétablir'></td>"+ 71. @SuppressWarnings("serial") 13. } 87. la méthode doPost (ligne 82) est appelée pour traiter la requête du client. "<head>"+ 50. public void doGet(HttpServletRequest request. la méthode doGet (ligne 32) est appelée pour traiter la requête du client. "</tr>"+ 73. 29. private String defaultNom = null. defaultNom = "NNNNNNNNNNNNNNN". 15. 79. si la servlet a été appelée par la méthode HTTP POST. "</body>"+ 77. } A la simple lecture de la servlet. "</form>"+ 75. 81. "<table>"+ 68. public class ServletFormulaire extends HttpServlet { 14. defaultAge = config. throws IOException. 43. defaultAge = "AAA". "</center>"+ 76. "<td><input name='txtNom' value='"+nom+"' type='text' size='20'></td>"+ 61. "<html>"+ 49.12. String age = request. "</html>" 78. ServletConfig config = getServletConfig(). si la servlet a été appelée par la méthode HTTP GET. 18. } 40. public void doPost(HttpServletRequest request. Les bases du développement web MVC en Java. ServletException { 34. Explicitons quelques points importants de la servlet ci-dessus : lorsqu'une servlet est appelée pour la 1ère fois.getParameter("txtNom"). "<tr>"+ 63.

getWriter() sert à obtenir un flux d'écriture vers le client  lignes 47-78 : on écrit le document HTML à envoyer au client dans le flux d'écriture obtenu ligne 46. Elle est de type HttpServletRequest qui est une interface.6. on donne une valeur par défaut à la variable [defaultNom]. On pourra pour cela s'aider de Tomcat. Ce paramètre aura pour valeur le nom d'une personne. La méthode doGet :  ligne 32 : la méthode reçoit deux paramètres request et response.xml] de l'application. • lignes 26-29 : on fait de même pour le paramètre appelé " defaultAge ". Cet entête indique au client la nature du document qu'il va recevoir. on affecte à la variable " nom " le nom par défaut " defaultNom " initialisé dans la méthode init. Cela signifie que le client pourra envoyer indifféremment ses paramètres par un POST ou un GET. La compilation de cette servlet va produire un fichier .xml]. par l'exemple 49/264 . La méthode init exécutée au chargement initial de la servlet est le bon endroit pour récupérer le contenu du fichier [web.setContentType(String) sert à fixer la valeur de l'entête HTTP Content-type. Les bases du développement web MVC en Java. Sur la page d'entrée de Tomcat 5. • ligne 22 : la configuration [config] du projet web est récupérée. Ligne 36.  lignes 37-39 : si le paramètre " txtNom " n'est pas dans la requête.xml] les valeurs des paramètres d'initialisation appelés " defaultNom " et " defaultAge ". L'objet response sert à envoyer une réponse au client. on récupère la valeur du paramètre " txtNom ". request est un objet représentant la totalité de la requête du client. response est de type HttpServletResponse qui est également une interface.class dans le dossier [build/classes] du projet [personne] : Le lecteur est invité à consulter l'aide Java sur les servlets. • lignes 24-25 : si le paramètre appelé " defaultNom " n'existe pas. on a un lien [Documentation] : Ce lien mène à une page que le lecteur est invité à explorer. S'il n'existe pas. Il est fait de même lignes 41-43 pour l'âge.  request. on obtient la valeur null comme valeur du paramètre. on obtiendra la valeur null. • ligne 23 : on récupère dans cette configuration la valeur de type String du paramètre appelé " defaultNom ".La méthode init sert ici à récupérer dans [web. La méthode doPost renvoie à la méthode doGet. Lançons le serveur Tomcat si besoin est.getParameter("param") sert à récupérer dans la requête du client la valeur du paramètre de nom param.5 Test de la servlet Nous sommes prêts pour faire un test. Cet objet reflète le contenu du fichier [WEBINF/web.  ligne 46 : response.  ligne 45 : response. Le type text/html indique un document HTML. Le lien sur la documentation des servlets est le suivant : 3. Si ces paramètres ne sont pas présents dans la requête. ligne 40 celle du paramètre " txtAge ".

il est indiqué que cette servlet est la classe [istia. String nom = request. 3. Le fichier [web. On obtient le résultat suivant avec le navigateur interne à Eclipse : Nous obtenons les valeurs par défaut du nom et de l'âge. 13. age = defaultAge. // GET 2. elle le sera. throws IOException.xml].getParameter("txtNom"). celles inscrites dans le fichier [web.6. Si la classe n'était pas déjà chargée. Elle restera alors en mémoire pour les futures requêtes. 9. } 10. Demandons maintenant l'URL [http://localhost:8080/personne/formulaire?txtNom=tintin&txtAge=30] : Cette fois. HttpServletResponse response) 3.6 Rechargement automatique du contexte de l'application web Lançons Tomcat : puis modifions le code de la servlet de la façon suivante : 1. ServletException { 4.servlets. } 14. nous obtenons les paramètres passés dans la requête. Dans le même fichier. 5.. Nous demandons ici l'url [/formulaire] du contexte [/personne]. Le lecteur est invité à relire le code de la servlet [ServletFormulaire] s'il ne comprend pas ces deux résultats.ServletFormulaire]. nom = "--"+defaultNom+"--". C'est donc à cette classe que Tomcat va confier le traitement de la requête client.xml] de ce contexte indique que l'url [/formulaire] est traitée par la servlet de nom [formulairepersonne]. public void doGet(HttpServletRequest request.st.. if (age == null) { 12. // on récupère les paramètres du formulaire 6. String age = request. if (nom == null) { 8.getParameter("txtAge").Puis demandons avec un navigateur l'URL [http://localhost:8080/personne/formulaire]. par l'exemple .. • la ligne 8 a été modifiée 50/264 Les bases du développement web MVC en Java. 11. 7.

sauvegardons le nouveau fichier [web. 16. 4. 6. 10.com/xml/ns/j2ee/webapp_2_4. aucun log ne signale le rechargement du contexte de l'application.sun. Ceci apparaît dans les logs de la vue [console] : Demandons l'url [http://localhost:8080/personne/formulaire] sans relancer Tomcat : La modification faite a bien été prise en compte. <?xml version="1.. de la part d'Eclipse. </web-app> • la ligne 12 a été modifiée Ceci fait. 12. 8. </servlet> .4" xmlns="http://java.Sauvegardons la nouvelle classe. Demandons l'url [http://localhost:8080/personne/formulaire] sans relancer Tomcat : La modification faite n'a pas été prise en compte.xml] de la façon suivante : 1. <init-param> <param-name>defaultNom</param-name> <param-value>INCONNU</param-value> </init-param> .com/xml/ns/j2ee" xmlns:xsi="http://www. 9. 11.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.w3.com/xml/ns/j2ee http://java.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. 7. par l'exemple 51/264 . Relançons Tomcat [clic droit sur serveur -> Restart -> Start] : Les bases du développement web MVC en Java. 15. 17. 5. 14. modifions le fichier [web. 13. 2. 3. Maintenant.xsd"> <display-name>personne</display-name> <servlet> <servlet-name>formulairepersonne</servlet-name> ...sun.sun. une recompilation automatique de la classe [ServletFormulaire] que Tomcat va détecter.xml]. Cette sauvegarde va entraîner. Dans la vue [console].... Il va alors recharger le contexte de l'application web [personne] pour prendre en compte les modifications.

5.xml] ne provoque donc pas un rechargement automatique de l'application qui prendrait en compte le nouveau fichier de configuration. Pour que cela soit possible. Pour forcer le rechargement de l'application web. page 27. par l'exemple 52/264 . demandons l'url [http://localhost:8080/formulaire] : Les bases du développement web MVC en Java.5 : Ouvrons un second navigateur [clic droit sur le navigateur -> New Editor] : Dans ce second navigateur. Une modification de [web. Il est préférable de passer par l'outil [manager] d'administration des applications déployées dans Tomcat.xml] est visible. on peut alors relancer Tomcat comme nous l'avons fait mais c'est une opération assez lente. il faut qu'au sein d'Eclipse. Tomcat ait été configuré comme il a été montré au paragraphe 2. avec le navigateur interne d'Eclipse.puis redemandons l'url [http://localhost:8080/personne/formulaire] : Cette fois la modification faite dans [web. comme expliqué à la fin du paragraphe 2. demandons l'url [http://localhost:8080] puis suivons le lien [Tomcat Manager]. Tout d'abord.

<servlet> 3. par l'exemple 53/264 .Modifions le fichier [web. <param-name>defaultNom</param-name> 9.st.<!-. 3. Dans la pratique. </servlet-class> 7.xml] a été prise en compte.7 Revenons aux deux architectures étudiées : Les bases du développement web MVC en Java. </servlet> Puis redemandons l'url [http://localhost:8080/formulaire]. Maintenant.xml] de la façon suivante puis sauvegardons-le : 1. <servlet-name>formulairepersonne</servlet-name> 4. il est utile d'avoir un navigateur ouvert sur l'application [manager] de Tomcat pour gérer ce genre de cas. <servlet-class> 5. </init-param> 11. <init-param> 12. Nous pouvons constater que la modification n'a pas été prise en compte. <init-param> 8. <param-value>YYY</param-value> 10.ServletFormulaire 6.3. istia. <param-name>defaultAge</param-name> 13.7 Coopération servlet et pages JSP Lectures [ref1] : chapitre 2 : 2.personne.ServletFormulaire --> 2.servlets. allons dans le premier navigateur et rechargeons l'application [personne] : Puis redemandons l'url [http://localhost:8080/formulaire] avec le second navigateur : La modification de [web. </init-param> 15. <param-value>XXX</param-value> 14.

Cela devient très vite ingérable. la servlet s'appellera [ServletFormulaire2]. à moins que le document HTML soit basique. Nous allons éviter le mélange des technologies Java et HTML en adoptant l'architecture suivante : Application web ServletFormulaire.jsp 1 Aucune de ces deux architectures n'est satisfaisante.jsp] qui va servir à générer la réponse HTML au client. Bien qu'il n'y ait que du code Java dans la classe. sa génération devient compliquée et quasi impossible à maintenir. Nous explorons maintenant cette nouvelle architecture. ces éléments qui forment le modèle de la page. txtAge] de la requête du client. une fois terminé son travail.java Utilisateur formulaire.jsp Modèle • • l'utilisateur envoie sa requête à la servlet. • • la solution [1] à base de page JSP a l'inconvénient de mélanger code HTML et code Java au sein d'une même page. Ces valeurs forment ce qu'on appelle le modèle de la page JSP. Celle-ci la traite et construit les valeurs des paramètres dynamiques de la page JSP [formulaire. par l'exemple 54/264 .jsp] de générer la réponse HTML au client.1 La servlet [ServletFormulaire2] Dans l'architecture ci-dessus. 3. Là encore. Elles présentent toutes les deux l'inconvénient de mélanger deux technologies : celles de la programmation Java qui s'occupe de la logique de l'application web et celle du codage HTML qui s'occupe de la présentation d'informations dans un navigateur.java formulaire.7. Elle va lui fournir en même temps les éléments dont la page JSP a besoin pour générer cette réponse. Elle sera construite dans le même projet [personne] que précédemment. nous aurions été obligés de mettre du code Java dans la page. Mais si [formulaire. celle-ci doit générer un document HTML.Application web Utilisateur 2 Utilisateur ServletFormulaire. la servlet va demander à la page JSP [formulaire. la solution [2] à base de servlet présente le même problème.jsp] avait du vérifier la validité des paramètres [txtNom. Nous ne l'avons pas vu sur l'exemple traité qui était basique. ainsi que toutes les servlets à venir : Les bases du développement web MVC en Java.

20.servlet. 44. if (defaultAge == null) defaultAge = "AAA". 22. javax. nous en modifions le code de [ServletFormulaire2] de la façon suivante : 1. 54. 52. javax. 10.servlets.java] Puis ensuite.setAttribute("nom". 12. 55. 49. 31. import import import import import import java.http.getParameter("txtNom"). 35. 3. 39. defaultNom = config.servlets. 42.io. defaultAge = config. 18.forward(request. 43. } } Les bases du développement web MVC en Java.personne] -> clic droit -> Paste -> changer le nom en [ServletFormulaire2. 40. 33. 25.getInitParameter("defaultAge"). 26. } // GET public void doGet(HttpServletRequest request. 53. if (age == null) { age = defaultAge. 47. if (nom == null) { nom = defaultNom.personne.servlet. response). request.HttpServletResponse. 30. } // on affiche le formulaire request.HttpServlet. 11. // init public void init() { // on récupère les paramètres d'initialisation de la servlet ServletConfig config = getServletConfig(). 36. 23. javax. javax. par l'exemple 55/264 . ServletException { // on récupère les paramètres du formulaire String nom = request. 13. getServletContext().HttpServletRequest.ServletException. 46. @SuppressWarnings("serial") public class ServletFormulaire2 extends HttpServlet { // paramètres d'instance private String defaultNom = null.jsp"). 27.http. 6. package istia. nom). 2. 24.java] -> clic droit -> Copy • sélection [istia. 41. HttpServletResponse response) throws IOException. age).st. if (defaultNom == null) defaultNom = "NNNNNNNNNNNNNNN". 17. 7. private String defaultAge = null. 28. 4.ServletConfig. 15.st. } String age = request.[ServletFormulaire2] est tout d'abord obtenu par un copier / coller de [ServletFormulaire] au sein d'Eclipse : • sélection [ServletFormulaire. 5. javax. 16.servlet. 34.setAttribute("age".getParameter("txtAge"). 51. 32. 8. response). 50. 45. 48. 9. 37.servlet.servlet.IOException.getRequestDispatcher("/formulaire2. 29. 38. 21. 19.http. 14.getInitParameter("defaultNom"). ServletException { // on passe la main au GET doGet(request. } // POST public void doPost(HttpServletRequest request. HttpServletResponse response) throws IOException.

Seule la partie génération de la réponse HTTP a changé (lignes 44-46) : • • • • • ligne 46 : la génération de la réponse est confiée à la page JSP formulaire2. 18. 34.formulaire2</title> </head> <body> <center> <h2>Personne . 23.formulaire2</h2> <hr> <form action="" method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> </tr> </table> <table> <tr> <td><input type="submit" value="Envoyer"></td> <td><input type="reset" value="Rétablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table> Les bases du développement web MVC en Java. 30. 27.jsp] puis transformée de la façon suivante : 1. 24. On passe en paramètre à celle-ci : • la requête [request] du client. par l'exemple 56/264 .getAttribute("age"). 12. 35.jsp]. ces deux valeurs sont placées dans les attributs de la requête [request].getAttribute("nom").jsp] La page JSP formulaire2. String age=(String)request. 11. son code compilé apparaît dans [build/classes] : 3. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 13. 5. 10. 3. ligne 44 : le nom est mis dans la requête associé à la clé "nom" ligne 45 : l'âge est mis dans la requête associé à la clé "age" ligne 46 : demande l'affichage de la page JSP [formulaire2.7. 26.01 Transitional//EN"> <% // on récupère les valeurs nécessaire à l'affichage String nom=(String)request. 28. 25. Celle-ci pas encore étudiée.2 La page JSP [formulaire2. 8. charset=ISO-8859-1"> <title>Personne . Les attributs d'une requête sont gérés comme un dictionnaire. ce qui va permettre à la page JSP d'avoir accès aux attributs de celle-ci qui viennent d'être initialisés par la servlet • la réponse [response] qui va permettre à la page JSP de générer la réponse HTTP au client Une fois la classe [ServletFormulaire2] écrite. 15.jsp. 29. 9.jsp est obtenue par copier / coller de la page [formulaire. 31. sera chargée d'afficher les paramètres récupérés dans la requête du client. %> <html> <head> <meta http-equiv="Content-Type" content="text/html. 21. un nom (lignes 35-38) et un âge (lignes 39-42). associées à des clés. 7. 4. 2. 6. 32. 33. 16. 22. 14. 17. 36. 20. <%@ page language="java" contentType="text/html. 19.

6.ServletFormulaire 2--> <servlet> <servlet-name>formulairepersonne2</servlet-name> <servlet-class> istia. 14.personne.3 Configuration de l'application Le fichier de configuration [web. 4. 19. 24.ServletFormulaire </servlet-class> <init-param> <param-name>defaultNom</param-name> <param-value>inconnu</param-value> </init-param> <init-param> <param-name>defaultAge</param-name> <param-value>XXXX</param-value> </init-param> </servlet> <!-. 18. 30. </body> 40.xsd"> <display-name>personne</display-name> <!-. 27.Mapping ServletFormulaire 2--> <servlet-mapping> <servlet-name>formulairepersonne2</servlet-name> <url-pattern>/formulaire2</url-pattern> </servlet-mapping> <!-.ServletFormulaire2 </servlet-class> <init-param> <param-name>defaultNom</param-name> <param-value>inconnu</param-value> </init-param> <init-param> <param-name>defaultAge</param-name> <param-value>XXX</param-value> </init-param> </servlet> <!-. 16. 2. Nous demandons l'URL http://localhost:8080/personne/formulaire2?txtNom=milou&txtAge=10 : Les bases du développement web MVC en Java. 25. 21. 44.sun. 51. 33. 39.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.w3. 5. 35.sun. 13. 9.personne. 22. 34. 43.37. 20. 36. </center> 39. 38. </html> Seules les lignes 4-8 ont changé vis à vis de [formulaire. 41.html</welcome-file> </welcome-file-list> </web-app> Nous avons conservé l'existant et ajouté :  lignes 22-36 : une section <servlet> pour définir la nouvelle servlet ServletFormulaire2  lignes 42-46 : une section <servlet-mapping> pour lui associer l'URL /formulaire2 Lancez ou relancez le serveur Tomcat si besoin est. attribut créé par la servlet [ServletFormulaire2].servlets. 47. </form> 38. 10. 26.4" xmlns="http://java. 28. 46.jsp] : • • ligne 6 : récupère la valeur de l'attribut nommé "nom" dans la requête [request]. 48. 37. 32. 7. 3.ServletFormulaire --> <servlet> <servlet-name>formulairepersonne</servlet-name> <servlet-class> istia.sun.st.st. ligne 7 : fait de même pour l'attribut "age" 3. 40.Mapping ServletFormulaire --> <servlet-mapping> <servlet-name>formulairepersonne</servlet-name> <url-pattern>/formulaire</url-pattern> </servlet-mapping> <!-. 50. 12. 8.com/xml/ns/j2ee http://java. 23.com/xml/ns/j2ee" xmlns:xsi="http://www.servlets. 49. 45. 15. 42. 11. par l'exemple 57/264 .7. <?xml version="1. 29.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.xml] est modifié de la façon suivante : 1.com/xml/ns/j2ee/webapp_2_4. 17.fichiers d'accueil --> <welcome-file-list> <welcome-file>index. 31.

la couche [metier] implémente les algorithmes " métier " de l'application.. elle les demande à la couche [dao] • chaque couche interrogée rend sa réponse à la couche de gauche jusqu'à la réponse finale à l'utilisateur. une interface de client riche. Elle doit ainsi pouvoir être testée en-dehors de l'interface web et notamment avec une interface console. Cette couche est indépendante de toute forme d'interface avec l'utilisateur.Nous obtenons le même résultat que précédemment mais la structure de notre application est désormais plus claire : une servlet qui contient de la logique applicative et délègue à une page JSP l'envoi de la réponse au client. du réseau. la couche [métier] a besoin des données. C'est généralement la couche la plus stable de l'architecture. Ainsi elle doit être utilisable aussi bien avec une interface console. . par l'exemple 58/264 . Mais cela peut être aussi des données qui proviennent de capteurs. La communication va de la gauche vers la droite : • l'utilisateur fait une demande à la couche [interface utilisateur] • cette demande est mise en forme par la couche [interface utilisateur] et transmise à la couche [métier] • si pour traiter cette demande. Elle ne change pas si on change l'interface utilisateur ou la façon d'accéder aux données nécessaires au fonctionnement de l'application. une interface web. Les couches [métier] et [dao] sont normalement utilisées via des interfaces Java. le plus souvent des données persistantes au sein d'un SGBD. Il en est de même entre les couches [interface utilisateur] et [métier]. 4 Développement MVC (Modèle – Vue – Contrôleur) Une application web a souvent une architecture 3tier : utilisateur Couche interface utilisateur [ui] Couche métier [metier] Couche d'accès aux données [dao] Données • • • la couche [dao] s'occupe de l'accès aux données. Nous procèderons désormais toujours de cette façon.. Ainsi la couche [métier] ne connaît de la couche [dao] que son ou ses interfaces et ne connaît pas les classes les implémentant. la couche [interface utilisateur] qui est l'interface (graphique souvent) qui permet à l'utilisateur de piloter l'application et d'en recevoir des informations. L'architecture MVC (Modèle – Vue – Contrôleur) prend place dans la couche [interface utilisateur] lorsque celle-ci est une interface web : Couche Interface Utilisateur [ui] utilisateur 1 Contrôleur 4 3 2 Modèle 5 Couche métier [metier] Couche d'accès aux données [dao] Données Vue 6 Les bases du développement web MVC en Java. C'est ce qui assure l'indépendance des couches entre-elles : changer l'implémentation de la couche [dao] n'a aucune incidence sur la couche [métier] tant qu'on ne touche pas à la définition de l'interface de la couche [dao].

une instruction du genre [getServletContext(). l'objet qui génère cette réponse. Une fois la demande du client traitée.. • lui fournir les données dont il a besoin pour générer cette réponse. la méthode [doAction] décide d'une page JSP à afficher. La méthode [doAction] va fournir ces éléments. Une page JSP a accès à ces trois contextes. Un exemple classique est : • une page d'erreurs si la demande n'a pu être traitée correctement • une page de confirmation sinon 3. Ce choix dépend en général du résultat de l'exécution de l'action demandée par l'utilisateur. La forme exacte de celle-ci dépend du générateur de vue. C'est la vue V du modèle MVC. le contrôleur choisit la réponse (= vue) à envoyer au client. Si c'est la réponse qui devait s'appeler vue. la page JSP a des éléments dynamiques qui doivent être fournis par la servlet. C'est le C de MVC. toutes les requêtes du client contiennent un attribut action. Pour ce faire. 4. la méthode [doAction] fait afficher la vue en transmettant le flux d'exécution à la page JSP choisie. selon le résultat de l'exécution. le contrôleur C traite cette demande. 2. par exemple (http://. le M de MVC.setAttribute(" clé ". En effet. L'étape 3 consiste donc en le choix d'une vue V et en la construction du modèle M nécessaire à celle-ci. elle utilise la couche [métier]. PDF. La méthodologie de développement web MVC ne nécessite pas nécessairement d'outils externes. nous appelerons vue. response)]. celle-ci peut appeler diverses réponses.. Ce modèle est placé le plus souvent dans le contexte de la requête (request. Il s'agit le plus souvent de faire exécuter une méthode particulière de la vue V chargée de générer la réponse au client. . Pour cela. celle-ci contient le plus souvent des informations calculées par le contrôleur. Dans ce document. 5. on pourrait appeler générateur de vue. C'est la porte d'entrée de l'application.getRequestDispatcher(" pageJSP "). On peut ainsi développer une application web Java avec une architecture MVC avec un simple JDK et les bibliothèques de base du développement web. C'est le modèle de la vue. Choisir la réponse à envoyer au client nécessite plusieurs étapes : • choisir l'objet qui va générer la réponse. la réponse est envoyée au client. "valeur ").jsp Modèle Cette architecture correspond à l'architecture ntier suivante : Les bases du développement web MVC en Java. Revenons à l'architecture de l'application web précédente : Application web ServletFormulaire. la méthode [doAction] exécute l'action demandée par l'utilisateur. C'est le C de MVC. le V de MVC. 6.. voire moins fréquemment. par l'exemple 59/264 . il peut avoir besoin de l'aide de la couche métier. selon la valeur de l'attribut action. Ces informations forment ce qu'on appelle le modèle M de la vue. si besoin est.forward(request.. Ce peut être un flux HTML. le générateur de vue V utilise le modèle M préparé par le contrôleur C pour initialiser les parties dynamiques de la réponse qu'il doit envoyer au client. aussi bien l'objet qui génère la réponse au client que cette réponse elle-même. Une méthode utilisable pour des applications simples est la suivante : • • • • • • • le contrôleur est assuré par une servlet unique. dans le contexte de la session ou de l'application. Celui-ci voit passer toutes les demandes des clients. La littérature MVC n'est pas explicite sur ce point. C'est ce qu'on appelle la vue V. le M de MVC.)].Le traitement d'une demande d'un client se déroule selon les étapes suivantes : 1. Elle utilise pour cela. le contrôleur C demande à la vue choisie de s'afficher. Une servlet unique traite toutes les requêtes de tous les utilisateurs.java Utilisateur formulaire./appli?action=liste)... Ce modèle d'architecture (Design Pattern) MVC est appelée le modèle " Front Controller " ou encore modèle à contrôleur unique. la servlet fait exécuter une méthode interne de type [doAction(. le client fait une demande au contrôleur. Excel.

apache. ont le même mécanisme pour déterminer quelle méthode [doAction] il faut exécuter pour traiter l'action demandée par l'utilisateur 2. par l'exemple 60/264 . on s'aperçoit que les servlets de deux applications différentes : 1. Lorsqu'on a écrit plusieurs applications de ce type. souvent appelés " frameworks ". Le plus ancien et probablement le plus connu d'entre-eux est Struts (http://struts. sont apparus pour apporter les facilités précédentes aux développeurs. celle de l'interface web.org/). ne diffèrent en fait que par le contenu de ces méthodes [doAction] La tentation est alors grande de : • factoriser le traitement (1) dans une servlet générique ignorante de l'application qui l'utilise • déléguer le traitement (2) à des classes externes puisque la servlet générique ne sait pas dans quelle application elle est utilisée • faire le lien entre l'action demandée par l'utilisateur et la classe qui doit la traiter à l'aide d'un fichier de configuration Des outils.1 Les vues de l'application L'application reprend le formulaire utilisé dans les précédents exemples.springframework.developpez.org/) offre des facilités analogues à celles de Struts. De façon générale.com/java/struts/). Nous présentons maintenant un exemple d'architecture MVC à base de servlet et pages JSP. Ce framework est décrit dans (http://tahe.com/java/springmvc-part1/). La première page de l'application est la suivante : Les bases du développement web MVC en Java. une application web MVC à base de servlets et pages JSP aura l'architecture suivante : Application web couche [web] Servlet Utilisateur JSP1 JSP2 JSPn Pour des applications simples. Son utilisation a été décrite dans plusieurs articles (http://tahe. Jakarta Struts est un projet de l'Apache Software Foundation (www.org). couche [metier] Modèles couche [dao] Données 5 Application web MVC [personne] – version 1 5.apache. cette architecture est suffisante. le framework Spring (http://www.utilisateur Couche interface utilisateur [ui / web] Il n'y a en fait qu'une couche.developpez. Apparu plus récemment.

Pour répondre à ceux-ci. [ServletPersonne] est le contrôleur de l'application qui traite toutes les requêtes des clients. les erreurs sont signalées dans une vue appelée [erreurs] : vue [erreurs] vue [formulaire] 5. par l'exemple Modèles 61/264 . Requête GET Les bases du développement web MVC en Java.2 Architecture de l'application L'application web [personne1] aura l'architecture suivante : Application web couche [web] ServletPersonne Utilisateur [formulaire] [réponse] [erreurs] Cette architecture est une architecture 1tier : il n'y a pas de couches [métier] ou [dao].Nous appellerons cette vue. Si on fait des saisies correctes. seulement une couche [web]. il utilise l'une des trois vues [formulaire. la vue [formulaire]. Il nous faut déterminer comment le contrôleur [ServletPersonne] détermine l'action qu'il doit faire à la réception d'une requête d'un utilisateur. erreurs]. Une requête client est un flux HTTP qui diffère selon qu'elle est faite avec une commande GET ou POST. celles-ci sont affichées dans une vue qui sera appelée [réponse] : vue [réponse] vue [formulaire] Si les saisies sont incorrectes. réponse.

9.q=0.q=0.q=0. POST /URL HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5. 6.1 Host: localhost:8080 User-Agent: Mozilla/5.4.q=0.9.q=0. 8. par exemple [/appli/ajouter?id=4].application/xhtml+xml. 7.1 Mais le paramètre [action] peut être également inclus dans les paramètres postés (ligne 15 ci-dessus) comme dans : POST /appli HTTP/1.7 Keep-Alive: 300 Connection: keep-alive [ligne vide] La ligne 1 précise l'url demandée. 2. 3. fr.8. U.Dans ce cas.0.4. 13.application/xhtml+xml. 14.7. rv:1.2 Accept-Encoding: gzip. le flux HTTP ressemble à ce qui suit : 1.en. un paramètre de l'url précise l'action. Dans le cas du GET.1 • poster le paramètre action : POST /appli HTTP/1.3 Accept: text/xml.0 (Windows.5. 7. Ici le paramètre [action] indique au contrôleur l'action qui lui est demandée.8. GET /URL HTTP/1. Ici.utf-8.q=0. D'autres solutions sont possibles.0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/personne1/main Cookie: JSESSIONID=9F5E5BFA29643FC6B1601EEED907E1F9 Content-Type: application/x-www-form-urlencoded Content-Length: 43 [ligne vide] txtNom=&txtAge=&action=validationFormulaire La ligne 1 précise l'url demandée.0.7.application/xml. nous utiliserons ces différentes techniques pour indiquer au contrôleur ce qu'il doit faire : • intégrer le paramètre action dans l'url demandée : POST /appli?action=ajouter&id=4 HTTP/1.2 Accept-Encoding: gzip.0 (Windows. 8.1.fr..text/plain..3) Gecko/20060426 Firefox/1.1 On peut utiliser cette url pour préciser l'action à faire comme pour le GET.3) Gecko/20060426 Firefox/1. le paramètre [action] était intégrée dans l'URL. le dernier élément de l'url [/ajouter] est utilisé par le contrôleur pour déterminer l'action qu'il doit faire.q=0.9. Cela peut être également le cas ici comme dans : POST /appli?action=ajouter&id=4 HTTP/1. par exemple [/appli?action=ajouter&id=4]. 4.*/*. par exemple : GET /personne1/ HTTP/1. Requête POST Dans ce cas.8.image/png.de.*. rv:1.q=0. 6.de.deflate Accept-Charset: ISO-8859-1.6.1 Les bases du développement web MVC en Java.fr. [ligne vide] action=ajouter&id=4 • utiliser le dernier élément de l'url comme nom de l'action : POST /appli/ajouter?id=4 HTTP/1..8.q=0.en-us. par l'exemple 62/264 .q=0.q=0. 3. 11.deflate Accept-Charset: ISO-8859-1.utf-8. 5.q=0.q=0. 10. U.1 .q=0. 5. Les deux précédentes sont courantes.text/html. 2.en-us.1 .6.application/xml.q=0.. Windows NT 5. par exemple : POST /personne1/main HTTP/1.en.3 Accept: text/xml.5 Accept-Language: fr-fr. 9. 12.8. [ligne vide] action=ajouter&id=4 Dans ce qui suit. 4.1 On peut utiliser cette url pour préciser l'action à faire. le dernier élément de l'url précise l'action. le flux HTTP ressemble à ce qui suit : 1.*/*.q=0.1. 15.text/plain.5.q=0. On peut utiliser diverses méthodes : 1.5 Accept-Language: fr-fr.8.0.image/png.text/html.*. fr. 2. 10. Windows NT 5.q=0.

on outilisera l'option [clic droit sur projet -> Properties -> J2EE] : 1 On indiquera en [1] le nouveau contexte. Les bases du développement web MVC en Java.1. On ne gardera pas le contexte [mvc-personne-01] proposé par défaut. on suivra la démarche décrite au paragraphe 3. on veut changer le contexte de l'application web. On choisira [personne1] comme indiqué ci-dessous : Le résultat obtenu est le suivant : Si d'aventure.5.3 Le projet Eclipse Pour créer le projet Eclipse [mvc-personne-01] de l'application web [personne1]. page 29. par l'exemple 63/264 .

erreurs] sont dans le dossier [WEB-INF/vues]. ce qui empêche l'utilisateur de les demander directement comme le montre l'exemple ci-dessous : Les bases du développement web MVC en Java. par l'exemple 64/264 . le projet sera le suivant : • • le contrôleur [ServletPersonne] est dans le dossier [src] les pages JSP des vues [formulaire.Nous allons créer un sous-dossier [vues] dans le dossier [WEB-INF] : [clic droit sur WEB-INF -> New -> Folder] : Le nouveau projet est maintenant celui-ci : Une fois complété. réponse.

jsp 18.xsd"> 6. /WEB-INF/vues/formulaire. <init-param> 15.st. <param-value> 23.ServletPersonne --> 9. Cela permet de les déplacer sans avoir à recompiler l'application.Mapping ServletPersonne--> 34. C'est l'url de la vue [formulaire]. </servlet-mapping> 38. <!-.<?xml version="1. lignes 20-25 : définissent un paramètre de configuration nommé [urlErreurs].sun. <servlet> 10.sun. </param-value> 25. </init-param> 26. </param-value> 19. ligne 40 : [index. 5.com/xml/ns/j2ee/web-app_2_4.fichiers d'accueil --> 39. 7.4 Configuration de l'application web [personne1] Le fichier web. Que dit ce fichier de configuration ?       lignes 34-37 : l'URL /main est traitée par la servlet nommée personne lignes 10-13 : la servlet nommée personne est une instance de la classe [ServletPersonne] lignes 14-19 : définissent un paramètre de configuration nommé [urlReponse]. <param-value> 17. <servlet-mapping> 35. <param-value> 29. </init-param> 20. lignes 26-31 : définissent un paramètre de configuration nommé [urlFormulaire]. <servlet-name>personne</servlet-name> 11. /WEB-INF/vues/erreurs. <init-param> 27. Le lecteur est invité à les créer au fil de sa lecture. <servlet-name>personne</servlet-name> 36. xmlns="http://java. <welcome-file-list> 40.com/xml/ns/j2ee" 4.personne. <init-param> 21.0" encoding="UTF-8"?> 2. <param-name>urlErreurs</param-name> 22.sun. xmlns:xsi="http://www. </param-value> 31.ServletPersonne 13.jsp] sera la page d'accueil de l'application. <servlet-class> 12. </welcome-file-list> 42.Nous décrivons maintenant les différents composants de l'application web [/personne1]. </servlet-class> 14. Les bases du développement web MVC en Java. /WEB-INF/vues/reponse.w3.com/xml/ns/j2ee http://java. par l'exemple 65/264 . C'est l'url de la vue [erreurs]. erreurs] font l'objet chacune d'un paramètre de configuration. <!-. </servlet> 33.jsp 30. réponse.4" 3.</web-app> 43. istia.jsp</welcome-file> 41. <param-name>urlFormulaire</param-name> 28. <welcome-file>index. Les url des pages JSP des vues [formulaire. <display-name>mvc-personne-01</display-name> 8. C'est l'url de la vue [réponse].<web-app id="WebApp_ID" version="2.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://java. </init-param> 32. <!-.xml de l'application /personne1 sera le suivant : 1.jsp 24.servlets. <param-name>urlReponse</param-name> 16. <url-pattern>/main</url-pattern> 37.

le navigateur va donc demander l'url [/personne1/main] comme on le lui demande (ligne 4).jsp] lui envoie la réponse HTTP suivante : 1.jsp] se contente de rediriger le client vers l'url [/personne1/main]. Ce fichier est à la racine du dossier [WebContent] : Son contenu est le suivant : 1. 4. ligne 40). lorsque le navigateur demande l'url [/personne1]. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 3. HTTP/1.5. 2. 5. Celles-ci permettent de cerner les besoins de l'utilisateur en termes d'interface graphique et peuvent être testées sans la présence du contrôleur. Path=/personne1 Location: http://localhost:8080/personne1/main Content-Type: text/html. %> La page [index.Lorsque l'utilisateur va demander l'url [/personne1].1 pour indiquer au serveur de se rediriger vers une autre URL ligne 4 : l'URL vers laquelle doit se rediriger le navigateur Après cette réponse. 5. 4.jsp] qui va envoyer la réponse (fichier d'accueil. 5.5 Le code des vues Nous commençons notre écriture de l'application web par celle de ses vues. 6. 6. 3. 18 May 2006 15:45:23 GMT • • ligne 1 : réponse HTTP/1. c'est le fichier [index. 2.01 Transitional//EN"> <% response.1 La vue [formulaire] Cette vue est celle du formulaire de saisie du nom et de l'âge : 1 2 3 n° 1 2 3 4 5 4 rôle 5 type HTML <input type= "text "> <input type= "text "> <input type= "submit "> <input type= "reset "> <input type= "button "> nom txtNom saisie du nom txtAge saisie de l'âge envoi des valeurs saisies au serveur à l'url /personne1/main pour remettre la page dans l'état où elle a été reçue initialement par le navigateur pour effacer les contenu des champs de saisie [1] et [2] 66/264 Les bases du développement web MVC en Java.charset=ISO-8859-1 Content-Length: 0 Date: Thu. <%@ page language="java" contentType="text/html. 5.sendRedirect("/personne1/main").x 302 Déplacé Temporairement Server: Apache-Coyote/1. [index. 7.1 Set-Cookie: JSESSIONID=9F5E5BFA29643FC6B1601EEED907E1F9. par l'exemple . Ainsi.xml] de l'application [/personne1] indique que cette demande va être traitée par le contrôleur [ServletPersonne] (lignes 35-36). Le fichier [web.

nous appellerons cette vue. les paramètres [txtNom. 14. Le code de la page JSP [formulaire.formulaire</title> </head> <body> <center> <h2>Personne . 4. C'est donc cette dernière qui fixe le contenu initial des champs de saisie du formulaire. 2. 7. par l'exemple 67/264 . 24. 34. la vue [formulaire(nom. Son modèle est constitué des éléments suivants : • • [nom] : un nom (String) trouvé dans les attributs de session associé à la clé "nom" [age] : un âge (String) trouvé dans les attributs de session associé à la clé "age" La vue [formulaire] est obtenue lorsque l'utilisateur demande l'url [/personne1/main]. 18. Par la suite. 17. 40. 16. txtAge] sont initialisés respectivement avec les variables [nom] (ligne 22) et [age] (ligne 26). 12. 36.getAttribute("age"). lignes 18-38 : la page JSP va générer un formulaire HTML (balise <form>) ligne 18 : la balise <form> n'a pas d'attribut action pour désigner l'url qui devra traiter les valeurs postées par le bouton [Envoyer] de type submit (ligne 32). 19. Ce dernier paramètre va permettre au contrôleur de savoir ce qu'il doit faire. ligne 33 : le bouton [Rétablir] de type [reset] permet de remettre le formulaire dans l'état où il était lorsque le navigateur l'a reçu. String age=(String)request. les valeurs postées sont celles des champs HTML [txtNom] (ligne 22) et [txtAge] (ligne 26) et [action] (ligne 37). 32. 37. Les bases du développement web MVC en Java. 25. 28. 13. 30. 6. Les valeurs du formulaire seront alors postées à l'url à partir de laquelle a été obtenue le formulaire. 8. Ces variables obtiennent leurs valeurs d'attributs de la requête (lignes 67). 5. 33.getAttribute("nom").d. à l'affichage initial du formulaire. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 35. attributs qu'on sait initialisés par la servlet. 21.a. Par ailleurs. ligne 34 : le bouton [Effacer] de type [reset] n'a pour l'instant aucune fonction. age] de son modèle. c. 15. 29. 9. 27. age)] lorsque nous voudrons préciser à la fois le nom de la vue et son modèle. 20. 10.01 Transitional//EN"> <% // on récupère les données du modèle String nom=(String)request. 41. 31. c'est à dire l'url du contrôleur [ServletPersonne]. Ainsi celui-ci est-il utilisé à la fois pour générer le formulaire vide demandé initialement par un GET et traiter les données saisies qui lui seront postées avec le bouton [Envoyer]. on se rappellera que lorsque l'utilisateur clique sur le bouton [Envoyer]. <%@ page language="java" contentType="text/html.jsp] qui génère la vue [formulaire] est le suivant : 1. les champs de saisie [txtNom. %> <html> <head> <title>Personne . 3. 22. 11. 38. 39.Elle est générée par la page JSP [formulaire. 42. ce sera le contrôleur [ServletPersonne] qui construira ce modèle.jsp]. 23. txtAge] sont postés à l'url [/personne1/main].formulaire</h2> <hr> <form method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> </tr> <tr> </table> <table> <tr> <td><input type="submit" value="Envoyer"></td> <td><input type="reset" value="Rétablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table> <input type="hidden" name="action" value="validationFormulaire"> </form> </center> </body> </html> • • • • • • • lignes 6-7 : la page JSP commence par récupérer dans la requête [request] les éléments [nom. Dans le fonctionnement normal de l'application. 26. l'url du contrôleur [ServletPersonne].

les éléments [nom. Dans le fonctionnement normal de l'application. 12. la vue [réponse(nom.réponse</h2> <hr> <table> <tr> <td>Nom</td> <td><%= nom %> </tr> <tr> <td>Age</td> <td><%= age %> </tr> </table> </body> </html> • • lignes 6-7 : la page JSP commence par récupérer dans la requête [request] les éléments [nom.5. 14.getAttribute("nom"). 5.5. associé à la clé "age" Le code de la page JSP [reponse.3 La vue [erreurs] Cette vue signale les erreurs de saisie dans le formulaire : Les bases du développement web MVC en Java. 17. Son modèle est constitué des éléments suivants : • • [nom] : un nom (String) qui sera trouvé dans les attributs de session. 27. 29. 22. 19. 2. 23.getAttribute("age"). par l'exemple 68/264 . associé à la clé "nom" [age] : un âge (String) qui sera trouvé dans les attributs de session. 15. 21. 4. 8. 25. age)]. 13. 11. %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne . 9.2 La vue [reponse] Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides : vue [réponse] vue [formulaire] Elle est générée par la page JSP [reponse. 5. 24. 26. age] du modèle sont ensuite affichés lignes 20 et 24 Par la suite.5. <%@ page language="java" contentType="text/html. 6. 18.01 Transitional//EN"> <% // on récupère les données du modèle String nom=(String)request. 3.jsp] est le suivant : 1. 7. 20. 28. 16. nous appelons cette vue. ce sera le contrôleur [ServletPersonne] qui construira ce modèle.jsp]. age] de son modèle. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 10. String age=(String)request.

ce sera le contrôleur [ServletPersonne] qui construira ce modèle.println("<li>" + (String) erreurs. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 14. <%@ page language="java" contentType="text/html. %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0.i<erreurs. Pour cela. 18.ArrayList" %> <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. 22. On doit toujours viser à réduire celui-ci au minimum pour ne pas polluer le code HTML. 23. Cet élément représente un objet de type ArrayList d'éléments de type String. 26. 9. nous dupliquons les pages JSP des vues dans le dossier [/WebContent/JSP] du projet Eclipse : Les bases du développement web MVC en Java. 4. Pour cela deux conditions : • il faut pouvoir les demander directement à l'application sans passer par le contrôleur • il faut que la page JSP initialise elle-même le modèle qui normalement sera construit par le contrôleur Pour réaliser ces tests. lignes 18-22 : affichent la liste des messages d'erreur.get(i) + "</li>\n"). 7. 20. par l'exemple 69/264 . ligne 4 : à noter la balise d'importation des paquetages nécessaires à la page JSP Par la suite. 17. 25. 13.getAttribute("erreurs"). 3.6 Tests des vues Il est possible de tester la validité des pages JSP sans avoir écrit le contrôleur. 5.jsp] est le suivant : 1. 8. on est amené à écrire du code Java dans le corps HTML de la page. 10. 6. 21. nous appelons cette vue. 2.vue [erreurs] vue [formulaire] Elle est générée par la page JSP [erreurs. 16.jsp].01 Transitional//EN"> <%@ page import="java. 19. la vue [erreurs(erreurs)]. 15. Dans le fonctionnement normal de l'application.util. 24. Nous verrons ultérieurement qu'il existe des solution permettant de réduire la quantité de code Java dans les pages JSP. }//for %> </ul> </body> </html> • • • ligne 8 : la page JSP commence par récupérer dans la requête [request] l'élément [erreurs] de son modèle. 12. Ces éléments sont des messages d'erreurs. Son modèle est constitué des éléments suivants : • [erreurs] : une liste (ArrayList) de messages d'erreur qui sera trouvée dans les attributs de requête. 5. associée à la clé "erreurs" Le code de la page JSP [erreurs.i++){ out.size(). 11.

. 4. 9. 12..add("erreur2"). <% 12. 4. 6. 6. 8. ArrayList erreurs=(ArrayList)request. request."tintin"). request. 7. Les lignes 3-7 ont été ajoutées pour créer le modèle dont a besoin la page lignes 11-12. les pages sont modifiées de la façon suivante : [formulaire.setAttribute("nom". 13. <% // -. ArrayList<String> erreurs1=new ArrayList<String>(). 7. <% // -. 6.jsp] : 1. 5. 7.setAttribute("nom". 14. [reponse. %> 15.setAttribute("age". 9. 15."30").getAttribute("nom"). 14.. 3.setAttribute("erreurs"."10"). 17.. 16. 9.. 10. 14. [erreurs. 13. %> <html> <head> . request. 11. String age=(String)request. 16. 17. erreurs1. par l'exemple 70/264 .getAttribute("age").getAttribute("nom").getAttribute("age").setAttribute("age". 2. 5. 15. 3..erreurs1).. 17.. %> <html> <head> . 11. // on récupère les données du modèle 13.jsp] : 1. 16. 11.Puis dans le dossier JSP.getAttribute("erreurs"). %> <% // on récupère les données du modèle String nom=(String)request. 8. 2.. .add("erreur1").test : on crée le modèle de la page request. <html> Les bases du développement web MVC en Java. 12. Les lignes 3-7 ont été ajoutées pour créer le modèle dont a besoin la page lignes 11-12. 2. %> <% // on récupère les données du modèle String nom=(String)request.test : on crée le modèle de la page 5.. %> 10. 18. 8.. . String age=(String)request. erreurs1."milou").jsp] : 1. 3. <% 4. 10. // -.test : on crée le modèle de la page request.

7.vérifier les valeurs des paramètres [txtNom.7.1 1. . 5. java.xml] de l'application (cf paragraphe 5.si elles sont incorrectes.envoyer en réponse la vue appropriée. txtAge] . import import import import java.util. nous pouvons passer à l'écriture de son contrôleur [ServletPersonne]. le contrôleur.récupérer la requête du client. 5.io. par l'exemple 71/264 . Le contrôleur [ServletPersonne] va traiter les actions suivantes : n° 1 2 demande [GET /personne1/main] [POST /personne1/main] avec paramètres [txtNom.ArrayList.servlet.Map. txtAge. java. Lançon Tomcat si ce n'est déjà fait puis demandons les url suivantes : Nous obtenons bien les vues attendues. page 65). 8. 6. java..util. 3.18.4. 4. D'après le fichier [web.ServletConfig.7 Le contrôleur [ServletPersonne] Il reste à écrire le coeur de notre application web. . 5.st.si elles sont correctes.HashMap. <head> 19. envoyer la vue [erreurs(erreurs)] . action] postés origine url tapée par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement . Les bases du développement web MVC en Java. Son rôle consiste à : .traiter l'action demandée par celui-ci. .envoyer la vue [formulaire] vide . Les lignes 3-9 ont été ajoutées pour créer le modèle dont a besoin la page ligne 13..IOException.util. Squelette du contrôleur package istia.age)] L'application démarre lorsque l'utilisateur demande l'url [/personne1/main].servlets. envoyer la vue [reponse(nom. 2. Le code du contrôleur [ServletPersonne] est le suivant : import javax.personne. cette demande est traitée par une instance de type ServletPersonne que nous décrivons maintenant. Maintenant que nous avons une confiance raisonnable dans les pages JSP de l'application.

// on passe la main au GET 45.servlet. Ce sera la seule fois. } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request.st. le contrôleur ne doit pas avoir de champs que ses méthodes pourraient modifier. doGet(request..servlet.. package istia. ServletException { 44. // init Les bases du développement web MVC en Java.7. On rappelle que. 13. Chaque client fait l'objet d'un thread d'exécution et les méthodes du contrôleur sont ainsi exécutées simultanément par différents threads... action]..9. 10. pour cette raison. . // paramètres d'instance 7. HttpServletResponse response) 43. public class ServletPersonne extends HttpServlet { 6. 9. 41. 37. Une fois chargée en mémoire. 8. IOException{ 32.servlet. 33. le contrôleur y restera et traitera les requêtes des différents clients.http. 35. throws IOException."urlReponse"}.HttpServletRequest.. } • • • • • lignes 20-22 : la méthode [init] exécutée au chargement initial de la servlet lignes 25-28 : la méthode [doGet] appelée par le serveur web lorsqu'une requête de type GET a été faite à l'application lignes 42-46 : la méthode [doPost] appelée par le serveur web lorsqu'une requête de type POST a été faite à l'application..ServletException. Ils sont initialisés par la méthode [init] dont c'est le rôle principal.servlet. public void doPost(HttpServletRequest request.. 11. 14. 16. 10. } // affichage vue initiale void doInit(HttpServletRequest request. } 34. 18. response). 24. sa méthode [init] est exécutée. 27.. javax. 21. 12. 17. Ses champs doivent être en lecture seule. // validation du formulaire 36. Ces objets partagés peuvent être placés en deux endroits : ..le contexte d'exécution de l'application (ServletContext) Le code de la méthode [init] du contrôleur [ServletPersonne] est le suivant : 1.http. 31. . 46. private String[] paramètres={"urlFormulaire". } 40. 20. Nous décrivons maintenant les différentes méthodes du contrôleur 5. HttpServletResponse response) throws ServletException. 28. import import import import javax.HttpServlet. javax. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { . } 47. // post 42. 29. void doValidationFormulaire(HttpServletRequest request. 12.personne. 3. 4. ..http. HttpServletResponse response) throws ServletException. 22. 11.. private ArrayList erreursInitialisation = new ArrayList<String>(). La méthode [init] a pour but d'initialiser les objets nécessaires à l'application web et qui seront partagés en lecture seule par tous les threads clients. lignes 31-33 : la méthode [doInit] traite l'action n° 1 [GET /personne1/main] lignes 36-39 : la méthode [doValidationFormulaire] traite l'action n° 2 [POST /personne1/main] avec les paramètres postés [txtNom. @SuppressWarnings("serial") 5.les champs privés du contrôleur . txtAge.String>(). 23.servlets. par l'exemple 72/264 . private Map params=new HashMap<String.HttpServletResponse. ServletException { . 39. 30. Il n'y a donc pas de problèmes d'accès concurrents aux champs du contrôleur dans cette méthode. javax. Comme il est montré. 2. HttpServletResponse response) throws IOException. IOException{ 38.2 Initialisation du contrôleur Lorsque la classe du contrôleur est chargée par le conteneur de servlets. 26. 25. 15. celle-ci sera traitée par la méthode [doGet] également (ligne 45). 19. Cette méthode a en effet la particularité d'être exécutée une unique fois par un seul thread. // init @SuppressWarnings("unchecked") public void init() throws ServletException { . private String urlErreurs = null.

getMethod(). 5. 5.getInitParameter("urlErreurs"). Son code est le suivant : 1.personne.length. 38. l'erreur est ajoutée à la liste des erreurs [erreursInitialisation] initialement vide (ligne 8). erreursInitialisation). par l'exemple 73/264 . // on note l'erreur 25. 23. il est mémorisé avec sa valeur dans le dictionnaire [params] initialement vide (ligne 10). for(int i=0. // fin return. on interrompt l'application en lançant une [ServletException] (ligne 33).put(paramètres[i]. 17. 7. 26. 12. 22. . // action ? if(action==null){ action="init".getInitParameter(paramètres[i]). 35. le contenu du fichier [web. params. 25.getParameter("action").13. package istia. S'il n'existe pas. public void init() throws ServletException { 15. } // exécution action Les bases du développement web MVC en Java. private Map params=new HashMap<String.. urlErreurs = config. 11.. } • • • • • • ligne 16 : on récupère la configuration de l'application web. private ArrayList erreursInitialisation = new ArrayList<String>(). 30. valeur=config. 19.toLowerCase(). 22.valeur). if (urlErreurs == null) 33. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramètres d'instance private String urlErreurs = null. }else{ 27. throw new ServletException( 34. } 36. // l'url de la vue [erreurs] a un traitement particulier 31. 29. 19. 14.String>(). 16. 13. 10. // valeur du paramètre 21.st. if(valeur==null){ 24. } 30. } 37. 29. 8. 20. 15. 9. 4. // paramètre présent ? 23. ligne 28 : si le paramètre est présent. 34.add("Le paramètre ["+paramètres[i]+"] n'a pas été initialisé"). // on traite les autres paramètres d'initialisation 18.a.. 26. 32. 28.xml] lignes 19-29 on récupère les paramètres d'initialisation de la servlet dont les noms sont définis dans le tableau [paramètres] de la ligne 9 ligne 21 : la valeur du paramètre est récupérée ligne 25 : si le paramètre est absent. response).d. c.i++){ 20. private String[] paramètres={"urlFormulaire". 31. getServletContext(). 2. HttpServletResponse response) throws IOException. lignes 31-35 : le paramètre [urlErreurs] doit être obligatoirement présent car il désigne l'url de la vue [erreurs] capable d'afficher les éventuelles erreurs d'initialisation. 27. ServletConfig config = getServletConfig(). // on récupère les paramètres d'initialisation de la servlet 16. ServletException { // on vérifie comment s'est passée l'initialisation de la servlet if (erreursInitialisation."urlReponse"}. 21. du fait que la méthode [doPost] renvoie sur la méthode [doGet]. // on récupère l'action à exécuter String action=request. 32. erreursInitialisation..servlets.3 La méthode [doGet] La méthode [doGet] traite aussi bien les demandes GET que POST à la servlet. @SuppressWarnings("unchecked") 14. 6.size() != 0) { // on passe la main à la page d'erreurs request..getRequestDispatcher(urlErreurs). @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request.i<paramètres. 35.7. } // on récupère la méthode d'envoi de la requête String méthode=request.. 3. // on mémorise la valeur du paramètre 28. . 18. String valeur=null. 24. 33.setAttribute("erreurs". "Le paramètre [urlErreurs] n'a pas été initialisé"). .forward( request. 17.

response). return. Pour comprendre ce code. 15."urlReponse"}.. 2.equals("init")){ 37.equals("validationFormulaire")){ 42. lignes 36-40 : traitement de la requête n° 1 [GET /personne1/main]. Son code est le suivant : 1. 21.equals("post") && action. Dans cette requête. Sur cette requête. package istia. doInit(request. // affichage vue initiale void doInit(HttpServletRequest request. Si ce n'est pas le cas. Les bases du développement web MVC en Java. // autres cas 47. 45. getServletContext(). 23. throws IOException. } • lignes 18-25 : on vérifie que la liste des erreurs d'initialisation est vide. Ce sera le cas lors de la requête initiale n° 1 [GET /personne1/main]. request. il a la valeur [validationFormulaire]. ""). 55. } 46..setAttribute("nom". lignes 41-45 : traitement de la requête n° 2 [POST /personne1/main]. private String[] paramètres={"urlFormulaire".st. } 49. 18. return. on fait afficher la vue [erreurs(erreursInitialisation)] qui va signaler la ou les erreurs. 11.. } • lignes 18-19 : la vue [formulaire] est affichée. Rappelons que dans notre application seule la requête n° 2 [POST /personne1/main] a le paramètre [action].age)] vide. doInit(request.response). ligne 28 : on récupère la méthode [get] ou [post] que le client a utilisée pour faire sa requête ligne 30 : on récupère la valeur du paramètre [action] de la requête. par l'exemple 74/264 .7.setAttribute("age". 6.getAttribute("nom"). 14. } 41. response). IOException{ // on envoie le formulaire vide request.String>().get("urlFormulaire")). public void doPost(HttpServletRequest request. 19.36. // validation du formulaire de saisie 43. on lui affecte la valeur " init ".forward( request. // démarrage application 38. 13. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramètres d'instance private String urlErreurs = null. private ArrayList erreursInitialisation = new ArrayList<String>(). ServletException { 53. 39.response). 12. HttpServletResponse response) throws ServletException. 8.4 La méthode [doInit] Cette méthode traite la requête n° 1 [GET /personne1/main]. 10. 17. 44.servlets. 40... 50. on fait comme si on était dans le cas n° 1 5. // post 51. elle doit envoyer la vue [formulaire(nom. . 22. private Map params=new HashMap<String. } 56. doGet(request. il faut se rappeler le modèle de la vue [erreurs] : <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"). 5.getRequestDispatcher((String)params. 9. lignes 31-34 : si le paramètre [action] n'est pas présent.response). 48. } . Rappelons le modèle attendu par cette vue : <% // on récupère les données du modèle String nom=(String)request.personne. doValidationFormulaire(request. 4. if(méthode. 16. . 7. // on passe la main au GET 54. if(méthode. Le contrôleur crée cet élément ligne 20. %> • • • • • • La vue [erreurs] attend un élément de clé " erreurs " dans la requête. HttpServletResponse response) 52. 3.equals("get") && action. ""). ligne 47 : si on n'est pas dans l'un des deux cas précédents.. return. 20.

getAttribute("erreurs"). on fait afficher la vue [reponse(nom.%> String age=(String)request. 35. par l'exemple 75/264 . 7. 37. private String[] paramètres={"urlFormulaire". 33.8 Tests Incluons le projet [mvc-personne-01] dans les applications de Tomcat en suivant la procédure décrite page 34 du paragraphe 3. • lignes 16-17 : le modèle [nom.5 La méthode [doValidationFormulaire] Cette méthode traite la requête n° 2 [POST /personne1/main] dans laquelle les paramètres postés sont [action.size() != 0) { // on envoie la page d'erreurs request.getParameter("txtNom").. 23. erreursAppel). txtNom.. private Map params=new HashMap<String. 20. 18. ..add("Le champ [nom] n'a pas été rempli").getRequestDispatcher((String)params. 4. txtAge]. 13.setAttribute("nom". return. 21. 31. nom). 9. 16.getRequestDispatcher(urlErreurs). 12.getParameter("txtAge"). .forward(request.matches("^\\s*\\d+\\s*$")) erreursAppel.7.age)]. 38. 40. on fait afficher la vue [erreurs(erreursAppel)].add("Le champ [age] est erroné")..personne. 11. 10. 3. 25. String age=(String)request. // validation du formulaire void doValidationFormulaire(HttpServletRequest request.get("urlReponse")). Rappelons le modèle de cette vue : <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. } .forward(request. String age = request. HttpServletResponse response) throws ServletException. response).getAttribute("age"). 36.setAttribute("age"."urlReponse"}. } • • • lignes 16-17 : on récupère dans la requête du client les valeurs des paramètres "txtNom" et "txtAge". // l'âge doit être un entier >=0 if (!age. if (nom. %> 5.. return.age] de la vue [formulaire] est initialisé avec des chaînes vides. 6.3 : Les bases du développement web MVC en Java. lignes 19-26 : la validité de ces deux paramètres est vérifiée lignes 28-33 : si l'un des paramètres est erroné. 34. 32. Il faut se rappeler le modèle de la vue [reponse] : <% // on récupère les données du modèle String nom=(String)request. 39. } // les paramètres sont corrects . @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramètres d'instance private String urlErreurs = null. 42. 22. package istia. getServletContext(). age). %> • lignes 35-38 : si les deux paramètres " txtNom " et " txtAge " récupérés ont des valeurs valides. response).on envoie la page réponse request.getAttribute("age"). Son code est le suivant : 1. 15.getAttribute("nom"). 30.servlets. 14. request. // le nom doit être non vide nom = nom. 2. 19. getServletContext().trim(). 41. private ArrayList erreursInitialisation = new ArrayList<String>(). 8. 28.String>(). 26. // vérification des paramètres ArrayList<String> erreursAppel = new ArrayList<String>().setAttribute("erreurs". 29..st. 5. 17. 24. 27.equals("")) erreursAppel. IOException{ // on récupère les paramètres String nom = request. // des erreurs dans les paramètres ? if (erreursAppel. 5.

. Ainsi ci-dessous.xml et voir le résultat..]. nous ferons plus court dans nos explications.1 Introduction Nous nous proposons maintenant d'ajouter à notre application une gestion de session. On obtient la réponse suivante : 6 Application web MVC [personne] – version 2 Nous allons maintenant proposer des variantes de l'application [/personne1] précédente que nous appellerons [/personne2. par l'exemple 76/264 . page 60. Ces variantes ne modifient pas l'architecture initiale de l'application qui reste la suivante : Application web couche [web] ServletPersonne Utilisateur [formulaire] [réponse] [erreurs] Pour ces variantes.1. Modèles 6.jsp </param-value> </init-param> --> On lance / relance Tomcat et on demande l'url [http://localhost:8080/personne1/main]. il y a N sessions.Lancez Tomcat. Nous ne présenterons que les modifications apportées vis à vis de la version précédente. Rappelons les points suivants :  le dialogue HTTP client-serveur est une suite de séquences demande-réponse déconnectées entre elles  la session sert de mémoire entre différentes séquences demande-réponse d'un même utilisateur. On pourra par exemple supprimer la présence d'un des paramètres de configuration urlXXX dans web. S'il y a N utilisateurs. On pourra en rajouter. Ceci fait. La séquence d'écran suivante montre ce qui est maintenant désiré dans le fonctionnement de l'application : Echange n° 1 Les bases du développement web MVC en Java. /personne3.xml] : <!-<init-param> <param-name>urlFormulaire</param-name> <param-value> /WEB-INF/vues/formulaire. . on pourra reprendre les tests montrés en exemple au paragraphe 5. l'un des paramètres est mis en commentaires dans [web.

C'est la notion de session qui. Si au cours de l'échange. Au cours de l'échange n°1. Echange n° 2 demande réponse Dans l'échange n° 1. par l'exemple 77/264 .âge) les valeurs (xx. Or on peut constater que lors de l'échange n° 2 il est capable de réafficher leurs valeurs dans sa réponse. Il y a d'autres solutions possibles pour résoudre ce problème. ici. à la fin de l'échange il les "oublie". le serveur a eu connaissance de ces valeurs. le serveur va mémoriser dans la session le couple (nom. l'utilisateur a donné pour le couple (nom. Echange n° 2 Les bases du développement web MVC en Java.age) que le client lui a envoyé afin d'être capable de l'afficher au cours de l'échange n° 2.yy). Voici un autre exemple de mise en oeuvre de la session entre deux échanges : Echange n° 1 demande réponse La nouveauté vient du lien de retour au formulaire qui a été rajouté dans la page de la réponse. va permettre au serveur web de mémoriser des données au fil des échanges successifs client-serveur.demande réponse La nouveauté vient du lien de retour au formulaire qui a été rajouté dans la vue [erreurs].

Pour cela procédons de la manière suivante : [clic droit sur projet mvc-personne-01 -> Copy] : puis [clic droit dans Package Explorer -> Paste] : 1 2 .indiquons en [1] le nom du nouveau projet et en [2] le nom d'un dossier existant mais vide Le projet [mvc-personne-02] est alors créé : Les bases du développement web MVC en Java. nous allons dupliquer le projet Eclipse [mvcpersonne-01] afin de récupérer l'existant. par l'exemple 78/264 .2 Le projet Eclipse Pour créer le projet Eclipse [mvc-personne-02] de l'application web [/personne2].demande réponse 6.

6. Il va nous falloir faire quelques modifications à la main avant de pouvoir l'utiliser. Les bases du développement web MVC en Java. nous pouvons en profiter pour modifier. je n'ai pas vu comment faire la première sans passer par le fichier de configuration. Ce nom est. Allons dans la vue [Servers] et essayons d'ajouter cette nouvelle application à celles gérées par Tomcat : 1 On voit qu'en [1]. <?xml version="1. Pour qu'il le voit. 7. 9. ici.0" encoding="UTF-8"?> <project-modules id="moduleCoreId"> <wb-module deploy-name="mvc-personne-01"> <wb-resource deploy-path="/" source-path="/WebContent"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src"/> <property name="java-output-path" value="/build/classes/"/> <property name="context-root" value="personne1"/> </wb-module> </project-modules> La ligne 3 désigne le nom du module web à déployer au sein de Tomcat. 8. ligne 7. le nouveau projet [mvc-personne-02] n'est pas vu par Tomcat. un fichier de configuration du projet [mvc-personne-02] doit être modifié.settings/. 4. le nom du contexte de l'application [mvc-personne-02] qui est en conflit avec celui du projet [mvc-personne-01] : <property name="context-root" value="personne2"/> Cette seconde modification pouvait être faite directement au sein d'Eclipse.Il est identique pour l'instant au projet [mvc-personne-01]. Nous le changeons en [mvc-personne-02] : <wb-module deploy-name="mvc-personne-02"> Par ailleurs.component] : 1. 2. Utilisons l'option [File / Open File] pour ouvrir le fichier [<mvc-personne02>/. le même que celui du projet [mvcpersonne-01]. par l'exemple 79/264 . 3. En revanche. 5.

4" xmlns="http://java.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.w3. <?xml version="1.jsp </param-value> </init-param> <init-param> <param-name>urlFormulaire</param-name> <param-value> /WEB-INF/vues/formulaire.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. 5. 33. 19.com/xml/ns/j2ee" xmlns:xsi="http://www. 7. 27.com/xml/ns/j2ee http://java.st. 16.ServletPersonne </servlet-class> <init-param> <param-name>urlReponse</param-name> <param-value> /WEB-INF/vues/reponse. Nous l'ajoutons aux projets configurés pour être exécutés par Tomcat : 6.personne.sun. 29.3 Configuration de l'application web [personne2] Le fichier web. 24.content] puis nous quittons et relançons Eclipse afin qu'il soit pris en compte. 21.jsp </param-value> </init-param> <init-param> <param-name>urlControleur</param-name> <param-value> Les bases du développement web MVC en Java.xml de l'application /personne2 est le suivant : 1. 3.xsd"> <display-name>mvc-personne-02</display-name> <!-. 32.sun. 2. 22.jsp </param-value> </init-param> <init-param> <param-name>urlErreurs</param-name> <param-value> /WEB-INF/vues/erreurs. 23. 9.com/xml/ns/j2ee/webapp_2_4. 4. 30. 25. 11.servlets.ServletPersonne --> <servlet> <servlet-name>personne</servlet-name> <servlet-class> istia. nous sauvegardons le nouveau fichier [. Une fois Eclipse relancé. 17. 8.sun. 6. le projet [mvc-personne-02] est bien vu. 10. 20. par l'exemple 80/264 . 28.Ceci fait. 15. essayons de faire l'opération qui a échoué précédemment : 1 Cette fois-ci. 31. 18. 26. 12. 14. 13.

2. String urlAction=(String)request. 8. <%@ page language="java" contentType="text/html.4. 6. 5. </init-param> 37.jsp] et [reponse.jsp] change : 1.getAttribute("urlAction"). <servlet-mapping> 46. <init-param> 38. 4. des pages JSP [erreurs. </param-value> 42. String age=(String)session. </welcome-file-list> 53. 5. 10. <!-. <param-name>lienRetourFormulaire</param-name> 39. <url-pattern>/main</url-pattern> 48. <!-.jsp</welcome-file> 52.4 6. <param-value> 40.jsp] suivante : 1.Mapping ServletPersonne--> 45.sendRedirect("/personne2/main"). main 35. <welcome-file>index. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.jsp] redirige le client vers l'url du contrôleur [ServletPersonne] de l'application [/personne2]. </param-value> 36. Ce fichier est identique à celui de la version précédente. </servlet> 44. <%@ page language="java" contentType="text/html.fichiers d'accueil --> 50.getAttribute("age"). <servlet-name>personne</servlet-name> 47.jsp]. %> • ligne 5 : la page [index. </web-app> 54. par l'exemple 81/264 . %> <html> Les bases du développement web MVC en Java. 9.01 Transitional//EN"> <% // on récupère les données du modèle String nom=(String)session. 6. 4. 7. <welcome-file-list> 51. 11. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. </servlet-mapping> 49. La page d'accueil [index. si ce n'est qu'il déclare deux nouveaux paramètres d'initialisation :    ligne 6 : le nom d'affichage de l'application web a changé en [mvc-personne-02] lignes 31-36 : définissent le paramètre de configuration nommé [urlControleur] qui est l'url [main] qui mène à la servlet [ServletPersonne] lignes 37-42 : définissent un paramètre de configuration nommé [lienRetourFormulaire] qui est le texte du lien de retour vers le formulaire. </init-param> 43. 3. Retour au formulaire 41.1 Le code des vues La vue [formulaire] Cette vue est identique à celle de la version précédente : Elle est générée par page JSP [formulaire. 6. 3.getAttribute("nom").34.01 Transitional//EN"> <% response. 2.

4. le contrôleur place dans la session les données saisies dans le formulaire. le formulaire a désormais un attribut [action] dont la valeur est l'Url à laquelle le navigateur devra poster les valeurs du formulaire lorsque l'utilisateur va cliquer sur le bouton [Envoyer] de type submit. la navigateur va utiliser la première partie de l'Url de la page actuellement affichée [/personne2] et y ajouter l'Url relative. age] du modèle. La variable [urlAction] aura la valeur action="main". <td><input type="button" value="Effacer"></td> 36.2 La vue [reponse] Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides : Les bases du développement web MVC en Java. <form action="<%=urlAction%>" method="post"> 20. Elle sera utilisée ligne 19.formulaire</h2> 18. </html> Les nouveautés : • ligne 19. <tr> 22. <body> 16. <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> 28. <table> 21. </form> 40. <title>Personne . lignes 6-7 : on récupère les valeurs des éléments [nom. </tr> 37. </table> 38. </center> 41. <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> 24. <tr> 33.12. ce qui lui permet de les retrouver lorsque l'utilisateur utilise le lien [Retour au formulaire] des vues [réponse] et [erreurs]. </tr> 25. Cette requête POST sera accompagnée des paramètres [txtNom. ligne 8 : on récupère la valeur de l'élément [urlAction] du modèle. <td><input type="submit" value="Envoyer"></td> 34. <input type="hidden" name="action" value="validationFormulaire"> 39. Cela pour répondre aux besoins de la requête [GET /personne2/main?action=retourFormulaire] du lien des vues [réponse] et [erreurs]. • • 6.formulaire</title> 14. <td>Age</td> 27. <tr> 26. txtAge. <head> 13. </tr> 29. <td><input type="reset" value="Rétablir"></td> 35. action] des lignes 23. <table> 32. <center> 17. par l'exemple 82/264 . Elle est cherchée dans les attributs de la requête courante. Ils sont cherchés dans les attributs de la session et non plus dans ceux de la requête comme dans la version précédente. 27 et 38. </head> 15. </table> 31. </body> 42. L'Url du POST sera donc [/personne2/main]. Avant d'afficher ces deux vues. <tr> 30. celle du contrôleur. <hr> 19. La vue [formulaire] est affichée après les actions suivantes de l'utilisateur : • demande initiale : GET /personne2/main • clic sur le lien [Retour au formulaire] : GET /personne2/main?action=retourFormulaire Comme l'attribut [action] ne précise pas d'Url absolue (commençant par /) mais une Url relative (ne commençant pas par /). <td>Nom</td> 23. <h2>Personne .

14. Celui-ci fera partie du modèle transmis à la page par le contrôleur et récupéré ligne 10. 11. 13. En l'absence d'Url dans [href].a. C'est donc cette dernière Url qui est affichée dans le navigateur lorsque la vue [réponse] est affichée. 32. c. %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne . 17. 22. 16. 27. 24. ici "?action=retourFormulaire".réponse</h2> <hr> <table> <tr> <td>Nom</td> <td><%= nom %> </tr> <tr> <td>Age</td> <td><%= age %> </tr> </table> <br> <a href="?action=retourFormulaire"><%= lienRetourFormulaire %></a> </body> </html> • ligne 31 : le lien de retour au formulaire.jsp] à l'url [/personne2/main].getAttribute("age"). La vue est générée par la page JSP [reponse. <%@ page language="java" contentType="text/html. le texte du lien. Un clic sur le lien [Retour au formulaire] va alors provoquer un Get du navigateur vers l'Url précisée par l'attribut [href] du lien. 8. 29. le navigateur va utiliser celle de la vue actuellement affichée. String age=(String)request.d. 26.getAttribute("lienRetourFormulaire"). le clic sur le lien [Retour au formulaire] va provoquer un Get du navigateur vers l'url [/personne2/main?action=retourFormulaire]. 3. 28. la nouveauté vient du lien [Retour au formulaire]. 25. 4. 10. 21. 20.a. 6.3 La vue [erreurs] Cette vue signale les erreurs de saisie dans le formulaire : Vis à vis de la version précédente. La vue [réponse] est affichée après le POST du formulaire [formulaire. 9. par l'exemple 83/264 .01 Transitional//EN"> <% // on récupère les données du modèle String nom=(String)request. Au final. 6. 19.jsp] suivante : 1. 18. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. la nouveauté vient du lien [Retour au formulaire]. 7.jsp] suivante : 1. 2. 15. String lienRetourFormulaire=(String)request.4. Ce lien a deux composantes : • • la cible [href="?action=retourFormulaire"].getAttribute("nom"). La vue est générée par la page JSP [erreurs. 23. 12.Vis à vis de la version précédente. 31.d l'url du contrôleur de l'application accompagnée du paramètre [action] pour lui indiquer ce qu'il doit faire. 33. 30. [/personne2/main]. c. 5. <%@ page language="java" contentType="text/html. charset=ISO-8859-1" Les bases du développement web MVC en Java.

%> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0. 8. 14.2. 9. [reponse.println("<li>" + (String) erreurs.setAttribute("urlAction". String lienRetourFormulaire=(String)request. 11. 22. 6.getAttribute("nom"). 14."main"). 27. 6. 4.5 Tests des vues Pour réaliser les tests des vues précédentes.setAttribute("age". 2. 13. pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 25. 7.getAttribute("lienRetourFormulaire"). 8. 5.test : on crée le modèle de la page session.jsp] : 1.get(i) + "</li>\n").ArrayList" %> <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. 9.jsp] : 1. Ce lien est identique à celui de la vue [réponse].. session.getAttribute("urlAction"). 24.setAttribute("nom".i++){ out. 12. 15. par l'exemple 84/264 . request.size(). Le lecteur est invité à relire éventuellement les explications données pour cette vue. <% // -. 19. 26. 16. . 5.01 Transitional//EN"> <%@ page import="java. Les bases du développement web MVC en Java."tintin"). }//for %> </ul> <br> <a href="?action=retourFormulaire"><%= lienRetourFormulaire %></a> </body> </html> • ligne 26 : le lien de retour au formulaire.. 20. 15. 11.util. 3. nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse : Puis dans le dossier JSP. 6. 3. 18. 28. 7. String urlAction=(String)request. 29. 4.getAttribute("erreurs"). 23. 13.i<erreurs. String age=(String)session.getAttribute("age")."30"). 12. 17. les pages sont modifiées de la façon suivante : [formulaire. %> Les lignes 4-5 ont été ajoutées pour créer le modèle dont a besoin la page lignes 11-13. 10. 10. 21. %> <% // on récupère les données du modèle String nom=(String)session.

17. 13. par l'exemple 85/264 . <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. 16.setAttribute("age"."10").erreurs1). String age=(String)request. <% // on récupère les données du modèle String nom=(String)request. 7. 15. 8."milou"). String lienRetourFormulaire=(String)request."Retour au formulaire"). 10. 11.getAttribute("lienRetourFormulaire").getAttribute("lienRetourFormulaire"). 3. 8. 4. Lançons Tomcat si ce n'est déjà fait puis demandons les url suivantes : Nous obtenons bien les vues attendues. <% %> // -.getAttribute("age"). erreurs1.setAttribute("nom". 12.setAttribute("erreurs".getAttribute("nom").setAttribute("lienRetourFormulaire". 13. 12. 14. request.setAttribute("lienRetourFormulaire". request.2. 14.add("erreur2"). String lienRetourFormulaire=(String)request. 5. 15. %> Les lignes 4-8 ont été ajoutées pour créer le modèle dont a besoin la page lignes 13-14. erreurs1. 7.test : on crée le modèle de la page request.add("erreur1"). <% %> // -. 6. 11. 6. 3. 9.jsp] : 1.getAttribute("erreurs")."Retour au formulaire"). 5. Les bases du développement web MVC en Java. [erreurs. 4. 2. request. 9. %> Les lignes 4-6 ont été ajoutées pour créer le modèle dont a besoin la page lignes 11-13. 16.test : on crée le modèle de la page ArrayList<String> erreurs1=new ArrayList<String>(). request. 10.

age)] Le contrôleur [ServletPersonne] de l'application web [/personne2] va traiter les actions suivantes : 3 [GET clic sur le lien [Retour au . . ServletException { ..1 1. 27. 12. void doRetourFormulaire(HttpServletRequest request.envoyer la vue [formulaire] pré-remplie avec les dernières valeurs saisies /personne2/main?action=retour formulaire] des vues Formulaire] [réponse] et [erreurs]. .http. 21.servlets. void doValidationFormulaire(HttpServletRequest request. . 9. envoyer la vue [erreurs(erreurs)] . // post 38. 20. txtAge] . 37. Squelette du contrôleur package istia. // affichage formulaire pré-rempli 28. 40. 23. 14. 2. IOException{ 29..servlet.6. Nous avons donc une nouvelle action à traiter : [GET /personne2/main?action=retourFormulaire]. 6. 19. 34. 25.. } 41. 6.. HttpServletResponse response) throws ServletException.envoyer la vue [formulaire] vide .personne.. 18. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { .. 15. HttpServletResponse response) throws ServletException. 13. 30.si elles sont incorrectes.vérifier les valeurs des paramètres [txtNom. 16. .HttpSession. } Les nouveautés : • • ligne 4 : l'usage d'une session impose d'importer le paquetage [HttpSession] lignes 28-30 : la nouvelle méthode [doRetourFormulaire] traite la nouvelle action : [GET /personne2/main?action=retourFormulaire]. 17. // validation du formulaire 33. import javax. 6. IOException{ 24... txtAge..st. 3.2 Initialisation du contrôleur [init] 86/264 Les bases du développement web MVC en Java. 5. action] postés origine url tapée par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement .. HttpServletResponse response) throws IOException. 32. } 36. par l'exemple .6 n° 1 2 Le contrôleur [ServletPersonne] demande [GET /personne2/main] [POST /personne2/main] avec paramètres [txtNom. 22.. } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request..si elles sont correctes. 35. } 26. HttpServletResponse response) 39.. 4. . public void doPost(HttpServletRequest request..6.. 7.6.. } Le squelette du contrôleur [ServletPersonne] est quasi identique à celui de la version précédente : // affichage formulaire vide void doInit(HttpServletRequest request. envoyer la vue [reponse(nom. 11. // init @SuppressWarnings("unchecked") public void init() throws ServletException { . 8. } 31. 10.

27. 17. 26. 20. 11. 5. // paramètres d'instance 3. s'il y a eu erreurs d'initialisation de l'application. 2.equals("post") && action.6.toLowerCase(). 35. } • lignes 6-14 : on vérifie que la liste des erreurs d'initialisation est vide. 30. 6.La méthode [init] est identique à celle de la version précédente.equals("validationFormulaire")){ // validation du formulaire de saisie doValidationFormulaire(request.public class ServletPersonne extends HttpServlet { 2. private Map params=new HashMap<String. • lignes 34-37 : traitement de la nouvelle action [GET /personne2/main?action=retourFormulaire] 87/264 Les bases du développement web MVC en Java. ""). return. 29.getRequestDispatcher(urlErreurs). Elle vérifie la présence dans le fichier [web. 6.getAttribute("lienRetourFormulaire"). 14. 4.getMethod(). 28. private String[] paramètres={"urlFormulaire". return. Il n'y a pas lieu de proposer à l'utilisateur de continuer l'application via un lien. 8. return.size() != 0) { // on passe la main à la page d'erreurs request. 36. 10. 3. } if(méthode.response).setAttribute("erreurs". par l'exemple . } if(méthode. // on récupère l'action à exécuter String action=request. 4.forward( request. @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request.response). 21. il faut se rappeler le modèle de la vue [erreurs] : 1. Le contrôleur crée cet élément ligne 8. 18.getAttribute("erreurs"). 23. 3. erreursInitialisation). 40. Il n'y aura donc pas de lien dans la vue [erreurs] envoyée. // action ? if(action==null){ action="init". %> La vue [erreurs] attend un élément de clé " erreurs " dans la requête.getParameter("action"). elle doit être reconfigurée.equals("get") && action. 13.equals("init")){ // démarrage application doInit(request. HttpServletResponse response) throws IOException.xml] des éléments déclarés dans le tableau [paramètres] : 1.String>(). Ici le texte du lien sera vide. Si ce n'est pas le cas. • ligne 5 : les paramètres [urlControleur] (Url du contrôleur) et [lienRetourFormulaire] (Texte du lien des vues [réponse] et [erreurs] ont été rajoutés. response). 7.response). 31. 19.equals("retourFormulaire")){ // retour au formulaire de saisie doRetourFormulaire(request. En effet. 16. } // on récupère la méthode d'envoi de la requête String méthode=request. } // autres cas doInit(request."urlReponse". 9. private ArrayList erreursInitialisation = new ArrayList<String>(). Pour comprendre ce code.setAttribute("lienRetourFormulaire". <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. 15. 24. Le contrôleur crée cet élément ligne 9. 22. 5.response). 34. 41. ServletException { // on vérifie comment s'est passée l'initialisation de la servlet if (erreursInitialisation."urlControleur". private String urlErreurs = null. 6. Elle attend également un élément de clé "lienRetourFormulaire". // fin return. 25. 38.3 La méthode [doGet] La méthode [doGet] doit traiter l'action [GET /personne2/main?action=retourFormulaire] qui n'existait pas auparavant : 1. request. String lienRetourFormulaire=(String)request. 39. getServletContext().equals("get") && action."lienRetourFormulaire"}. 32. 2. 33. on fait afficher la vue [erreurs(erreursInitialisation)] qui va signaler la ou les erreurs. 37. 5. 4. } // exécution action if(méthode. 12.

response). age).forward(request. request. 33. <% // on récupère les données du modèle String nom=(String)session. return. 8.5 La méthode [doValidationFormulaire] Cette méthode traite la requête n° 2 [POST /personne2/main] dans laquelle les paramètres postés sont [action. // le nom doit être non vide nom = nom. 11. "").setAttribute("age". 26. 3. getServletContext(). 5. lignes 9-10 : la vue [formulaire] est affichée. String urlAction=(String)request. request. 31. 6. String age=(String)session.add("Le champ [age] est erroné"). 22. 7. // on envoie le formulaire vide 6. // validation du formulaire void doValidationFormulaire(HttpServletRequest request. txtNom. if (nom. 8.4 La méthode [doInit] Cette méthode traite la requête n° 1 [GET /personne2/main].matches("^\\s*\\d+\\s*$")) erreursAppel.getRequestDispatcher((String)params. 24. 7.equals("")) erreursAppel. 2. (String)params. 30. 36.getAttribute("age"). elle doit envoyer la vue [formulaire(nom. 11. 21.setAttribute("lienRetourFormulaire". } // les paramètres sont corrects . session.6. response).getSession(true). Son code est le suivant : 1. IOException{ // on récupère les paramètres String nom = request. // l'âge doit être un entier >=0 if (!age. 12.setAttribute("nom". String age = request. HttpServletResponse response) throws ServletException. 18.getRequestDispatcher(urlErreurs). 2.setAttribute("age". 10. 3.setAttribute("lienRetourFormulaire".getAttribute("nom"). 17. request. request.setAttribute("erreurs".get("urlFormulaire")). 2. 15. %> • • lignes 6-7 : les éléments [nom. // affichage formulaire vide void doInit(HttpServletRequest request.trim(). response). 6.getAttribute("urlAction"). 16. } • • ligne 4 : la session courante est récupérée si elle existe. request.get("lienRetourFormulaire")). 34. IOException{ 3. // on récupère la session de l'utilisateur 4. session. 14.setAttribute("urlAction".getRequestDispatcher((String)params. 19.get("urlReponse")). 12.get("urlControleur")). HttpServletResponse response) throws ServletException. session.getSession(true). (String)params. } Les bases du développement web MVC en Java. Son code est le suivant : 1. créée sinon (paramètre true de getSession).age)] vide. txtAge].forward( 10. (String)params.forward( request. HttpSession session = request. // vérification des paramètres ArrayList<String> erreursAppel = new ArrayList<String>().6.setAttribute("nom". 4. // des erreurs dans les paramètres ? if (erreursAppel. par l'exemple 88/264 . 5. 5. 20. getServletContext().size() != 0) { // on envoie la page d'erreurs request. ligne 8 : l'élément [urlAction] du modèle est initialisé avec la valeur du paramètre [urlControleur] du fichier [web. nom). 25. return. 4. 23. 35. age). "").setAttribute("nom". 32. 28. 29. 9.xml] et placé dans la requête.getParameter("txtAge"). return.get("lienRetourFormulaire")).6. getServletContext(). Sur cette requête.add("Le champ [nom] n'a pas été rempli"). 6.getParameter("txtNom").on envoie la page réponse request.setAttribute("age". Rappelons le modèle attendu par cette vue : 1. erreursAppel). 9. 13. nom).age] du modèle de la vue [formulaire] sont initialisés avec des chaînes vides et placés dans la session car c'est là que les attend la vue. // qu'on mémorise dans la session HttpSession session = request. session. 27.

• • • •

lignes 5-6 : on récupère dans la requête du client les valeurs des paramètres "txtNom" et "txtAge". lignes 8-10 : on mémorise ces valeurs dans la session pour pouvoir les retrouver lorsque l'utilisateur va cliquer sur le lien [Retour au formulaire] des vues [réponse] et [erreurs]. lignes 12-19 : la validité des valeurs des deux paramètres est vérifiée lignes 21-28 : si l'un des paramètres est erroné, on fait afficher la vue [erreurs(erreurs,lienRetourFormulaire)]. Rappelons le modèle de cette vue :
1. 2. 3. 4. 5. <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

lignes 30-34 : si les deux paramètres " txtNom " et " txtAge " récupérés ont des valeurs valides, on fait afficher la vue [reponse(nom,age,lienRetourFormulaire)]. Il faut se rappeler le modèle de la vue [reponse] :
1. 2. 3. 4. 5. 6. <% // on récupère les données du modèle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

6.6.6

La méthode [doRetourFormulaire]

Cette méthode traite la requête n° 3 [GET /personne2/main?action=retourFormulaire]. Son code est le suivant :
1. // affichage formulaire pré-rempli 2. void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 3. // on récupère la session de l'utilisateur 4. HttpSession session = request.getSession(true); 5. // on prépare le modèle du formulaire 6. // nom présent dans la session ? 7. String nom = (String) session.getAttribute("nom"); 8. if (nom == null) 9. session.setAttribute("nom", ""); 10. // âge présent dans la session ? 11. String age = (String) session.getAttribute("age"); 12. if (age == null) 13. session.setAttribute("age", ""); 14. // urlAction 15. request.setAttribute("urlAction", (String)params.get("urlControleur")); 16. // on affiche le formulaire 17. getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( 18. request, response); 19. return; 20. }

A l'issue de cette méthode, on doit afficher la vue [formulaire] pré-remplie avec les dernières saisies faites par l'utilisateur. Rappelons le modèle de la vue [formulaire] :
1. 2. 3. 4. 5. 6. <% // on récupère les données du modèle String nom=(String)session.getAttribute("nom"); String age=(String)session.getAttribute("age"); String urlAction=(String)request.getAttribute("urlAction"); %>

La méthode [doRetourFormulaire] doit donc construire le modèle précédent.
• • •

• • •

ligne 4 : on récupère la session dans laquelle le contrôleur a mémorisé les valeurs (nom, age) saisies. ligne 7 : on récupère le nom dans la session lignes 8-9: s'il n'y est pas, on l'y met avec une valeur vide. Ce cas ne devrait pas se produire dans le fonctionnement normal de l'application, l'action [retourFormulaire] ayant toujours lieu après l'action [validationFormulaire] dont après la mise en session des données saisies. Mais une session peut expirer car elle a une durée de vie limitée, souvent quelques dizaines de minutes. Dans ce cas, la ligne 4 a recréé une nouvelle session dans laquelle on ne trouvera pas le nom. On met alors un nom vide dans la nouvelle session. lignes 11-13 : on fait de même pour l'âge si on ignore le problème de la session expirée, alors les lignes 3-13 sont inutiles. Les éléments [nom,age] du modèle sont déjà dans la session. Il n'y a donc pas à les y remettre. ligne 15 : on fixe la valeur de l'élément [urlAction] du modèle 89/264

Les bases du développement web MVC en Java, par l'exemple

6.7

Tests

Lancer ou relancer Tomcat. Demander l'url [http://localhost:8080/personne2] puis reprendre les tests montrés en exemple au paragraphe 6.1, page 76.

7 Application web MVC [personne] – version 3
Lectures conseillées dans [ref1] : chapitre 9

7.1

Introduction

Nous nous proposons d'ajouter du code Javascript dans les pages HTML envoyées au navigateur. C'est ce dernier qui exécute le code Javascript embarqué dans la page qu'il affiche. Cette technologie est indépendante de celle utilisée par le serveur web pour générer le document HTML (Java/servlets/JSP, ASP.NET, ASP, PHP, Perl, Python, ...). [formulaire.jsp] La vue issue de cette page aura l'allure suivante :

Les boutons dont le libellé est encadré font appel à du code Javascript embarqué dans la page HTML : Libellé
Submit [Envoyer] Rétablir [Effacer]

Type HTML <submit> <button> <reset> <button>

Fonction joue le rôle du bouton [Envoyer] des versions précédentes : poste au contrôleur les valeurs saisies nouveau bouton – vérifie localement la validité des données saisies avant de les poster au contrôleur rétablit le formulaire dans l'état où il a été reçu initialement par le navigateur efface le contenu des deux champs de saisie

Voici un exemple d'utilisation des boutons [Envoyer] et [Effacer] :

Les bases du développement web MVC en Java, par l'exemple

90/264

Nous utiliserons également du code Javascript pour gérer le lien [Retour au formulaire] des vues [erreurs] et [réponse]. Prenons l'exemple de la vue [réponse] :

1

La différence est dans l'url affichée en [1]. Dans la version précédente, celle-ci était :
[http://localhost:8080/personne2/main?action=retourFormulaire]

Ici, l'action ne fait plus partie de l'Url car elle va être envoyée par un POST au lieu d'un GET. Cette modification va avoir pour effet que l'unique Url affichée par le navigateur sera [http://localhost:8080/personne3/main] quelque soit l'action demandée.

7.2

Le projet Eclipse

Pour créer le projet Eclipse [mvc-personne-03] de l'application web [/personne3], on dupliquera le projet [mvc-personne-02] en suivant la procédure décrite au paragraphe 6.2, page 78.

Les bases du développement web MVC en Java, par l'exemple

91/264

7.3
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. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48.

Configuration de l'application web [personne3]
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/webapp_2_4.xsd"> <display-name>mvc-personne-03</display-name> <!-- ServletPersonne --> <servlet> <servlet-name>personne</servlet-name> <servlet-class> istia.st.servlets.personne.ServletPersonne </servlet-class> <init-param> <param-name>urlReponse</param-name> <param-value> /WEB-INF/vues/reponse.jsp </param-value> </init-param> <init-param> <param-name>urlErreurs</param-name> <param-value> /WEB-INF/vues/erreurs.jsp </param-value> </init-param> <init-param> <param-name>urlFormulaire</param-name> <param-value> /WEB-INF/vues/formulaire.jsp </param-value> </init-param> <init-param> <param-name>lienRetourFormulaire</param-name> <param-value> Retour au formulaire </param-value> </init-param> </servlet> <!-- Mapping ServletPersonne--> <servlet-mapping> <servlet-name>personne</servlet-name> <url-pattern>/main</url-pattern> </servlet-mapping> <!-- fichiers d'accueil --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

Le fichier web.xml de l'application /personne3 est le suivant :

Ce fichier est identique à celui de la version précédente hormis quelques détails :  ligne 6 : le nom d'affichage de l'application web a changé en [mvc-personne-03] 92/264

Les bases du développement web MVC en Java, par l'exemple

Le paramètre [urlControleur] a disparu. Dans la version précédente, il servait à fixer la cible du POST de la vue [formulaire]. Dans cette nouvelle version, la cible sera la chaîne vide, c.a.d. l'Url affichée par le navigateur. Nous avons expliqué que celle-ci serait toujours [http://localhost:8080/personne3/main], celle qu'il nous faut pour le POST de la vue [formulaire]. La page d'accueil [index.jsp] change :
1. 2. 3. 4. 5. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <% response.sendRedirect("/personne3/main");

6. %>

ligne 5 : la page [index.jsp] redirige le client vers l'url du contrôleur [ServletPersonne] de l'application [/personne3].

7.4
7.4.1

Le code des vues
La vue [formulaire]

Cette vue est devenue la suivante :

Elle est générée par page JSP [formulaire.jsp] suivante :
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. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%// on récupère les données du modèle String nom = (String) session.getAttribute("nom"); String age = (String) session.getAttribute("age"); %> <html> <head> <title>Personne - formulaire</title> <script language="javascript"> // ------------------------------function effacer(){ // on efface les champs de saisie with(document.frmPersonne){ txtNom.value=""; txtAge.value=""; }//with }//effacer // ------------------------------function envoyer(){ // vérification des paramètres avant de les envoyer with(document.frmPersonne){ // le nom ne doit pas être vide champs=/^\s*$/.exec(txtNom.value); if(champs!=null){ // le nom est vide alert("Vous devez indiquer un nom"); txtNom.value=""; txtNom.focus(); // retour à l'interface visuelle return; }//if // l'âge doit être un entier positif

Les bases du développement web MVC en Java, par l'exemple

93/264

<h2>Personne . lignes 24-49 : la fonction Javascript [envoyer] vérifie la validité des valeurs des champs de saisie [txtNom. Elle utilise pour cela des expressions régulières. Ainsi [document. <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> 61. <tr> 59. ligne 29 : s'il y a correspondance avec le modèle ligne 30 : on affiche un message à l'utilisateur ligne 32 : on met la chaîne vide dans le champ [txtNom] (il pouvait y avoir une suite d'espaces) ligne 33 : on positionne le curseur clignotant sur le champ [txtNom] 94/264 • • • • Les bases du développement web MVC en Java. • • • lignes 16-22 : la fonction Javascript [effacer] met une chaîne vide dans les champs de saisie [txtNom. [document. <table> 58.value] désigne le contenu du champ de saisie [txtNom]. Dans ce document. </table> 76. L'objet [txtNom] a diverses propriétés dont la propriété [value] qui désigne le contenu du champ de saisie. </table> 68. <td>Nom</td> 60.txtNom] désigne l'objet image du champ de saisie HTML défini ligne 60. </html> 81. // l'âge est incorrect 41. <hr> 56. <tr> 63.38. }//envoyer 50. ligne 70 : le bouton [Submit] joue le rôle du bouton [Envoyer] des versions précédentes ligne 71 : un clic sur le bouton [Envoyer] de type [Button] fait exécuter la fonction Javascript [envoyer] définie ligne 24 ligne 72 : le bouton [Rétablir] n'a pas changé de fonction ligne 73 : un clic sur le bouton [Effacer] de type [Button] fait exécuter la fonction Javascript [effacer] définie ligne 16 Avant de commenter le code Javascript.frmPersonne. </center> 79. la variable champs aura une valeur différente du pointeur null.exec(txtAge.focus(). Ce formulaire contient lui aussi des objets. <td><input type="reset" value="Rétablir"></td> 73. </head> 52. </tr> 66.value). txtAge] avant de les poster. <td>Age</td> 64. <body> 53. champs=/^\s*\d+\s*$/. ligne 28 : vérifie si la valeur du champ de saisie [txtNom] correspond au modèle /s* qui signifie 0 ou davantage d'espaces. </script> 51. 45. 82. </body> 80. sinon elle aura la valeur null. </tr> 75. </tr> 62. // retour à l'interface visuelle 44. <input type="hidden" name="action" value="validationFormulaire"> 77. }//if 46.on les envoie au serveur 47.txtNom. // les paramètres sont corrects . Les champs de saisie en font partie. <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> 65. Ce nom va être utilisé dans le code Javascript. return. <table> 69. Ainsi [document. submit(). <tr> 67. par l'exemple . <td><input type="button" value="[Effacer]" onclick="effacer()"></td> 74. <td><input type="button" value="[Envoyer]" onclick="envoyer()"></td> 72. }//with 49. Si la réponse est oui. il peut y avoir un ou plusieurs formulaires.formulaire</h2> 55. rappelons quelques notations : Javascript gère la page affichée et son contenu comme un arbre d'objets dont la racine est l'objet [document]. txtAge. alors cela signifie que l'utilisateur n'a pas indiqué de nom. <td><input type="submit" value="Submit"></td> 71. <tr> 70. <center> 54. <form name="frmPersonne" method="post"> 57.frmPersonne. alert("Age incorrect"). if(champs==null){ 40. 43. 48. 39. On notera l'absence de l'attribut [action] qui fait que le formulaire [frmPersonne] sera posté à l'Url affichée par le navigateur. S'il y a correspondance avec le modèle. </form> 78. txtAge].frmPersonne] désigne le formulaire de nom [frmPersonne] défini ligne 56. 42. Les nouveautés : • • • • • ligne 56 : le formulaire a un nom [frmPersonne].

lignes 38-45 : on a une démarche analogue avec le champ [txtAge] ligne 47 : si on arrive là. 11. Tout se passe alors comme si on avait appuyé sur le bouton libellé [Submit]. 4.getAttribute("nom").getAttribute("age").getAttribute("lienRetourFormulaire"). 15. 12. 3. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 7.2 La vue [reponse] Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides : Vis à vis de la version précédente. %> <html> <head> <title>Personne</title> </head> Les bases du développement web MVC en Java. 6. 17. celle-ci était : [http://localhost:8080/personne2/main?action=retourFormulaire] Ici.01 Transitional//EN"> <% // on récupère les données du modèle String nom=(String)request. l'action ne fait plus partie de l'Url car elle va être envoyée par un POST au lieu d'un GET. 14. Dans la version précédente. 10.jsp] est lqqqaaaaaaaaaqqAAAaaa suivante : 1.• • • ligne 34 : on interrompt la fonction. 2. c'est que les valeurs saisies sont correctes. 13. 7. 8. Il n'y a alors pas de POST au serveur des valeurs saisies. par l'exemple 95/264 . 16.4. 5. 9. On poste (submit) alors le formulaire [frmPersonne] au serveur web. La nouvelle page JSP [reponse. la nouveauté n'apparaît que lorsqu'on utilise le lien [Retour au formulaire] de la vue [réponse] : 1 La différence est dans l'url affichée en [1]. String lienRetourFormulaire=(String)request. String age=(String)request. <%@ page language="java" contentType="text/html.

<table> 22. <head> 15. par l'exemple 96/264 . <input type="hidden" name="action" value="retourFormulaire"> 34. <br> 32. provoque l'exécution du code Javascript de l'attribut [href]. <hr> 21.getAttribute("lienRetourFormulaire"). Un clic sur ce lien. <h2>Personne . du formulaire [frmPersonne]. <td>Age</td> 28. Comme nous l'avons vu dans l'étude de [formulaire. <a href="javascript:document. String lienRetourFormulaire=(String)request.getAttribute("erreurs"). La modification apportée est la même que pour la vue [réponse] : 1 La différence est dans l'url affichée en [1].. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 10. <select>.01 Transitional//EN"> 4.>.3 La vue [erreurs] Cette vue signale les erreurs de saisie dans le formulaire. ArrayList erreurs=(ArrayList)request.util. <td><%= age %> 29. %> 11. <% 7.ArrayList" %> 5."> 36. l'action ne fait plus partie de l'Url car elle va être envoyée par un POST au lieu d'un GET.4. Dans la version précédente. nous savons que ce code poste les valeurs des champs de type <input>. <tr> 23. donc un champ caché.18.submit(). <td><%= nom %> 25. </form> 35. <%= lienRetourFormulaire %> 37. </tr> 26. 13. <%@ page import="java. <form name="frmPersonne" method="post"> 33. .. <td>Nom</td> 24. </table> 31.frmPersonne. </tr> 30. <%@ page language="java" contentType="text/html. </html> 40.jsp].réponse</h2> 20. La nouvelle page JSP [reponse. Les nouveautés : • • lignes 35-37 : le lien [Retour au formulaire] embarque du Javascript. <tr> 27. Celui-ci n'a qu'un champ de type <input type= "hidden " . celle-ci était : [http://localhost:8080/personne2/main?action=retourFormulaire] Ici. pageEncoding="ISO-8859-1"%> 3.. // on récupère les données du modèle 8. <html> 14. 12. charset=ISO-8859-1" 2. lignes 32-34 : définissent le formulaire [frmPersonne]. 7..jsp] suivante : 1. <title>Personne</title> Les bases du développement web MVC en Java. Ce champ [action] sert à transmettre au contrôleur le nom de l'action à exécuter. </a> 38. 9. </body> 39. 6. <body> 19. ici [retourFormulaire].

%> 25. 5. 4. 5. <% // -..setAttribute("nom". }//for 24. 10..setAttribute("nom"."> 31. 3.5 Tests des vues Pour réaliser les tests des vues précédentes. 9.i<erreurs. 23.16. les pages sont modifiées de la façon suivante : [formulaire. .test : on crée le modèle de la page session. 6. <ul> 20. <a href="javascript:document."30"). 4.size(). 8.setAttribute("age". 7. String age = (String) session. <%= lienRetourFormulaire %> 32.jsp] sont valables également ici. <input type="hidden" name="action" value="retourFormulaire"> 29.submit().frmPersonne. request.getAttribute("age"). <% // -. Les bases du développement web MVC en Java.getAttribute("nom"). <form name="frmPersonne" method="post"> 28. nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse : Puis dans le dossier JSP.get(i) + "</li>\n"). 6. 7. 11. Les explications données à ce sujet dans l'étude de [réponse. </ul> 26. </form> 30. out. for(int i=0. 7. 9.setAttribute("lienRetourFormulaire".println("<li>" + (String) erreurs. </head> 17. </body> 34. <body> 18.jsp] : 1. 8. session.test : on crée le modèle de la page request. </a> 33. 2."Retour au formulaire"). 10. 2. %> Les lignes 4-5 ont été ajoutées pour créer le modèle dont a besoin la page lignes 9-10. request.."tintin")."10"). 3. par l'exemple 97/264 . 11.. .jsp] : 1. %> <% // on récupère les données du modèle String nom=(String)request.setAttribute("age"."milou"). </html> Les nouveautés : • lignes 30-32 : la nouvelle gestion du clic sur le lien.i++){ 22. [reponse. <h2>Les erreurs suivantes se sont produites</h2> 19. <br> 27. <% 21. %> <%// on récupère les données du modèle String nom = (String) session.getAttribute("nom").

12.12. 15. 4. [erreurs. 5. par l'exemple 98/264 .getAttribute("lienRetourFormulaire"). 13.add("erreur2"). 3. 14. 9. 13. 7.6 Le contrôleur [ServletPersonne] Le contrôleur [ServletPersonne] de l'application web [/personne3] va traiter les actions suivantes : Les bases du développement web MVC en Java. String lienRetourFormulaire=(String)request. 2.add("erreur1"). 14.getAttribute("erreurs"). erreurs1. 10. request. <% %> // -.setAttribute("erreurs". String age=(String)request. %> 15.erreurs1). <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. 8.. %> Les lignes 4-8 ont été ajoutées pour créer le modèle dont a besoin la page lignes 13-14. 7. 6.setAttribute("lienRetourFormulaire". Les lignes 4-6 ont été ajoutées pour créer le modèle dont a besoin la page lignes 11-13..test : on crée le modèle de la page ArrayList<String> erreurs1=new ArrayList<String>().getAttribute("lienRetourFormulaire"). String lienRetourFormulaire=(String)request. Lançon Tomcat si ce n'est déjà fait puis demandons les url suivantes : Nous obtenons bien les vues attendues. . request. 11.jsp] : 1. erreurs1.getAttribute("age")."Retour au formulaire").

. à la place de l'ancienne action [GET /personne3/main?action=retourFormulaire].http. .si elles sont incorrectes. envoyer la vue [reponse(nom. envoyer la vue [erreurs(erreurs)] . 35.. 18.servlet. private Map params=new HashMap<String. // affichage formulaire pré-rempli 33. 21. 16. 37. } // affichage formulaire vide void doInit(HttpServletRequest request. 19. 30. 17. 42. 1. HttpServletResponse response) throws ServletException..n° 1 2 demande [GET /personne3/main] [POST /personne3/main] avec paramètres [txtNom. private ArrayList erreursInitialisation = new ArrayList<String>(). action=validationFormulaire] postés [POST /personne3/main] avec paramètres [action=retourFormulaire] postés origine url tapée par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement . 4. HttpServletResponse response) 44. HttpServletResponse response) throws ServletException. 15. 27. 45.servlets. 28. // init @SuppressWarnings("unchecked") public void init() throws ServletException { . } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request.. . ServletException { . } 46.6.age)] 3 clic sur le lien [Retour au . 26.HttpSession. // validation du formulaire 38. 22. txtAge] . doInit.. 39.envoyer la vue [formulaire] vide . Les méthodes [doGet. } 41. 5.vérifier les valeurs des paramètres [txtNom. 3. void doValidationFormulaire(HttpServletRequest request."lienRetourFormulaire"}. public void doPost(HttpServletRequest request. Nous avons donc une nouvelle action à traiter : [POST /personne3/main] avec paramètre posté [action=retourFormulaire].. import javax... 12.. 24.. .1 Squelette du contrôleur Le squelette du contrôleur [ServletPersonne] est identique à celui de la version précédente. package istia. 32. Les bases du développement web MVC en Java. // post 43.. doValidationFormulaire] restent ce qu'elles étaient. 14. . 23. IOException{ 29. 7. private String[] paramètres={"urlFormulaire". . 25. } 36. IOException{ 34. txtAge. 9. . doRetourFormulaire] changent légèrement.. 2.. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramètres d'instance private String urlErreurs = null.String>().personne. 20. 40. HttpServletResponse response) throws IOException."urlReponse".envoyer la vue [formulaire] pré-remplie avec les dernières valeurs saisies formulaire] des vues [réponse] et [erreurs]. void doRetourFormulaire(HttpServletRequest request. 7. Les méthodes [init.si elles sont correctes.. le tableau [paramètres] ne contient plus le paramètre [urlControleur] qui a été supprimé du fichier [web. par l'exemple 99/264 .. 13..st. 8. 11. } 31. 6.xml]. } Les nouveautés : • ligne 11. 10.

4. 3. 15. } 11. // on affiche le formulaire getServletContext(). HttpServletResponse response) 3.2 La méthode [doGet] La méthode [doGet] doit traiter l'action [POST /personne3/main] avec le paramètre posté [action=retourFormulaire] : 1.getAttribute("age"). [http://localhost:8080/personne3] puis reprendre les tests montrés en exemple au paragraphe 7. par l'exemple 100/264 . Son code est le suivant : 1. request. 7.1. // affichage formulaire pré-rempli void doRetourFormulaire(HttpServletRequest request. // nom présent dans la session ? String nom = (String) session. IOException{ 3. 10.get("urlFormulaire")). 10. response).. ServletException { 4.3 La méthode [doInit] Cette méthode traite la requête n° 1 [GET /personne3/main]. 11.setAttribute("age". 9. 6. return..equals("post") && action. ""). 7. Son code est le suivant : 1.getSession(true). // on envoie le formulaire vide 6.forward( request. } • lignes 6-9 : traitement de l'action [POST /personne3/main] avec le paramètre posté [action=retourFormulaire] 7. 2. // affichage formulaire vide void doInit(HttpServletRequest request. response). 5. 9. 14.equals("retourFormulaire")){ 7. page 90. session. 5.response). // âge présent dans la session ? String age = (String) session.get("urlFormulaire")).6.setAttribute("nom". 6. doRetourFormulaire(request.forward( 9. getServletContext(). IOException{ // on récupère la session de l'utilisateur HttpSession session = request. // retour au formulaire de saisie 8. return. throws IOException.response).setAttribute("nom". 8. 17. 10.getSession(true). 8. if(méthode. 12.7 Tests Demander l'url Lancer ou relancer Tomcat après y avoir intégré le projet Eclipse [personne-mvc-03]. doInit(request. return.6. HttpServletResponse response) throws ServletException.getRequestDispatcher((String)params. HttpSession session = request. 16.4 La méthode [doRetourFormulaire] Cette méthode traite la requête n° 1 [POST /personne3/main] avec [action=retourFormulaire] dans les éléments postés. ""). Les bases du développement web MVC en Java. .getRequestDispatcher((String)params. } La nouveauté est que la vue [formulaire] affichée ligne 14 n'a plus l'élément [action] dans son modèle. 2. public void doGet(HttpServletRequest request.7. } La nouveauté est que la vue [formulaire] affichée ligne 8 n'a plus l'élément [action] dans son modèle. ""). if (age == null) session. HttpServletResponse response) throws ServletException.6. if (nom == null) session. 7. ""). // on récupère la session de l'utilisateur 4.setAttribute("age".getAttribute("nom"). session. // autres cas 12. 13. @SuppressWarnings("unchecked") 2. 11. 13. 7. 5.

13. 30.01 Transitional//EN"> <%@ page import="java.frmPersonne. 10.i++){ out. 3. String lienRetourFormulaire=(String)request. 15. <%@ page language="java" contentType="text/html. 32. 16. 7. 5. 13.getAttribute("lienRetourFormulaire"). 9. Pour éviter ce mélange.println("<li>" + (String) erreurs. Une première solution est d'utiliser du code Java comme il a été fait : 1.ArrayList" %> <% // on récupère les données du modèle ArrayList erreurs=(ArrayList)request. }//for %> </ul> <br> <form name="frmPersonne" method="post"> <input type="hidden" name="action" value="retourFormulaire"> </form> <a href="javascript:document. 17. 27. 5. 2.submit(). 12. 14. 29. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 15. 7. 4. 21.tld" prefix="c" %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach> </ul> Les bases du développement web MVC en Java. 26. 10. on utilise des bibliothèques de balises qui vont apporter des possibilités nouvelles aux pages JSP. 11. par l'exemple 101/264 . 20. 14."> <%= lienRetourFormulaire %> </a> </body> </html> La page JSP récupère la liste des erreurs dans la requête (ligne 8) et l'affiche à l'aide d'une boucle Java (lignes 19-23).getAttribute("erreurs"). <%@ page language="java" contentType="text/html.8 La bibliothèque de balises JSTL 8. 25.size().1. 22. 34.i<erreurs. 6. %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0. 12. 3. 16. 19. 18. 2. 6. Nous ne nous intéressons ici qu'à la partie affichage des erreurs.1 Introduction Considérons la vue [erreurs. 23. La page mélange code HTML et code Java. 33. 31.util. 11. Avec la bibliothèque de balises JSTL (Java Standard Tag Library). 8. 24.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c. ce qui peut être problématique si la page doit être maintenue par un infographiste qui en général ne comprendra pas le code Java. 8. 9. 28. la vue précédente devient la suivante : 1.jsp] qui affiche une liste d'erreurs : Il y a plusieurs façons d'écrire une telle page. 4.get(i) + "</li>\n").

ArrayList. 4. <input type="hidden" name="action" value="retourFormulaire"> 20. Les préfixes permettent d'utiliser des bibliothèques de balises qui pourraient avoir les mêmes noms pour certaines balises...items="${erreurs}" indique la collection d'objets sur laquelle il faut itérer.getAttribute("erreurs") va rendre le ArrayList placé dans la requête par le contrôleur ."> 22. JSTL utilise la méthode erreur.prix}"/></td> <td><a href="<c:url value="?action=infos&id=${article. 2. A la place de la notation ${erreur}. Ici. Les objets de la collection traitée par la balise <forEach> peuvent être plus complexes que de simples chaînes de caractères. </html> La balise (ligne 4) <%@ taglib uri="/WEB-INF/c. La boucle <forEach> va être exécutée successivement pour chaque élément de la collection items.frmPersonne. Cette variable n'est pas nécessairement une chaîne de caractères. </a> 24. <form name="frmPersonne" method="post"> 19. . dans le flux HTML de la page.le contrôleur mettra dans la requête transmise à la page JSP un ArrayList de messages d'erreurs. Ici c signifie [core]. La nouvelle page n'a plus de code Java aux deux endroits où elle en avait précédemment : • • la récupération du modèle de la page [erreurs. Où celui-ci est-il trouvé ? La page JSP va chercher un attribut s'appelant "erreurs" successivement et dans l'ordre dans : o l'objet [request] qui représente la requête transmise par le contrôleur : request. objet implémentant l'interface List.nom}"/></td> <td><c:out value="${article. ici un message d'erreur.tld]. donc un ArrayList d'objets String : request.submit(). </body> 25. 6.id}"/>">Infos</a></td> </tr> Les bases du développement web MVC en Java. Pour en revenir à notre exemple d'affichage des erreurs : .getAttribute("erreurs") La collection désignée par l'attribut items peut avoir diverses formes : tableau. .à cause de l'attribut items="${erreurs}". ${lienRetourFormulaire} 23. - La notation ${erreur} insère la valeur de la variable erreur dans le texte. on peut également utiliser la balise <c:out value="${erreur}"/>.getAttribute("erreurs") o l'objet [session] qui représente la session du client : session.toString() pour insérer la valeur de la variable erreur. On peut utiliser tout préfixe de son choix. 5. La méthode erreur. lienRetourFormulaire] (partie disparue) l'affichage de la liste des erreurs (lignes 13-15) La boucle d'affichage des erreurs a été remplacée par code suivant : <c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach> - la balise <forEach> sert à délimiter une boucle la notation ${variable} sert à écrire la valeur d'une variable La balise <forEach> a ici deux attributs : . préfixées du mot c (prefix="c").tld" prefix="c" %> signale l'utilisation d'une bibliothèque de balises dont la définition se trouve dans le fichier [/WEB-INF/c. la page JSP va chercher un attribut appelé erreurs. <br> 18. </form> 21.la variable erreur de l'attribut var="erreur" va donc désigner l'élément courant du ArrayList. l'application. Prenons l'exemple d'une page JSP qui affiche une liste d'articles : 1. .17.getAttribute("erreurs") o l'objet [application] qui représente le contexte de l'application web : application. la collection est l'objet erreurs.setAttribute("erreurs". Elle va le trouver dans la requête : request.erreurs) où erreurs est le ArrayList . 3. var="erreur" sert à donner un nom à l'élément courant de la collection en cours de traitement. donc un objet String.toString() va insérer la valeur de ce String. l'élément de la collection en cours de traitement sera donc désigné ici par erreur. Ces balises seront utilisées dans le code de la page. <a href="javascript:document. <c:forEach var="article" items="${listarticles}"> <tr> <td><c:out value="${article. successivement dans la requête. par l'exemple 102/264 . L'utilisation du préfixe lève l'ambiguïté. A l'intérieur de la boucle. la session.

} public String getPrénom(){ return prénom. Pour les découvrir.2 Installer et découvrir la bibliothèque JSTL Les explications données précédemment suffiront pour l'application qui nous intéresse mais la bibliothèque de balises JSTL offre d'autres balises que celles présentées. on peut installer un tutoriel fourni dans le paquetage de la bibliothèque. </c:forEach> où [listarticles] est un ArrayList d'objets de type [Article] qu'on suppose être un Javabean avec les champs [id. L'objet courant article (var="article") désigne donc un objet de type [Article].getNom() . 8.apache. stockActuel. stockMinimum].7.getEnfants(1).1 du projet [Jakarta Taglibs] disponible à l'Url [http://jakarta. par l'exemple 103/264 .} } Pour obtenir la valeur de ${individu. nom. private String prénom.nom}. prix. // méthodes de la norme Javabean public String getNom(){ return nom. article.org/taglibs/] (mai 2006) : Les bases du développement web MVC en Java. article. la page JSP va essayer diverses méthodes dont celle-ci qui réussira : individu.enfants[1].enfants[1].get("nom") L'objet [article] peut donc être un bean avec un champ nom. Ainsi la balise <c:out value="${individu.nom}"/> permet de traiter un objet [individu] de type suivant : class Individu{ private String nom. Nous utiliserons l'implémentation JSTL 1. private Individu[] enfants.nom}"/> Que signifie ${article. chacun de ces champs étant accompagné de ses méthodes get et set. Considérons la balise de la ligne 3 : <c:out value="${article.on notera l'orthographe getNom pour récupérer le champ nom (norme Javabean) 2.nom} ? En fait diverses choses selon la nature de l'objet article. Il n'y a pas de limites à la hiérarchie de l'objet traité.} public Individu getEnfants(int i){ return enfants[i]. Pour obtenir la valeur de article.getNom() où individu désigne un objet de type Individu. La page JSP précédente va le récupérer dans l'attribut items de la balise forEach. la page JSP va essayer deux choses : 1. ou un dictionnaire avec une clé nom.1. L'objet [listarticles] a été placé dans la requête par le contrôleur.nom.

page 14. comme il a été montré au paragraphe 2.3. Les bases du développement web MVC en Java.war sont des archives d'applications web : • standard-doc : documentation sur les balises JSTL • standard-examples : exemples d'utilisation des balises Nous allons déployer cette dernière application au sein de Tomcat. Nous nous identifions comme manager / manager ou admin / admin.Le fichier zip téléchargé a un contenu analogue au suivant : Les deux fichiers de suffixe . par l'exemple 104/264 .3. Nous lançons ce dernier via l'option adéquate du menu [Démarrer] puis nous demandons l'Url [http://localhost:8080] et suivons le lien [Tomcat Manager] : Nous obtenons alors une page d'authentification.

mais nous avons sélectionné le fichier [standard-examples. Le bouton [Deploy] enregistre et déploie cette application au sein de Tomcat.war] de la distribution JSTL téléchargée. par l'exemple 105/264 . L'application [/standard-examples] a bien été déployée.war à déployer. Nous la lançons : Les bases du développement web MVC en Java. La copie d'écran ne le montre pas.Nous obtenons une page listant les applications actuellement déployées dans Tomcat : Nous pouvons ajouter une nouvelle application grâce à des formulaires placés en bas de la page : Nous utilisons le bouton [Parcourir] pour désigner un fichier .

sun. on peut placer localement. Cette URI va être utilisée par le serveur web lorsque la page JSP va être traduite en code Java pour devenir une servlet.3 Utiliser JSTL dans une application web Dans les exemples livrés avec la bibliothèque JSTL 1.1.com/jsp/jstl/core] n'est pas utilisable si on n'est pas raccordé à l'internet public.1. les pages JSP ont en début de fichier la balise suivante : <%@ taglib prefix="c" uri="http://java. Elle présente moins d'intérêt pour le débutant. par l'exemple 106/264 . Nous placerons le fichier [c.2. page 102 et nous en avons donné une courte explication : • • [uri] : URI (Uniform Resource Identifier) où l'on trouve la définition des balises utilisées dans la page. Elle est également utilisée par les outils de développement de pages web pour vérifier la bonne syntaxe des balises utilisées dans la page ou pour proposer une aide à la frappe. Dans ce cas. 8. Nous n'utiliserons que la bibliothèque [c. L'application [standard-doc] pourra être déployée de la même façon à partir du fichier [standard-doc.war].1. Plusieurs tels fichiers sont fournis avec la distribution JSTL 1. Elle donne accès à des informations assez techniques sur la bibliothèque JSTL. le fichier de définition des balises. [prefix] : préfixe qui identifie ces balises dans la page L'uri [http://java.com/jsp/jstl/core" %> Nous avons déjà rencontré cette balise au paragraphe 8. un outil connaissant la bibliothèque peut alors proposer à l'utilisateur les attributs possibles pour cette balise.Le lecteur est invité à suivre les différents liens offerts par cette page lorsqu'il cherche des exemples d'utilisation des balises JSTL.tld] dite bibliothèque " core ".sun.2 dans le dossier [tld] (Tag Language Definition) : JSTL est en fait un ensemble de bibliothèque de balises.tld] ci-dessus dans le dossier [WEB-INF] de nos applications : Les bases du développement web MVC en Java. Lorsqu'on commence l'écriture d'une balise.

et mettrons la balise suivante dans nos pages JSP pour déclarer l'utilisation de la bibliothèque " core " :
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

Si l'utilisation de bibliothèques de balises nous permet d'éviter de mettre du code Java dans les pages JSP, ces balises sont bien sûr traduite en code Java lors de la traduction de la page JSP en servlet Java. Elles utilisent des classes définies dans deux archives [jstl.jar, standard.jar] qu'on trouve dans le dossier [lib] de la distribution JSTL :

Ces deux archives sont placées dans le dossier [WEB-INF/lib] de nos applications :

Nous avons maintenant les bases pour aborder la version suivante de notre application exemple.

9 Application web MVC [personne] – version 4
Cette versions utilise la bibliothèque de balises JSTL présentée précédemment.

9.1

Le projet Eclipse

Pour créer le projet Eclipse [mvc-personne-04] de l'application web [/personne4], on dupliquera le projet [mvc-personne-03] en suivant la procédure décrite au paragraphe 6.2, page 78.

9.2

Configuration de l'application web [personne4]

Le fichier web.xml de l'application /personne4 est le suivant :
1. <?xml version="1.0" encoding="UTF-8"?> 2. <web-app id="WebApp_ID" version="2.4" Les bases du développement web MVC en Java, par l'exemple

107/264

3. 4. 5. 6. 7.

xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/webapp_2_4.xsd"> <display-name>mvc-personne-04</display-name> ...

Ce fichier est identique à celui de la version précédente hormis la ligne 6 où le nom d'affichage de l'application web a changé en [mvc-personne-04]. La page d'accueil [index.jsp] change :
1. 2. 3. 4. 5. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <c:redirect url="/main"/>

ligne 5 : la page [index.jsp] redirige le client vers l'url [/main] qui mène au contrôleur [ServletPersonne] de l'application [/personne4]. La balise <c:redirect> appartient à la bibliothèque JSTL / Core. Elle présente la particularité de compléter l'Url de son attribut [url] en y ajoutant : • le préfixe [/contexte], où [contexte] est le contexte de l'application, ici [personne4]. • le suffixe [?jsessionid=id_session] si le navigateur qui fait la requête n'a pas envoyé de cookie de session. L'identifiant [jsessionid] désigne l'identifiant du jeton de session envoyé par le serveur web à ses clients. Il dépend du serveur web. Ici, c'est celui du serveur Tomcat. [id_session] est le jeton de session lui-même. Ainsi, la véritable Url de redirection de la ligne 5 de [index.jsp] est l'Url [/personne4/main?jsessionid=XX] où XX est le jeton de session. C'est ce que montre la page ci-dessous obtenue après avoir demandé initialement l'Url [http://localhost:8080/personne4] :

Le lien entre la balise <c:redirect> et le jeton de session est assez subtil. Son étude est ici prématurée mais nous aurons l'occasion d'y revenir.

9.3
9.3.1

Le code des vues
La vue [formulaire]

Cette vue n'a pas changé :

Elle est générée par page JSP [formulaire.jsp] suivante :
Les bases du développement web MVC en Java, par l'exemple

108/264

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. 38. 39. 40. 41. 42.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <html> <head> <title>Personne - formulaire</title> <script language="javascript"> ... </script> </head> <body> <center> <h2>Personne - formulaire</h2> <hr> <form name="frmPersonne" method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="${nom}" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="${age}" type="text" size="3"></td> </tr> <tr> </table> <table> <tr> <td><input type="submit" value="Submit"></td> <td><input type="button" value="[Envoyer]" onclick="envoyer()"></td> <td><input type="reset" value="Rétablir"></td> <td><input type="button" value="[Effacer]" onclick="effacer()"></td> </tr> </table> <input type="hidden" name="action" value="validationFormulaire"> </form> </center> </body> </html>

Les nouveautés :
• •

ligne 4 : déclaration de la bibliothèque de balises JSTL / Core lignes 21, 25 : récupération d'attributs [nom, age] dans le modèle de la page. Rappelons que ces attributs seront cherchés successivement dans la requête [request], la session [session] et l'application [application] de la page JSP. Dans notre cas, le contrôleur les mettra dans la session. il n'y a plus de code Java en début de page JSP pour récupérer le modèle de la page du fait du fonctionnement précédent.

9.3.2

La vue [reponse]

Cette vue n'a pas changé :

La nouvelle page JSP [reponse.jsp] est la suivante :
Les bases du développement web MVC en Java, par l'exemple

109/264

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.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne - réponse</h2> <hr> <table> <tr> <td>Nom</td> <td>${nom}</td> </tr> <tr> <td>Age</td> <td>${age}</td> </tr> </table> <br> <form name="frmPersonne" method="post"> <input type="hidden" name="action" value="retourFormulaire"> </form> <a href="javascript:document.frmPersonne.submit();"> ${lienRetourFormulaire} </a> </body> </html>

Les nouveautés :
• •

ligne 4 : déclaration de la bibliothèque de balises JSTL / Core lignes 16, 20, 28 : récupération d'attributs [nom, age, lienRetourFormulaire] dans le modèle de la page. Il n'y a plus de code Java en début de page JSP pour récupérer celui-ci.

9.3.3

La vue [erreurs]

Cette vue n'a pas changé :

La nouvelle page JSP [erreurs.jsp] est la suivante :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach> </ul> <br>

Les bases du développement web MVC en Java, par l'exemple

110/264

18. <form name="frmPersonne" method="post"> 19. <input type="hidden" name="action" value="retourFormulaire"> 20. </form> 21. <a href="javascript:document.frmPersonne.submit();"> 22. ${lienRetourFormulaire} 23. </a> 24. </body> 25. </html> 26.

Les nouveautés :
• • •

ligne 4 : déclaration de la bibliothèque de balises JSTL / Core lignes 13-15 : affichage de la liste d'erreurs à l'aide de JSTL il n'y a plus de code Java en début de page JSP pour récupérer le modèle de celle-ci.

9.4

Tests des vues

Pour réaliser les tests des vues précédentes, nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse :

Puis dans le dossier JSP, les pages sont modifiées de la façon suivante : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. ... <% // -- test : on crée le modèle de la page session.setAttribute("nom","tintin"); session.setAttribute("age","30"); %> <html> <head>

Les lignes 4-5 ont été ajoutées pour créer le modèle dont a besoin la page. [reponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. ... <% // -- test : on crée le modèle de la page request.setAttribute("nom","milou"); request.setAttribute("age","10"); request.setAttribute("lienRetourFormulaire","Retour au formulaire"); %> <html> <head> ...

Les lignes 4-6 ont été ajoutées pour créer le modèle dont a besoin la page. [erreurs.jsp] :
Les bases du développement web MVC en Java, par l'exemple

111/264

erreurs1.si elles sont incorrectes. // -. envoyer la vue [erreurs(erreurs)] . 7. erreurs1. <html> 12.add("erreur1"). 2. Nous passons en revue les modifications amenées aux méthodes [doInit. doRetourFormulaire].5 n° 1 2 Le contrôleur [ServletPersonne] demande [GET /personne4/main] [POST /personne4/main] avec paramètres [txtNom.envoyer la vue [formulaire] vide . txtAge.erreurs1). request. Le squelette du contrôleur [ServletPersonne] est identique à celui de la version précédente. ArrayList<String> erreurs1=new ArrayList<String>(). request.add("erreur2"). 8. doValidationFormulaire. 9.vérifier les valeurs des paramètres [txtNom. Lançon Tomcat si ce n'est déjà fait puis demandons les url suivantes : Nous obtenons bien les vues attendues.setAttribute("erreurs". <head> 13. 6. 9. Les lignes 4-8 ont été ajoutées pour créer le modèle dont a besoin la page.envoyer la vue [formulaire] pré-remplie avec les dernières valeurs saisies formulaire] des vues [réponse] et [erreurs]. doGet. 5. %> 10. Les bases du développement web MVC en Java.si elles sont correctes.age)] Le contrôleur [ServletPersonne] de l'application web [/personne3] va traiter les actions suivantes : 3 clic sur le lien [Retour au . les méthodes [init. <% 3. par l'exemple 112/264 . envoyer la vue [reponse(nom. 11."Retour au formulaire"). txtAge] . action=validationFormulaire] postés [POST /personne4/main] avec paramètres [action=retourFormulaire] postés origine url tapée par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement .test : on crée le modèle de la page 4.setAttribute("lienRetourFormulaire".1. doPost] ne changeant pas.

Celle-ci attend dans son modèle des attributs " nom " et " age ". } Les bases du développement web MVC en Java. return. // validation du formulaire 2. age] également dans la requête lorsqu'on envoyait la vue [réponse] cat cette vue les attendait là. 15. avec la bibliothèque JSTL. session. par l'exemple 113/264 . lienRetourFormulaire]. void doValidationFormulaire(HttpServletRequest request. IOException{ // on affiche le formulaire getServletContext(). via la requête. IOException{ 4. Cela ne provoque pas d'erreurs et des valeurs vides seront affichées pour les éléments ${nom} et ${age} de la vue [formulaire]. 3.2 La méthode [doValidationFormulaire] Cette méthode traite la requête n° 2 [POST /personne4/main] avec [action. Son code est le suivant : 1. request. Cela nous convient. HttpServletResponse response) throws ServletException.setAttribute("lienRetourFormulaire".1 La méthode [doInit] Cette méthode traite la requête n° 1 [GET /personne4/main].getRequestDispatcher((String)params. response). // qu'on mémorise dans la session 8.getParameter("txtNom"). Dans la version précédente. 6. } Les nouveautés : • ligne 15 : la méthode [doValidationFormulaire] envoie en réponse la vue [réponse]. // affichage formulaire pré-rempli void doRetourFormulaire(HttpServletRequest request. application) vont être explorés pour trouver les éléments du modèle.. txtAge] dans les éléments postés.getSession(true).setAttribute("age". on avait placé les éléments [nom. 16. // vérification des paramètres 12.5. 18. via la session. 7.5. String nom = request. 7.get("urlFormulaire")). 9. getServletContext(). age. (String)params.forward( 4. txtNom.getRequestDispatcher((String)params.. session. [lienRetourFormulaire] est mis dans le modèle ligne 14. 6. IOException{ 3. 5. Dans ce cas.9. response).setAttribute("nom". // les paramètres sont corrects . String age = request. HttpServletResponse response) throws ServletException.on envoie la page réponse 14. Ils seront donc trouvés dans la session puisque le contrôleur les y a mis (lignes 810). Celle-ci a dans son modèle les éléments [nom. Les éléments [nom.} Les nouveautés : • ligne 3 : on affiche la vue [formulaire]. session.getRequestDispatcher((String)params. void doInit(HttpServletRequest request. la bibliothèque JSTL va récupérer des pointeurs null pour ces attributs. 2. 6.. 9. Son code est le suivant : 1.age] sont eux mis dans le modèle lignes 8-10.get("urlFormulaire")).5.forward( request. request.3 La méthode [doRetourFormulaire] Cette méthode traite la requête n° 3 [POST /personne4/main] avec [action=retourFormulaire] dans les éléments postés. 11.get("lienRetourFormulaire")). return. 13.getParameter("txtAge"). 3. response). // on récupère les paramètres 5. age). getServletContext(). 5. 9. on sait que les différents contextes (request.forward(request. // affichage formulaire vide 2. 4.get("urlReponse")). Elle ne va pas les y trouver puisqu'ici le contrôleur ne les y met pas. nom). return. HttpSession session = request. 10. Ici. Nous évitons ainsi d'avoir à initialiser le modèle de la vue [formulaire]. Son code est le suivant : 1. HttpServletResponse response) throws ServletException. 17.

<servlet-name>personne</servlet-name> 3. Du coup. Là encore.6 Tests Dans cette nouvelle version. le modèle construit par le contrôleur. <servlet-mapping> 2. y avoir intégré le projet Eclipse [personne-mvc-04]. Toutes les Url de l'application auront la forme [/personne5/do/action]. Celle-ci attend dans son modèle des attributs " nom " et " age ". Jusqu'à maintenant celleci était précisée à l'aide d'un paramètre appelé [action] dans la requête du GET ou du POST du client. le POST provoqué par le lien [Retour au formulaire] a été fait à l'Url [/personne5/do/retourFormulaire]. les méthodes [doInit] et [doRetourFormulaire] sont identiques et on pourrait supprimer l'action [retourFormulaire] pour la remplacer par l'action [init]. Il n'y a donc pas à initialiser le modèle de [formulaire] avant son affichage ligne 4. l'Url à laquelle a été postée le formulaire est [/personne5/do/validationFormulaire]. Lancer ou relancer Tomcat après [http://localhost:8080/personne4]. l'action sera précisée par le dernier élément de l'Url demandée par le client comme le montre la séquence suivante : 1 2 En [1].getPathInfo().1 Introduction Dans cette version. C'est le dernier élément [validationFormulaire] de l'Url qui a permis au contrôleur de reconnaître l'action à faire. La méthode [getPathInfo] de l'objet [request] donne le dernier élément de l'Url de la requête. // on récupère l'action à exécuter 2. ces informations sont mémorisées dans une session. La méthode [doRetourFormulaire] disparaîtrait alors. En [2]. Ici. 9. seules les vues changent. <url-pattern>/do/*</url-pattern> 4.</servlet-mapping> Le contrôleur récupérera le nom de l'action à faire de la façon suivante : 1. par l'exemple 114/264 . Nous introduisons cette modification parce que c'est la méthode utilisée par les frameworks de développement web les plus répandus tels Struts ou Spring MVC. Pour l'instant.xml] de l'application [/personne5] indiquera que celle-ci accepte des Url de la forme [/do/*] : 1. Le contrôleur [ServletPersonne] lui ne change pas. Demander l'url 10 Application web MVC [personne] – version 5 10. Cette méthode peut présenter des inconvénients s'il Les bases du développement web MVC en Java. le dernier élément [retourFormulaire] de l'Url indique au contrôleur l'action à faire. Le fichier [web.Les nouveautés : • ligne 4 : on affiche la vue [formulaire]. Elle les trouvera dans la session puisque la méthode [doValidationFormulaire] les y a mis et que cette méthode est forcément exécutée avant la méthode [doRetourFormulaire]. String action=request. nous apportons deux modifications : La première porte sur la façon utilisée par le client pour indiquer au serveur l'action qu'il désire faire. La seconde modification apportée porte sur la façon de mémoriser les saisies faites par l'utilisateur entre deux cycles demande / réponse. L'utilisation de JSTL nous a simplement permis d'exploiter plus simplement dans les pages JSP.

1 Host: localhost:8080 User-Agent: Mozilla/5. On le voit au jeton de session que le navigateur renvoie au serveur ligne 11 et qu'il avait reçu précédemment du serveur. des entêtes HTTP [Set-Cookie] ont été envoyés au navigateur client.0. si ce n'est que bien qu'on ne va pas utiliser de session.de. fr. 4.5.*/*.q=0.y a beaucoup d'utilisateurs et beaucoup de données à mémoriser pour chacun d'eux. 11. 7.7. U.fr. Les valeurs de ces cookies sont les valeurs postées ligne 14 du POST [1] ci-dessus. 3.q=0.q=0. chaque utilisateur a sa session personnelle.8.text/html. il existe des alternatives à la session.2 Accept-Encoding: gzip. par l'exemple 115/264 .q=0. 15. 2. rv:1. 8. 9. 3. En effet.application/xml. 6.q=0.en-us. Illustrons-là sur un exemple.q=0.1. 7. 5.utf-8.en. Etape 2 : Retour au formulaire Les bases du développement web MVC en Java. moins coûteuses en mémoire et il est bon de les connaître. 5.q=0.q=0. le serveur web en crée une quand même.9. 6. 22 May 2006 08:03:51 GMT On voit que lignes 3 et 4. HTTP/1.5 Accept-Language: fr-fr.image/png.6.3 Accept: text/xml. POST /personne5/do/validationFormulaire HTTP/1. De plus celle-ci reste active un certain temps après le départ d'un utilisateur sauf si on a pris soin de lui offrir une option de déconnexion.charset=ISO-8859-1 Content-Length: 547 Date: Mon. 14.3) Gecko/20060426 Firefox/1. 2.0. Néanmoins. Windows NT 5. Ainsi 1000 sessions de 1000 octets vont occuper 1Mo de mémoire.q=0.8.x 200 OK Server: Apache-Coyote/1.application/xhtml+xml. 13.1 Set-Cookie: nom=pauline Set-Cookie: age=18 Content-Type: text/html.4. Cela reste une exigence modérée et il y a peu d'applications qui ont 1000 sessions actives simultanément. Il n'y a rien de particulier à signaler ici.*. Nous utiliserons ici la méthode des cookies.8.deflate Accept-Charset: ISO-8859-1.0 (Windows.text/plain.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/personne5/do/formulaire Cookie: JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7 Content-Type: application/x-www-form-urlencoded Content-Length: 24 txtNom=pauline&txtAge=18 C'est un POST classique. 10. 12. 4. Etape 1 : l'utilisateur valide un formulaire : 1 2 Ce cycle demande / réponse donne lieu aux échanges HTTP suivants entre le client et le serveur : [1] : [demande du client] 1. un pour le nom (ligne 3) et un pour l'âge (ligne 4). [2] : [réponse du serveur] 1.

q=0. 4. 6. C'est le principe des cookies. rv:1.3) Gecko/20060426 Firefox/1. page 78.de. 2. Dans cet exemple.charset=ISO-8859-1 Content-Length: 2341 Date: Mon. Cela n'empêchera pas le navigateur de renvoyer au prochain échange tous les cookies qu'il a reçus du serveur même si cela ne sert à rien.1 Content-Type: text/html.4.8. 9. JSESSIONID=6C6F4D112803A7E3696D41F5750CEDE7 Content-Type: application/x-www-form-urlencoded Content-Length: 0 Nous assistons ici au POST provoqué par le clic sur le lien [Retour au formulaire]. 10. on dupliquera le projet [mvc-personne-04] en suivant la procédure décrite au paragraphe 6. 3.deflate Accept-Charset: ISO-8859-1. txtAge] de la vue [formulaire] affichée en [2]. 11.7. 2.6. Windows NT 5. fr.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/personne5/do/validationFormulaire Cookie: nom=pauline.text/plain.8.5 Accept-Language: fr-fr. le contrôleur va recevoir les valeurs [pauline. HTTP/1.0. 22 May 2006 08:16:47 GMT Il n'y a là rien à signaler de particulier autre que le fait que dans cette réponse.*/*.application/xhtml+xml. 5.*.1.image/png. 13.q=0. par l'exemple 116/264 . 4.2 1 1 Ce cycle demande / réponse donne lieu aux échanges HTTP suivants entre le client et le serveur : [1] : [demande du client] 1. nous voyons que le navigateur renvoie au serveur. [2] : [réponse du serveur] 1.3 Accept: text/xml. le serveur n'a pas envoyé de cookies.q=0.text/html.q=0. 18] qu'il doit placer dans les champs [txtNom.0 (Windows. 10. POST /personne5/do/retourFormulaire HTTP/1. On diminue donc la pression sur la mémoire disponible du serveur au prix d'une augmentation du flux de caractères dans les échanges client / serveur. U. JSESSIONID] au moyen de l'entête Http [Cookie].x 200 OK Server: Apache-Coyote/1.2 Le projet Eclipse Pour créer le projet Eclipse [mvc-personne-05] de l'application web [/personne5]. 5. 12.2. 7.q=0. Ligne 11. 8. les cookies qu'il a reçus [nom.en-us.8. age.0.9.q=0.1 Host: localhost:8080 User-Agent: Mozilla/5. 3.utf-8.q=0.application/xml.2 Accept-Encoding: gzip. Les bases du développement web MVC en Java.q=0.q=0. age=18.en.fr. Le client renvoie au navigateur les cookies que celui-ci lui a envoyés.5.

4" xmlns="http://java. 21.tld" prefix="c" %> <c:redirect url="/do/formulaire"/> • ligne 5 : la page [index.st. 23.com/xml/ns/j2ee" xmlns:xsi="http://www. 22. 16. 10. 2.ServletPersonne </servlet-class> . seule l'Url [/main] était traitée. <%@ page language="java" contentType="text/html.Mapping ServletPersonne--> <servlet-mapping> <servlet-name>personne</servlet-name> <url-pattern>/do/*</url-pattern> </servlet-mapping> <!-.xsd"> <display-name>mvc-personne-05</display-name> <!-. 20. 19. 11. 15. par l'exemple 117/264 .servlets.sun.10. 18.ServletPersonne --> <servlet> <servlet-name>personne</servlet-name> <servlet-class> istia. Les bases du développement web MVC en Java. 4. 12. La page d'accueil [index.jsp] change : 1.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. 2.sun. 5. 3. 9.com/xml/ns/j2ee http://java. 24. on a autant d'Url que d'actions à traiter. 7. 4.fichiers d'accueil --> <welcome-file-list> <welcome-file>index.com/xml/ns/j2ee/webapp_2_4.jsp] redirige le client vers l'url [/personne5/do/formulaire]. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c. 13. Maintenant.. 14. Configuration de l'application web [personne5] <?xml version="1. 5.3 1.jsp</welcome-file> </welcome-file-list> </web-app> Le fichier web. ce qui revient à demander au contrôleur d'exécuter l'action [formulaire]. Auparavant.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java..sun.xml de l'application /personne5 est le suivant : Ce fichier est identique à celui de la version précédente hormis quelques détails :   ligne 6 : le nom d'affichage de l'application web a changé en [mvc-personne-05] ligne 18 : les Url traitées par l'application sont de la forme [/do/*].w3. 3. 6. </servlet> <!-. 25. 17.personne. 8.

12. l'attribut [action] a pour valeur une Url relative (ne commençant pas par /). 5.. 3.. réponse."> ${lienRetourFormulaire} </a> </body> </html> • • ligne [7]: la cible du POST sera [/do/retourFormulaire] le champ caché [action] a disparu dans le formulaire des lignes 7-8. 7. L'unique changement vient du fait que l'action à accomplir n'est plus précisée de la même façon qu'auparavant où elle était définie dans un champ caché nommé [action] des formulaires postés. 3. . 18..a. 7. 10."> ${lienRetourFormulaire} </a> </body> </html> • • ligne [6]: la cible du POST sera [/do/retourFormulaire] le champ caché [action] a disparu dans le formulaire des lignes 6-7. 6. 11. <body> . <form name="frmPersonne" action="retourFormulaire" method="post"> </form> <a href="javascript:document..jsp] : 1. 2. 2... 7. 12. 16. 3. Le dernier élément va être remplacé par l'Url relative de l'attribut [action] de la balise <form> pour donner l'Url [/do/validationFormulaire] comme cible du POST. </form> </center> </body> </html> • • ligne [13]: le paramètre [action] du formulaire réapparaît après avoir disparu un certain temps dans les versions précédentes. 4. 5.submit(). c.d. Aussi le navigateur va-t-il la compléter avec l'Url de la page actuellement affichée donc nécessairement une Url de la forme [/do/action]. Pour comprendre la valeur de cet attribut ici.formulaire</title> <script language="javascript"> .jsp] : 1. 10. Le lecteur est invité à tester ces nouvelles vues selon le principe vu dans les versions précédentes.10. 9. 8. <form name="frmPersonne" action="retourFormulaire" method="post"> </form> <a href="javascript:document. <html> <head> <title>Personne . Maintenant elle est définie dans l'Url cible des formulaires postés.4 Le code des vues Les vues [formulaire. 6. le champ caché [action] a disparu [réponse. <html> ... Les bases du développement web MVC en Java. 14. 8. 13.. [erreurs. 8. dans l'attribut [action] de la balise <form> : [formulaire. il faut se rappeler que toutes les Url traitées par l'application sont de la forme [/do/action]. 15. 17. 11.. Ligne [13]. <html> . 12. 11. erreurs] changent peu. 2..frmPersonne. 9. 13.. 13. par l'exemple 118/264 ... 9... 4. . 10. 5. 14. 4.frmPersonne. </script> </head> <body> <center> <h2>Personne . <body> . 6.jsp] : 1.submit(). ..formulaire</h2> <hr> <form name="frmPersonne" action="validationFormulaire" method="post"> ..

doInit(request. // exécution action 18. @SuppressWarnings("unchecked") 2. txtAge] dans les éléments postés. if(méthode. } 28. } 9. 35. 13. } • • • • ligne 12 : on récupère l'action à exécuter.5 n° 1 2 Le contrôleur [ServletPersonne] demande origine url tapée par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement .equals("/retourFormulaire")){ 29.1 La méthode [doGet] La méthode [doGet] ne récupère pas l'action à exécuter de la même façon que dans les versions précédentes : 1.size() != 0) { 7.response).age)] . 26.response). 11. les méthodes [init. 5. 21. doInit(request.si elles sont incorrectes. doInit. if(méthode. // on récupère l'action à exécuter 12. // autres cas 34. 10.2 La méthode [doValidationFormulaire] Cette méthode traite la requête n° 2 [POST /personne5/do/validationFormulaire] avec [txtNom.toLowerCase(). } 23.5. par l'exemple 119/264 . lignes 18-22 : traitement de l'action [/formulaire] demandée par une requête GET lignes 23-27 : traitement de l'action [/validationFormulaire] demandée par une requête POST lignes 28-32 : traitement de l'action [/retourFormulaire] demandée par une requête POST 10.. // retour au formulaire de saisie 30. // on récupère la méthode d'envoi de la requête 10.equals("/validationFormulaire")){ 24. if (erreursInitialisation. } 17. doValidationFormulaire(request. public void doGet(HttpServletRequest request.equals("/formulaire")){ 19. } 33. envoyer la vue [reponse(nom. 8. String méthode=request.equals("get") && action. 27. 31.envoyer la vue [formulaire] pré-remplie avec les dernières valeurs saisies Le contrôleur [ServletPersonne] de l'application web [/personne5] va traiter les actions suivantes : [GET /personne5/do/formulaire] [POST /personne5/do/validationFormulaire] avec paramètres [txtNom. 22. ServletException { 4.response). // action ? 14. 16. Elle est de la forme [/action].envoyer la vue [formulaire] vide . return. // validation du formulaire de saisie 25.getMethod().5. doGet]. Nous passons en revue les modifications amenées aux méthodes [doValidationFormulaire. // démarrage application 20. return.si elles sont correctes.vérifier les valeurs des paramètres [txtNom. if(action==null){ 15. . // on vérifie comment s'est passée l'initialisation de la servlet 6. if(méthode. envoyer la vue [erreurs(erreurs)] . doRetourFormulaire. String action=request. txtAge] postés [POST /personne5/do/retourFormulaire] sans paramètres postés 3 clic sur le lien [Retour au formulaire] des vues [réponse] et [erreurs].response). HttpServletResponse response) 3. txtAge] .equals("post") && action.equals("post") && action. 32.10. Le squelette du contrôleur [ServletPersonne] est identique à celui de la version précédente. action="/formulaire". doRetourFormulaire(request.getPathInfo().. return. throws IOException. doPost] ne changeant pas. Son code est le suivant : Les bases du développement web MVC en Java.

getRequestDispatcher((String)params. response.getCookies(). Aussi est-on obligé de passer en revue chacun des cookies reçu.equals("age")){ age=cookies[i]. request. } Les nouveautés : La méthode [doRetourFormulaire] doit faire afficher un formulaire pré-rempli avec les dernières saisies faites. 26. String age = request. 20. le navigateur va renvoyer au serveur les deux cookies qu'il a reçus. String age=null. String nom = request. 11. 4. on n'utilise plus la session mais des cookies pour mémoriser des éléments entre deux échanges client-serveur. 7.setAttribute("age". Un cookie est représenté par un objet [Cookie] dont le constructeur admet deux paramètres. 5. HttpServletResponse response) throws ServletException. ligne 8 : la valeur saisie pour le nom est mise dans un cookie de clé "nom" ligne 9 : la valeur saisie pour l'âge est mise dans un cookie de clé "age" un cookie est ajouté à la réponse HTTP faite au client avec la méthode [response. 6. 10. 25. 3.getParameter("txtNom").1. 21. 9. Son code est le suivant : 1.get("urlFormulaire")). .getName(). 14. par l'exemple 120/264 .. 8. les deux valeurs obtenues sont placées dans le modèle de la vue [formulaire] (lignes 20-21) afin que celle-ci les affiche. 12. 6. 19. Assez bizarrement.addCookie(new Cookie("nom".// validation du formulaire 2. HttpServletResponse response) throws ServletException.nom). int nbCookies=0. celles-ci étaient dans la session.. 23. 9. 12. 7. String nom=null. • • lignes 4-18 : on récupère les valeurs des cookies libellés " nom " et " age ". 10.length && nbCookies<2.i++){ if(cookies[i]. Les bases du développement web MVC en Java. ceci fait. response). Dans la version précédente. IOException{ // on récupère les cookies de l'utilisateur Cookie[] cookies=request. // affichage formulaire pré-rempli void doRetourFormulaire(HttpServletRequest request. le contrôleur met dedans deux cookies.3 La méthode [doRetourFormulaire] Cette méthode traite la requête n° 2 [POST /personne5/do/retourFormulaire] sans éléments postés.age). 2.i<cookies. 15. } Les nouveautés : • • • • la méthode [doValidationFormulaire] envoie en réponse l'une des vues [réponse. Dans celle-ci. la clé du cookie et la valeur associée à celle-ci. return. 10. Cette réponse n'est ici que préparée. 17.setAttribute("nom".getName().5. lignes 8-9.age)). 22. // vérification des paramètres 11. nbCookies++.forward( request. void doValidationFormulaire(HttpServletRequest request. Quelle que soit cette réponse.getValue(). Lors du clic sur le lien [Retour au formulaire] de ces deux vues qui provoque un POST sur l'Url [/do/retourFormulaire]. 24.getValue(). il a reçu en réponse la vue [réponse] ou [erreurs] selon les cas. } } } // on prépare le modèle du formulaire request. IOException{ 4. 13. erreurs]. 18. nbCookies++. 3. accompagnée de deux cookies libellés " nom " et " age ". response. }else{ if(cookies[i].getParameter("txtAge"). Lorsque le client a demandé la validation du formulaire. for(int i=0.nom)).addCookie(new Cookie("age". // on récupère les paramètres 5. 16. il n'existe pas de méthode permettant d'avoir la valeur d'un cookie à partir de sa clé.equals("nom")){ nom=cookies[i].addCookie]. // qu'on mémorise dans un cookie 8. Elle ne sera envoyée véritablement que par exécution de la page JSP de la vue envoyée au client. // on affiche le formulaire getServletContext().

La technique des champs cachés est utilisable ici car tous les appels du client sont de type POST. nous apportons la modification suivante : La version précédente n'a pas utilisé la session pour garder en mémoire des éléments entre deux échanges client / serveur. par l'exemple .la technique nécessite que tous les appels du client au serveur pour que celui-ci les renvoie au serveur lors de ses demandes soient des POST afin que les champs cachés soient envoyés au serveur. qui précède le document HTML. Dans cette nouvelle version.2 Le projet Eclipse Pour créer le projet Eclipse [mvc-personne-06] de l'application web [/personne6]. page 78. .le serveur place les éléments à mémoriser dans le document . 11. qui fait que les éléments à mémoriser sont envoyés au client par le serveur afin que celui-ci les lui renvoie lors du prochain échange.6 Tests Lancer ou relancer Tomcat après y avoir intégré le projet Eclipse [personne-mvc-05] puis demander l'url [http://localhost:8080/personne5]. nous utilisons une technique proche.1 Introduction Dans cette version. GET ou POST. La plupart des le code source du document HTML reçu.l'utilisateur a accès aux éléments mémorisés en faisant afficher afficher les cookies reçus par son navigateur. navigateurs proposent cette option. 11 Application web MVC [personne] – version 6 11.l'utilisateur a accès aux éléments mémorisés en faisant .xml de l'application /personne6 est le suivant : Les bases du développement web MVC en Java.2. Elle a utilisé la technique des cookies.10. .le serveur place les éléments à mémoriser dans le flux HTTP HTML. celles des champs cachés dans les formulaires. on dupliquera le projet [mvc-personne-05] en suivant la procédure décrite au paragraphe 6. Cookies 11.la technique nécessite que le navigateur accepte les cookies .3 Configuration de l'application web [personne6] 121/264 Le fichier web. Il existe des différences entre ces deux techniques : Champs cachés .

1.. 2. <html> . 7.. <body> . 11.sun.frmPersonne. ces formulaires ne postaient auncun paramètre. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.com/xml/ns/j2ee" xmlns:xsi="http://www.. 6. Le lecteur est invité à tester ces nouvelles vues selon le principe vu dans les versions précédentes. 8.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java. [erreurs. 6. 8. 4.. 5. Ce seront les valeurs saisies dans le formulaire. 14. 4.com/xml/ns/j2ee/webapp_2_4."> ${lienRetourFormulaire} </a> </body> </html> Les modifications et les explications sont les mêmes que pour la vue [réponse.jsp]. 5.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. par l'exemple 122/264 . 2.. 12. 9. Leurs valeurs ${nom} et ${age} seront fixées par le contrôleur qui affichera [réponse..jsp] : 1. 3. 7. erreurs] changent. 13.. 5. 4.submit(). Ce fichier est identique à celui de la version précédente hormis quelques détails :  ligne 6 : le nom d'affichage de l'application web a changé en [mvc-personne-06] La page d'accueil [index...xsd"> <display-name>mvc-personne-06</display-name> . Les bases du développement web MVC en Java.frmPersonne.jsp]. 12.. 3.sun. 7. [réponse. 11. 13. 10..4" xmlns="http://java. 10. 15.sun."> ${lienRetourFormulaire} </a> </body> </html> • • • lignes 6-9 : le formulaire qui sera posté au serveur lors du clic sur le lien des lignes 10-12 ligne 6 : la cible du POST sera [/do/retourFormulaire] lignes 7-8 : les champs cachés [nom] et [age] seront postés. 5.. age] qui viennent s'ajouter aux deux autres qui existaient déjà : [erreurs.4 Le code des vues Seules les vues [réponse. 3.com/xml/ns/j2ee http://java. lienRetourFormulaire]. <form name="frmPersonne" action="retourFormulaire" method="post"> <input type="hidden" name="nom" value="${nom}"> <input type="hidden" name="age" value="${age}"> </form> <a href="javascript:document. 2.jsp] : 1. 4...w3. <form name="frmPersonne" action="retourFormulaire" method="post"> <input type="hidden" name="nom" value="${nom}"> <input type="hidden" name="age" value="${age}"> </form> <a href="javascript:document. <body> .tld" prefix="c" %> <c:redirect url="/do/formulaire"/> 11. . 14. . Elles ont maintenant des champs cachés dans leurs formulaires respectifs alors que dans la version précédente.submit(). 6. On notera que le modèle de la vue [erreurs] s'enrichit de deux nouveaux éléments [nom. 3. 2. 9. 16. <html> .jsp] est identique à celle de l'application [/personne5] : 1. <%@ page language="java" contentType="text/html. <?xml version="1.

on met dans le modèle les éléments [nom. 16. String age = request. // validation du formulaire 2. request. @SuppressWarnings("unchecked") 3.forward( 11. l'exécution de code Javascript embarqué dans les pages HTML affichées Les bases du développement web MVC en Java.jsp] sera remplacée par la chaîne vide. 12 Application web MVC [personne] – version 7 12. } 15. // fin 13.getParameter("txtAge"). erreurs] 8. HttpServletResponse response) 4. request.. Lignes 8-10. 11. 11. age] du formulaire de [erreurs. Il n'y aura donc pas de lien. } En fait.age).jsp]. ServletException { 5.get("lienRetourFormulaire")). age] n'étaient pas mis dans le modèle de la vue [erreurs]. 6. // vérification des paramètres 12.size() != 0) { 8. // on vérifie comment s'est passée l'initialisation de la servlet 7. Seules les méthodes faisant afficher cette vue sont concernées. . request. Cela nous convient. le renvoi des cookies qu'envoie le serveur 2. void doValidationFormulaire(HttpServletRequest request.setAttribute("lienRetourFormulaire". response). age. Il s'agit des méthodes [doGet] et [doValidationFormulaire]. public void doGet(HttpServletRequest request.1 Introduction Dans cette version.. Les changements viennent du fait que le modèle de la vue [erreurs] a changé.setAttribute("nom". les variables ${nom} et ${age} de [erreurs. 10. age].jsp] seront remplacées par la chaîne vide.1 La méthode [doGet] Le code de [doGet] est le suivant : 1. En effet. // on récupère les paramètres 5. La variable ${lienRetourFormulaire} de [erreurs.nom). Dans la version précédente. getServletContext(). erreursInitialisation). // GET 2.5. ce code reste identique à ce qu'il était.. par l'exemple 123/264 . car dans ce cas précis. if (erreursInitialisation.2 La méthode [doValidationFormulaire] Son code est le suivant : 1.11. On sait que la méthode affiche l'une des vues [réponse] ou [erreurs].5 Le contrôleur [ServletPersonne] Le contrôleur [ServletPersonne] de l'application web [/personne6] est très proche à celui de la version précédente..getParameter("txtNom"). 11.setAttribute("age". 12. HttpServletResponse response) throws ServletException.5.getRequestDispatcher(urlErreurs).. lienRetourFormulaire]. request. 6. IOException{ 4. request. throws IOException. La valeur de ces champs peut donc être la chaîne vide. Cette dernière aura donc les éléments [nom. 3. (String)params. nous supposons qu'il peut y avoir des navigateurs clients qui ont inhibé : 1. 14. Il y faut mettre deux éléments supplémentaires : [nom. // on passe la main à la page d'erreurs 9.age] dans son modèle. String nom = request. 9. donc pas possibilité de poster les champs cachés [nom. le lien [Retour au formulaire] n'est pas présenté à l'utilisateur. 10. // on prépare le modèle des vues [réponse. return. Si on continue à ne pas les y mettre. 11. 7.setAttribute("erreurs". les éléments [nom.6 Tests Lancer ou relancer Tomcat après y avoir intégré le projet Eclipse [personne-mvc-06] puis demander l'url [http://localhost:8080/personne6]. nous ne mettons pas non plus l'élément [lienRetourFormulaire] dans le modèle.

Nous avons utilisé jusqu'à maintenant trois solutions pour mémoriser des informations au fil des échanges client / serveur : 1. S'il a inhibé ses cookies. les cookies 3. Lors du premier échange client / serveur. Prenons un navigateur où les cookies sont acceptés. on configure le navigateur Firefox : Les bases du développement web MVC en Java. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c. On peut alors envisager d'utiliser le protocole HTTPS qui crypte les échanges client / serveur. Ci-dessous. Le jeton de session est envoyé par le serveur dans un cookie. Cela n'est pas bon pour la sécurité de l'application. <%@ page language="java" contentType="text/html. le serveur va pouvoir reconnaître son client et lui affecter des informations qu'il avait mémorisées lors d'un échange précédent.On veut néanmoins que ce type de navigateurs puisse utiliser notre application. le serveur envoie au client un jeton de session que celui-ci va renvoyer au serveur à chaque nouvelle demande. 5. Grâce à ce jeton. Si le couple (login / mot de passe) est encapsulé dans chaque page envoyée au navigateur. Elle ne peut être utilisée pour des raisons de sécurité. En fusionnant les versions 2 et 6. il dispose d'une autre solution : il peut inclure le jeton de session dans l'Url qu'il demande. La solution 2 peut être éliminée puisque le navigateur client peut avoir inhibé l'utilisation des cookies. par l'exemple 124/264 . Ce n'est pas une contrainte dénuée de sens. 4. La version 2 faisait fonctionner l'application sans Javascript donc le point 2 est résolu. C'est ce que nous voyons maintenant en reprenant l'étude du fichier [index. Le navigateur qui n'a pas inhibé ses cookies peut lui renvoyer ce cookie lors de ses demandes suivantes. 2. 3.tld" prefix="c" %> <c:redirect url="/main"/> On rappelle que la ligne 5 ci-dessus redirige le client vers l'Url [/personne4/main?jsessionid=XX] où XX est le jeton de session comme le montre la copie d'écran ci-dessous obtenue après avoir demandé l'Url [http://localhost:8080/personne4] : Examinons de plus près le fonctionnement de la balise <c:redirect> pour ce qui est du jeton de session. le Javascript ayant été utilisé à partir de la version 3.jsp] de la version 4 : 1. le serveur doit mémoriser le couple (identifiant / mot de passe) de l'utilisateur pour lui éviter de s'authentifier à chaque page qu'il demande. Le point 2 nous ramène à la version 2 de notre application. les champs cachés. Dans une application où les utilisateurs doivent s'authentifier. Nous allons ajouter une contrainte supplémentaire : l'application doit gérer une session. On pourrait vouloir éliminer la solution 1 parce qu'elle est également à base de cookies. Mais l'utiliser pour chaque page de l'application va alourdir la charge du serveur. la session 2. La solution 3 est celle de la version 6 précédemment étudiée. La version 6 de notre application fonctionnait sans cookies. cela veut dire qu'il transite sur le réseau à chaque échange client / serveur. nous obtenons le résultat demandé. Le point 1 peut être délicat ou non à gérer.

5 Accept-Language: fr-fr. 6. 5. Nous obtenons la réponse suivante : La demande HTTP initiale du client a été la suivante : 1.8. rv:1.text/html. nous autorisons les cookies et en [2] nous supprimons ceux qui existent déjà afin de partir d'une situation connue. 9. 8.1 Set-Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC. Le navigateur.8.de.*/*. GET /personne4/main.jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC HTTP/1. 7. à qui on a demandé de se rediriger.charset=ISO-8859-1 Content-Length: 0 Date: Tue. 10.text/plain.*. 3. a ensuite fait la demande suivante : 1.deflate Accept-Charset: ISO-8859-1. 9.q=0.en-us.1 Host: localhost:8080 User-Agent: Mozilla/5.q=0.application/xhtml+xml.0.text/html.q=0.q=0. 5. La réponse HTTP envoyée par le serveur est la suivante : 1.q=0.3) Gecko/20060426 Firefox/1.0 (Windows. rv:1. 3. 5.image/png.6.8.q=0.de.application/xml. HTTP/1. 2.deflate Accept-Charset: ISO-8859-1.q=0. La balise <c:redirect> l'y a placé parce que le client n'avait pas envoyé de cookie de session.x 302 Déplacé Temporairement Server: Apache-Coyote/1.7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC Les bases du développement web MVC en Java. 7.6.3) Gecko/20060426 Firefox/1.q=0.jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC Content-Type: text/html.q=0.application/xhtml+xml. GET /personne4/ HTTP/1.en.3 Accept: text/xml.7 Keep-Alive: 300 Connection: keep-alive On notera simplement que le client n'envoie pas de cookie de session.1 Host: localhost:8080 User-Agent: Mozilla/5.9. Puis nous demandons l'Url [http://localhost:8080/personne4].4. 4.q=0.2 Accept-Encoding: gzip.utf-8.9.3 Accept: text/xml. 6. par l'exemple 125/264 . 2.application/xml.text/plain.fr.1. 23 May 2006 09:10:05 GMT • • • ligne 1 : le serveur demande au client de se rediriger ligne 3 : le serveur envoie un jeton de session associé à l'attribut [JSESSIONID] ligne 4 : l'Url de redirection contient le jeton de session.q=0. 8.0. Path=/personne4 Location: http://localhost:8080/personne4/main. U. 4.5 Accept-Language: fr-fr.1. 3.*.fr.0.q=0.0.q=0. fr. 7.en-us. 6. U.*/*. 4. fr.4.1 2 En [1]. Windows NT 5.7.7.q=0.en.image/png.2 Accept-Encoding: gzip.8.0 (Windows.5.q=0.q=0.q=0. Windows NT 5.8.utf-8.q=0. 2.5.8.

2.8. 2. On obtient alors la page suivante : On constate que l'Url affichée par le navigateur ne contient plus le jeton de session. GET /personne4 HTTP/1.fr.4.x 302 Déplacé Temporairement Server: Apache-Coyote/1.q=0. 23 May 2006 09:24:39 GMT Il demande au client de se rediriger.text/plain. 7. demandons de nouveau l'Url [http://localhost:8080/personne4] en la tapant à la main.1 Content-Type: text/html.6.*. il poursuit celle-ci et n'envoie pas de nouveau jeton de session.q=0.• • ligne 1 : il demande l'Url de redirection.en-us. les cookies reçus ne sont pas renvoyés.utf-8. la balise <c:redirect> n'inclut pas ce jeton de session dans l'Url de redirection.3) Gecko/20060426 Firefox/1. jeton de session inclus. 2.x 200 OK Server: Apache-Coyote/1.*/*.q=0. Regardons le premier échange client / serveur : Le navigateur a fait la demande suivante : 1. Le serveur a envoyé la réponse suivante : 1.q=0. 5. rv:1. C'est pourquoi l'Url affichée par la copie d'écran ci-dessus n'a-t-elle pas de jeton de session.deflate Accept-Charset: ISO-8859-1. le navigateur renvoie le jeton de session qu'il avait reçu lors du tout premier échange.q=0. ligne 10 : le navigateur renvoie le jeton de session que lui a envoyé dans l'échange précédent le serveur. 10.de. 23 May 2006 09:10:05 GMT Il a trouvé la page qu'on lui demandait et il l'envoie. C'est le fonctionnement normal du jeton de session : il est envoyé au navigateur une unique fois par le serveur sous la forme d'un cookie et le navigateur le renvoie ensuite à chaque demande pour se faire reconnaître.0.q=0. 5.en. HTTP/1. Si ce n'est pas le cas.9. fr. 5. Les bases du développement web MVC en Java.q=0.image/png. C'est pourquoi sur la copie d'écran.5.3 Accept: text/xml.q=0.5 Accept-Language: fr-fr. On retiendra de tout ça la règle suivante : la balise <c:redirect> n'inclut le jeton de session dans l'Url de redirection que si le client n'a pas envoyé l'entête HTTP : Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC Cette règle est également vraie pour la balise <c:url> que nous aurons l'occasion de rencontrer ultérieurement. C'est le fonctionnement normal des cookies lorsque ceux-ci sont autorisés sur le navigateur client.text/html. 4. Encore une fois. Windows NT 5. Comme il a reçu un jeton de session du client.1 Host: localhost:8080 User-Agent: Mozilla/5. 4. 3. 4.1 Location: http://localhost:8080/personne4/ Transfer-Encoding: chunked Date: Tue. c'est le fonctionnement normal si les cookies du navigateur sont actifs. 9. Maintenant.8. 3.q=0. 8.charset=ISO-8859-1 Content-Length: 2376 Date: Tue.2 Accept-Encoding: gzip. 3.1.8. Le serveur a répondu la chose suivante à cette seconde demande : 1. le navigateur affiche-t-il cette Url.0. U.application/xml.0 (Windows. On notera qu'il n'envoie plus le jeton de session.7. 6. par l'exemple 126/264 .7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC C'est exactement la même demande que la fois précédente avec cependant une différence : ligne 10. avec le même navigateur.application/xhtml+xml. Pour la même raison. HTTP/1.

q=0. 8.3 Accept: text/xml. 23 May 2006 09:39:55 GMT GET /personne4/main.en.0 (Windows.3 Accept: text/xml. Cela signifie qu'il continue la session précédente.q=0. 6.q=0. 24. On peut constater que bien que le navigateur ne lui ait pas envoyé de cookie de session.*/*.jsessionid=911B8156E0A9D32C2D256020C898E05C Content-Type: text/html. rv:1.8.application/xml. 25.text/plain.q=0.text/html.1 Content-Type: text/html. par l'exemple . il ne redémarre cependant pas une nouvelle session comme on aurait pu s'y attendre. 127/264 Les bases du développement web MVC en Java.image/png. lignes 19-27 : la demande n° 2 du navigateur.7. 4.charset=ISO-8859-1 Content-Length: 0 Date: Tue.deflate Accept-Charset: ISO-8859-1. 30.7.application/xml.en. Les échanges HTTP ne sont pourtant pas exactement les mêmes : 1. fr. U.2 Accept-Encoding: gzip. Nous obtenons la réponse suivante : Nous obtenons le même résultat que précédemment.fr.de. 3.7 Keep-Alive: 300 Connection: keep-alive HTTP/1. Tout d'abord nous réinitialisons le navigateur : 1 2 En [1]. 19.*. Puis nous demandons l'Url [http://localhost:8080/personne4].Que se passe-t-il avec un navigateur sur lequel on a inhibé les cookies ? Essayons.text/html.x 302 Déplacé Temporairement Server: Apache-Coyote/1.*.1.6. 15. 20.5.3) Gecko/20060426 Firefox/1.q=0. rv:1. 28.7 Keep-Alive: 300 Connection: keep-alive HTTP/1. 16. 31. Il a pu retrouver celle-ci grâce au jeton de session présent dans l'Url demandée par le navigateur ligne 19. On voit cela au fait qu'il n'envoie l'entête HTTP [Set-Cookie] comme il l'avait fait ligne 13. 33. 7. GET /personne4/ HTTP/1.1 Set-Cookie: JSESSIONID=911B8156E0A9D32C2D256020C898E05C.q=0. 23.en-us. 32.image/png.application/xhtml+xml.8. 5.q=0.4.q=0.q=0. lignes 11-17 : la réponse du serveur qui lui demande de se rediriger vers une autre Url.8. 29.2 Accept-Encoding: gzip.q=0.1 Host: localhost:8080 User-Agent: Mozilla/5.6.jsessionid=911B8156E0A9D32C2D256020C898E05C HTTP/1.9.x 200 OK Server: Apache-Coyote/1. 9. 17. lignes 29-33 : la réponse du serveur. 21. 26.5 Accept-Language: fr-fr.utf-8. 23 May 2006 09:39:55 GMT • • • • lignes 1-9 : la demande n° 1 du navigateur.8.q=0. 22.3) Gecko/20060426 Firefox/1.0.5.q=0. 14.utf-8.*/*. Il ne renvoie pas le cookie de session que vient de lui envoyer le serveur parce que ses cookies sont inhibés. 10. 13. 2.application/xhtml+xml. 12.0. Windows NT 5. 11.q=0.q=0.9.q=0.q=0.8.charset=ISO-8859-1 Content-Length: 2376 Date: Tue.0.0 (Windows. Il envoie un cookie de session ligne 13 : la balise <c:redirect> a inclus le jeton dans l'Url de redirection ligne 14.q=0. 18.1.1 Host: localhost:8080 User-Agent: Mozilla/5. Path=/personne4 Location: http://localhost:8080/personne4/main.deflate Accept-Charset: ISO-8859-1.q=0.de. U.fr.5 Accept-Language: fr-fr. 27. nous inhibons les cookies et en [2] nous supprimons ceux qui existent déjà afin de partir d'une situation connue.4. Il n'envoie pas de cookie de session.en-us. Windows NT 5.8.text/plain.0. fr.

2 Le projet Eclipse Pour créer le projet Eclipse [mvc-personne-07] de l'application web [/personne7]. le navigateur client n'envoie pas le cookie de session qu'il a reçu du serveur lors de la première demande de cette même Url. avec le même navigateur. page 78. puisque ses cookies sont inhibés. comme il avait été fait lorsque les cookies étaient autorisés.xml de l'application /personne7 est le suivant : Les bases du développement web MVC en Java.2. On obtient alors la page suivante : On a un résultat différent de celui obtenu lorsque les cookies étaient autorisés : le jeton de session est dans l'Url affichée par le navigateur. [cookies inhibés] • lors de la seconde demande de l'Url [http://localhost:8080/personne4].On retiendra que le serveur suit une session en récupérant le jeton de session envoyé par le client. La balise <c:redirect> inclut donc le jeton de session dans l'adresse de redirection. Les balises <c:redirect> et <c:url> permettent d'inclure le jeton de session dans les Url. on dupliquera le projet [mvc-personne-06] en suivant la procédure décrite au paragraphe 6. 12. C'est la solution qui est proposée ici. C'est pourquoi on le trouve sur la copie d'écran ci-dessus. le navigateur client avait renvoyé le cookie de session qu'il avait reçu du serveur lors de la première demande de cette même Url. La balise <c:redirect> n'avait donc pas inclus le jeton de session dans l'adresse de redirection. Exliquons ce résultat sans étudier les échanges HTTP qui ont eu lieu : [cookies autorisés] • lors de la seconde demande de l'Url [http://localhost:8080/personne4].3 Configuration de l'application web [personne7] 128/264 Le fichier web. demandons de nouveau l'Url [http://localhost:8080/personne4] en la tapant à la main. 12. par l'exemple . de deux façons possibles : • dans l'entête HTTP [Set-Cookie] envoyé par le client • dans l'Url demandée par le client Maintenant.

0" encoding="UTF-8"?> . <%@ page language="java" contentType="text/html. c. 29.. 30. Cependant elles conservent les balises JSTL des dernières versions. 8. réponse. . 25. 22. 23. 19. 21..1 La vue [formulaire] On a enlevé les boutons associés à du code Javascript.1.. 33.jsp] : 1. 15. 2. 2.jsp] ne change pas.a. 34. 36. erreurs] redeviennent ce qu'elles étaient dans la version 2. 24. 35. 12. La page d'accueil [index. 4. sans Javascript. 5. 6. <c:redirect url="/do/formulaire"/> 12..formulaire</h2> <hr> <form name="frmPersonne" action="<c:url value="validationFormulaire"/>" method="post"> <table> <tr> <td>Nom</td> <td><input name="txtNom" value="${nom}" type="text" size="20"></td> </tr> <tr> <td>Age</td> <td><input name="txtAge" value="${age}" type="text" size="3"></td> </tr> <tr> </table> <table> <tr> <td><input type="submit" name="bouton" value="Envoyer"></td> <td><input type="reset" value="Rétablir"></td> <td><input type="submit" name="bouton" value="Effacer"></td> </tr> </table> </form> </center> </body> </html> Les bases du développement web MVC en Java. 7. 17. 4.d. 11. 10. 28.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c.formulaire</title> </head> <body> <center> <h2>Personne . 18.tld" prefix="c" %> <html> <head> <title>Personne . 3. 9. 14. 27. 3. [formulaire. 13. 2. <display-name>mvc-personne-07</display-name> . Ce fichier est identique à celui de la version précédente hormis la ligne 3 où le nom d'affichage de l'application web a changé en [mvc-personne-07].4 Le code des vues Les vues [formulaire.. par l'exemple 129/264 .4. 26. 16. 31. 20. 32. 12. <?xml version="1. 1.. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.

16. 27.tld" prefix="c" %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne . 11. <%@ page language="java" contentType="text/html.réponse</h2> <hr> <table> <tr> <td>Nom</td> <td>${nom}</td> </tr> <tr> <td>Age</td> <td>${age}</td> </tr> </table> <br> <a href="<c:url value="retourFormulaire"/>">${lienRetourFormulaire}</a> </body> </html> • ligne 24 : l'Url cible du HREF est écrite avec la balise <c:url> afin que le jeton de session y soit au cas où le client serait un navigateur n'envoyant pas l'entête HTTP [Cookie]. 7.jsp] : Les bases du développement web MVC en Java. 24.4. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 22. le formulaire a deux boutons de type [submit] : [Envoyer] (ligne 28) et [Effacer] (ligne 30). 25. 19. 10. 2. 18.2 La vue [réponse] [réponse. Les deux boutons portent le même nom : bouton. 13. par l'exemple 130/264 . 4. 26. 5.3 La vue [erreurs] [erreurs. 12. 15. 20.jsp] : 1. 6.• • ligne 14 : l'Url cible du POST est écrite avec la balise <c:url> afin que le jeton de session y soit au cas où le client serait un navigateur n'envoyant pas l'entête HTTP [Cookie].4. 14. 9. Au moment du POST. 12. le navigateur enverra le paramètre : • bouton=Envoyer si le POST a été provoqué par le bouton [Envoyer] • bouton=Effacer si le POST a été provoqué par le bouton [Effacer] C'est ce paramètre qui nous aidera à définir l'action exacte à faire. 17. l'Url [/do/validationFormulaire] correspondant maintenant à deux actions distinctes. 12. 21. 3. 8.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c. 23.

30. 5.. 36. 21."urlReponse". 3. private Map params=new HashMap<String. 10. } // autres cas doInit(request.equals("get") && action. 20.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c. 44.getPathInfo(). ServletException { . } // GET @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request. 40.. . 4. 22. Le contrôleur [ServletPersonne] de l'application web [/personne7] est le suivant : .equals("post") && action.String>(). 9. return. 14. 38. 2. 19.getMethod(). 25. 29. 24. private ArrayList erreursInitialisation = new ArrayList<String>(). 19. if(méthode. 9.toLowerCase(). . 11. 12. 16. 15. 45. // on récupère l'action à exécuter String action=request. 31. 8..... 12. 35. 18. 6.equals("/retourFormulaire")){ // retour au formulaire de saisie doRetourFormulaire(request. 4. 12.response). HttpServletResponse response) throws ServletException.. 6. 33. 39. 10. 23. 14. par l'exemple 131/264 . 34.equals("/validationFormulaire")){ // validation du formulaire de saisie doValidationFormulaire(request.servlets. // init @SuppressWarnings("unchecked") public void init() throws ServletException { .1. } 48. 7. } // affichage formulaire vide void doInit(HttpServletRequest request. 37.st.. @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramètres d'instance private String urlErreurs = null. // on récupère la méthode d'envoi de la requête String méthode=request. 27.5 1. HttpServletResponse response) throws IOException. 13. 7. 43. 49. Le contrôleur [ServletPersonne] package istia. 28. 15. 13. return. 42. 16. 20.response). charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4. 32. 3. 17. 5."lienRetourFormulaire"}. 21. 2.. 18. 17. 11. 26. private String[] paramètres={"urlFormulaire". } if(méthode.. 8.response). 47. // affichage formulaire pré-rempli Les bases du développement web MVC en Java.tld" prefix="c" %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach> </ul> <br> <a href="<c:url value="retourFormulaire"/>">${lienRetourFormulaire}</a> </body> </html> • ligne 18 : l'Url cible du HREF est écrite avec la balise <c:url> afin que le jeton de session y soit au cas où le client serait un navigateur n'envoyant pas l'entête HTTP [Cookie]. 41. Le lecteur est invité à tester ces nouvelles vues selon le principe vu dans les versions précédentes.personne. <%@ page language="java" contentType="text/html. IOException{ 46.

setAttribute("nom".equals(bouton)){ 80.response). if("effacer". 78.getRequestDispatcher(urlErreurs).setAttribute("lienRetourFormulaire". return.setAttribute("erreurs". session. // on envoie la page d'erreurs 107. } 117. 77. } 123. 104. throws IOException. 55. session. 54. response). getServletContext().get("lienRetourFormulaire")). if (erreursAppel. erreursAppel). 60. HttpServletResponse response) throws ServletException. 66. void doRetourFormulaire(HttpServletRequest request.getParameter("txtAge"). return. "").equals(bouton)){ 84. } // affichage formulaire vide void doEffacer(HttpServletRequest request. HttpServletResponse response) throws ServletException. String age = request. doEffacer(request. 98. // validation du formulaire 70. session. IOException{ 92. 53. 94. La méthode [doValidationFormulaire] fait traiter ces deux cas par deux méthodes différentes. // on récupère le bouton qui a provoqué le POST 73. IOException{ // on affiche le formulaire getServletContext().getRequestDispatcher((String)params. 63. } 87.setAttribute("nom". return.forward( request. 56. void doValidationFormulaire(HttpServletRequest request. Les bases du développement web MVC en Java.size() != 0) { 106. 57. public void doPost(HttpServletRequest request.. // le lien du retour au formulaire est mis dans le modèle des vues [réponse.get("urlReponse")). } 79. void doEnvoyer(HttpServletRequest request. // validation du formulaire 90. String bouton = request.response). 52. 51. // qu'on mémorise dans la session 96. HttpSession session = request. 103. response).getRequestDispatcher((String)params. 99. 86. (String)params. 114. lignes 70-87 : l'action [/validationFormulaire] est faite par un POST provoqué par un clic sur l'un des boutons [Envoyer] ou [Effacer] de la vue [formulaire].get("urlFormulaire")). // vérification des paramètres 102. return.50. request. ""). // traitement selon le bouton qui a provoqué le POST 75. 58. 95. 69. 101. 64. // post 119.getParameter("txtNom"). } 88. response).} • • ligne 35 : l'action [/retourFormulaire] est faite par un GET et non plus par un POST comme dans la version précédente. age). 108. ServletException { 121. return. 97. // on récupère les paramètres 93.. 122.response). ArrayList<String> erreursAppel = new ArrayList<String>(). 65. IOException{ // on prépare le modèle du formulaire HttpSession session = request. HttpServletResponse response) throws ServletException. doInit(request. request. String nom = request. erreurs] 100. // on affiche le formulaire getServletContext().getRequestDispatcher((String)params. 71. .. } 83.. getServletContext(). session.forward( 109.forward(request. IOException{ 72. 111. 85. 61. if(bouton==null){ 76. 62. 115.setAttribute("age". 89. 81. } 112. HttpServletResponse response) 120. par l'exemple 132/264 . return.toLowerCase().getSession(true). } 59.setAttribute("age". response).getParameter("bouton"). request. HttpServletResponse response) throws ServletException. // des erreurs dans les paramètres ? 105.on envoie la page réponse 113.forward( request. return. nom). if("envoyer".getSession(true). 91. // les paramètres sont corrects . doEnvoyer(request. 67.get("urlFormulaire")). 110. 118. 116. 82. 68. 74..

le jeton de session est dans l'Url cible du POST. 2. lignes 58-67 : la nouvelle méthode [doEffacer] doit afficher un formulaire vide. age] déjà dans la session. age] de la session afin que celle-ci continue à refléter le dernier état du formulaire. 4. Dans Eclipse. <br> <a href="retourFormulaire.. par l'exemple 133/264 . 3. Ce modèle est en fait constitué des éléments [nom.. lignes 50-55 : demandent l'affichage de la vue [formulaire] sans initialisation apparente du modèle de cette vue.jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3">Retour au formulaire</a> </body> Ligne 3. .6 Tests Lancer ou relancer Tomcat après y avoir intégré le projet Eclipse [personne-mvc-07] puis demander l'url [http://localhost:8080/personne7] avec un navigateur dont on a inhibé les cookies et supprimé ceux existant déjà. Ici.. <form name="frmPersonne" action="validationFormulaire. le jeton de session est dans l'Url cible du lien. 12. 13 Application web MVC [personne] – version 8 La version 8 sera identique à la version 7 mais déployée dans une archive war (Web ARchive). Il n'y a pas lieu de faire davantage. On pourrait faire appel à la méthode [doInit] qui fait déjà ce travail. </form> Ligne 1. Remplissons le formulaire et validons-le : Le code source reçu par le navigateur est le suivant : 1. 3.• • • lignes 90-103 : la méthode [doEnvoyer] correspond à la méthode [doValidationFormulaire] de la version précédente. on en profite pour également effacer les éléments [nom. On obtient la réponse suivante : Le code source reçu par le navigateur est le suivant : 1. Les données saisies sont mises dans la session (lignes 96-98) alors dans la version précédente elles étaient placées dans la requête. 2.jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3" method="post"> .. nous cliquons droit sur le projet [mvc-personne-07] et prenons l'option [export] : Les bases du développement web MVC en Java.

zip et peut être décompressé avec les mêmes outils. Nous lançons Tomcat via l'option adéquate du menu [Démarrer] puis nous demandons l'Url [http://localhost:8080] et suivons le lien [Tomcat Manager] : Les bases du développement web MVC en Java.2. ici [mvc-personne-07] et avec le bouton [Browse] indiquons le fichier . page 104 pour le déploiement de la documentation de la bibliothèque JSTL. par l'exemple 134/264 . les codes source ayant été remplacés par leurs équivalents compilés placés dans [WEB-INF/classes] comme l'exige la norme de déploiement des servlets.war à produire.1. Terminons le processus avec le bouton [Finish] et avec l'explorateur windows allons voir le fichier qui a été produit : Un fichier .war] au sein de Tomcat en suivant la procédure décrite au paragraphe 8.1 2 Choisissons dans la liste déroulante [1] le nom du module à exporter. ici [personne8.war est analogue à un fichier .war]. Nous allons déployer l'application web [personne8. Décompressons-le et passons en revue tous les éléments de son arborescence : Nous pouvons constater que tous les éléments du projet [mvc-personne-07] sont là.

La copie d'écran ne le montre pas.Nous obtenons alors une page d'authentification. par l'exemple 135/264 . Le bouton [Deploy] enregistre et déploie cette application au sein de Tomcat. mais nous avons sélectionné le fichier [personne8. comme il a été montré au paragraphe 2. Nous obtenons une page listant les applications actuellement déployées dans Tomcat : Nous pouvons ajouter une nouvelle application grâce à des formulaires placés en bas de la page : Nous utilisons le bouton [Parcourir] pour désigner un fichier .war] créé précédemment. Les bases du développement web MVC en Java.3.3. Nous nous identifions comme manager / manager ou admin / admin.war à déployer. page 14.

xml] par la balise <diplay-name>. Ce nom est fixé dans le fichier [web. L’application web que nous allons écrire va permettre de gérer un groupe de personnes avec quatre opérations : • • • • couche [metier] Modèles couche [dao] Données liste des personnes du groupe ajout d’une personne au groupe modification d’une personne du groupe suppression d’une personne du groupe On reconnaîtra les quatre opérations de base sur une table de base de données. Nous écrirons deux versions de cette application : Les bases du développement web MVC en Java. 14 Application web MVC dans une architecture 3tier – Exemple 1 14.war]. par l'exemple 136/264 . La colonne [2] montre le nom d'affichage de l'application. L'archivage d'une application web dans un fichier . Dans l'application [mvc-personne-07] archivée dans [personne8. Nous présentons maintenant.war est le mode normal de distribution et de déploiement d'une application web.1 Présentation Jusqu’à maintenant. C'est ce que montre [1]. le contexte de l'application (ou le nom de l'application) sera XX. Ouvrons un navigateur et demandons l'url [http://localhost:8080/personne8] : Le lecteur est invité à poursuivre les tests. Pour cela. ce que montre [2]. page 58. Elle aura la particularité d’utiliser les trois couches d’une architecture 3tier : Application web couche [web] Servlet Utilisateur JSP1 JSP2 JSPn Le lecteur est invité à relire les principes d'une application web MVC dans une architecture 3tier s'il les a oubliés. au paragraphe 4.1 2 Si on déploie un fichier [XX. nous nous sommes contentés d’exemples à visée pédagogique.jar] on avait : <display-name>mvc-personne-07</display-name> Le nom d'affichage de l'application est donc [mvc-personne-07]. une application basique mais néanmoins plus riche que toutes celles présentées jusqu’à maintenant. ils se devaient d’être simples.

• • dans la version 1. nous placerons le groupe de personnes dans une table de base de données. Cela permettra au lecteur de tester l’application sans contrainte de base de données. Les copies d’écran qui suivent montrent les pages que l’application échange avec l’utilisateur. On ajoute une personne -> On retouve la personne telle qu’elle a été modifiée. par l'exemple 137/264 . dans la version 2. On la modifie maintenant -> On modifie la date de naissance. l’état marital. la couche [dao] n’utilisera pas de base de données. Une liste initiale de personnes est tout d’abord présentée à l’utilisateur. On la supprime maintenant -> Les bases du développement web MVC en Java. Les personnes du groupe seront stockées dans un simple objet [ArrayList] géré en interne par la couche [dao]. le nombre d’enfants et on valide -> Elle n’est plus là. Nous montrerons que cela se fera sans impact sur la couche web de la version 1 qui restera inchangée. Il peut ajouter une personne -> L’utilisateur a créé une nouvelle personne qu’il valide avec le bouton [Valider] -> La nouvelle personne a été ajoutée.

Les erreurs de saisie sont signalées -> On notera que le formulaire a été renvoyé tel qu’il a été saisi (Nombre d’enfants). Le lien [Annuler] permet de revenir à la liste des personnes -> 14.2 Le projet Eclipse Le projet de l’application s’appelle [personnes-01] : Ce projet recouvre les trois couches de l’architecture 3tier de l’application : Les bases du développement web MVC en Java. par l'exemple 138/264 .

} // constructeur d'une personne par recopie d'une autre personne Les bases du développement web MVC en Java. Date dateNaissance.st. 35. // la date de naissance private Date dateNaissance. nous serons peut-être parfois un peu rapides sur les explications sauf lorsque ce qui est présenté est nouveau. // constructeur par défaut public Personne() { } // constructeur avec initialisation des champs de la personne public Personne(int id. 33. 29. 31.util.mvc. celles-ci sont représentées par une classe [Personne] : La classe [Personne] est la suivante : 1. 36. public class Personne { // identifiant unique de la personne private int id.Date. boolean marie. 30. 9. Parce que ce serait trop long à écrire et peutêtre trop ennuyeux à lire. 28. int nbEnfants) { setId(id). 39.web] le paquetage [istia.st. 38. String nom. 18.entites] contient les objets partagés entre différentes couches le paquetage [istia..setters . 42. 24. 32. // getters . 22.Application web couche [web] Servlet Utilisateur JSP1 JSP2 JSPn • • • • • couche [metier] Modèles couche [dao] Données la couche [dao] est contenue dans le paquetage [istia. import java. 4. // le nombre d'enfants private int nbEnfants. 40. 23. 7. // la version actuelle private long version. 19.springmvc. import java.st. 5.mvc. // le nom private String nom. 2.mvc. 15.st.. 25.service] la couche [web] ou [ui] est contenue dans le paquetage [istia.entites. // l'état marital private boolean marie = false.text. 34. 12. 14. 16. setPrenom(prenom). package istia. setDateNaissance(dateNaissance). 21.personnes. String prenom. 20. setNom(nom). 14.personnes.SimpleDateFormat.st. 8. [service] et [web]. 10.personnes. 6.mvc. 3. 13. Formellement. setNbEnfants(nbEnfants).mvc. 17. par l'exemple 139/264 . setMarie(marie). Les copies d’écran page 137 ont montré certaines des caractéristiques d’une personne. 11. 26.st.3 La représentation d’une personne L’application gère un groupe de personnes. 37. 27.personnes. // le prénom private String prenom.tests] contient les tests Junit des couches [dao] et [service] Nous allons explorer successivement les trois couches [dao].personnes.dao] la couche [metier] ou [service] est contenue dans le paquetage [istia.personnes. 41.

D’un point de vue objet. } 60. On va alors pouvoir dire à l’utilisateur U2 que quelqu’un est passé avant lui et qu’il doit repartir de la nouvelle version de la personne P. U2 passe le nom de la personne P en majuscules.format(dateNaissance) 58.getVersion()). un utilisateur U1 entre en modification d’une personne P." + version + ". La notion de version de personne nous aide à résoudre ce problème. alors qu’actuellement la version de celle-ci est V2.getPrenom()). le nombre d’enfants est 0.getId()). un utilisateur U2 entre en modification de la même personne P. ligne 55 : la méthode [toString] est redéfinie pour rendre une chaîne de caractères représentant l’état de la personne 14.getMarie()). U2 voit le nombre d’enfants à 0. setDateNaissance(p. Au final. Il passe ce nombre à 1 mais avant qu’il ne valide sa modification. public String toString() { 56. Sa modification sera acceptée si la personne P enregistrée a toujours la version V2. 47. setNom(p. on va s’apercevoir qu’il détient une version V1 de la personne P. public Personne(Personne p) { 44. setVersion(p. // toString 55. On a alors deux objets de contenu identique mais référencés par deux pointeurs différents. setId(p. 45. 50. Ce sera le cas de l’utilisateur U1. par l'exemple . 49. 48. A ce moment. U2 voit le nombre d’enfants à 0 et la version à V1. les modifications faites par U1 et U2 seront prises en compte alors que dans le cas d’usage sans version. On reprend le même cas d’usage : Au temps T1. setPrenom(p. 51. return "[" + id + ". + ". un utilisateur U2 entre en modification de la même personne P. Sa modification est donc acceptée et on change alors la version de la personne modifiée de V1 à V2 pour noter le fait que la personne a subi un changement. on vérifie que celui qui modifie une personne P détient la même version que la personne P actuellement enregistrée. • • • lignes 32-40 : un constructeur capable d’initialiser les champs d’une personne. Puisque U1 n’a pas encore validé sa modification. L’un d’entre-eux est le suivant : Au temps T1.43." 57. passera le nom en majuscules. C’est la modification de U2 qui va gagner : le nom va passer en majuscules et le nombre d’enfants va rester à zéro alors même que U1 croit l’avoir changé en 1. setNbEnfants(p." + nom + ". + new SimpleDateFormat("dd/MM/yyyy"). Puisque U1 n’a pas encore validé sa modification. l’une des modifications était perdue. Puis U1 et U2 valident leurs modifications dans cet ordre. 59. lignes 43-51 : un constructeur qui crée une copie de la personne qu’on lui passe en paramètre. setMarie(p. } • • une personne est identifiée par les informations suivantes : • id : un n° identifiant de façon unique une personne • nom : le nom de la personne • prenom : son prenom • dateNaissance : sa date de naissance • marie : son état marié ou non • nbEnfants : son nombre d’enfants l’attribut [version] est un attribut artificiellement ajouté pour les besoins de l’application. Lors de la validation de la modification de U2. On omet le champ [version]. le nombre d’enfants est 0 et la version V1. récupèrera une personne P de version V2 qui a maintenant un enfant. Puis U1 et U2 valident leurs modifications dans cet ordre. un utilisateur U1 entre en modification d’une personne P. validera. 46. 54." + prenom + "." + nbEnfants + "]". Avant de valider une modification. Il passe le nombre d’enfants à 1 mais avant qu’il ne valide sa modification. U2 passe le nom de la personne P en majuscules. 53. il aurait été sans doute préférable d’ajouter cet attribut dans une classe dérivée de [Personne]. Il le fera. Son besoin apparaît lorsqu’on fait des cas d’usage de l’application web." + marie + ".getDateNaissance()).getNom()).getNbEnfants()). A ce moment.4 La couche [dao] La couche [dao] est constituée des classes et interfaces suivantes : • [IDao] est l’interface présentée par la couche [dao] 140/264 Les bases du développement web MVC en Java. } 52.

personnes.personnes. 16. par exemple NomManquantException. lancées par la couche [dao] L’interface [IDao] est la suivante : 1. import java. lignes 8-10 : la méthode qui permettra au code de gestion d’une exception d’en récupérer le code d’erreur. 15.personnes. 13. 6. // obtenir une personne particulière Personne getOne(int id). 17. public class DaoException extends RuntimeException { // code erreur private int code. package istia.• • [DaoImpl] est une implémentation de celle-ci où le groupe de personnes est encapsulé dans un objet [ArrayList] [DaoException] est un type d’exceptions non contrôlées (unchecked). 10. 9. 2. } } • ligne 3 : la classe [DaoException] dérivant de [RuntimeException] est un type d’exception non contrôlée : le compilateur ne nous oblige pas à : • gérer ce type d’exceptions avec un try / catch lorsqu’on appelle une méthode pouvant la lancer • mettre le marqueur " throws DaoException " dans la signature d’une méthode susceptible de lancer l’exception Cette technique nous évite d’avoir à signer les méthodes de l’interface [IDao] avec des exceptions d’un type particulier. public int getCode() { return code. AgeIncorrectException. import istia.personnes.st. this. 4. 15. } • l’interface a quatre méthodes pour les quatre opérations que l’on souhaite faire sur le groupe de personnes : • getAll : pour obtenir une collection de personnes • getOne : pour obtenir une personne ayant un id précis • saveOne : pour ajouter une personne (id=-1) ou modifier une personne existante (id <> -1) • deleteOne : pour supprimer une personne ayant un id précis La couche [dao] est susceptible de lancer des exceptions. 16. } // constructeur public DaoException(String message. 5.st. 7.dao. 3. 8.int code) { super(message). 7. 10. Cela permettra à la couche qui décidera de gérer l’exception de connaître l’origine exacte de l’erreur et de prendre ainsi les mesures appropriées. .springmvc. 9.springmvc. 3. 4.entites. import istia. Il y a d’autres façons d’arriver au même résultat. L’une d’elles est de créer un type d’exception pour chaque type d’erreur possible. 3. 14. 11.st. 5.st. // supprimer une personne void deleteOne(int id). Les bases du développement web MVC en Java. • ligne 6 : un code d’erreur. par l'exemple 141/264 . // ajouter/modifier une personne void saveOne(Personne personne).util. • • La classe [DaoImpl] implémente l’interface [IDao] : 1.st.springmvc.. La couche [dao] lancera diverses exceptions qui seront identifiées par des codes d’erreur différents.Personne. 12.code=code..Collection. 4. Celles-ci seront de type [DaoException] : 1. 14.personnes.dao. Toute implémentation lançant des exceptions non contrôlées sera alors acceptable amenant ainsi de la souplesse dans l’architecture. package istia. 6.dao. package istia. 12.springmvc. 11. 8. public interface IDao { // liste de toutes les personnes Collection getAll(). 2.springmvc.Personne.entites. 13. lignes 13-16 : le constructeur qui permettra de créer une exception identifiée par un code d’erreur ainsi qu’un message d’erreur. 2. PrenomManquantException.

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. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89.

import import import import

java.text.ParseException; java.text.SimpleDateFormat; java.util.ArrayList; java.util.Collection;

public class DaoImpl implements IDao { // une liste de personnes private ArrayList personnes = new ArrayList(); // n° de la prochaine personne private int id = 0; // initialisations public void init() { try { Personne p1 = new Personne(-1, "Joachim", "Major", new SimpleDateFormat("dd/MM/yyyy").parse("13/11/1984"), true, 2); saveOne(p1); Personne p2 = new Personne(-1, "Mélanie", "Humbort", new SimpleDateFormat("dd/MM/yyyy").parse("12/02/1985"), false, 1); saveOne(p2); Personne p3 = new Personne(-1, "Charles", "Lemarchand", new SimpleDateFormat("dd/MM/yyyy").parse("01/03/1986"), false, 0); saveOne(p3); } catch (ParseException ex) { throw new DaoException( "Erreur d'initialisation de la couche [dao] : " + ex.toString(), 1); } } // liste des personnes public Collection getAll() { return personnes; } // obtenir une personne en particulier public Personne getOne(int id) { // on cherche la personne int i = getPosition(id); // a-t-on trouvé ? if (i != -1) { return new Personne(((Personne) personnes.get(i))); } else { throw new DaoException("Personne d'id [" + id + "] inconnue", 2); } } // ajouter ou modifier une personne public void saveOne(Personne personne) { // le paramètre personne est-il valide ? check(personne); // ajout ou modification ? if (personne.getId() == -1) { // ajout personne.setId(getNextId()); personne.setVersion(1); personnes.add(personne); return; } // modification - on cherche la personne int i = getPosition(personne.getId()); // a-t-on trouvé ? if (i == -1) { throw new DaoException("La personne d'Id [" + personne.getId() + "] qu'on veut modifier n'existe pas", 2); } // a-t-on la bonne version de l'original ? Personne original = (Personne) personnes.get(i); if (original.getVersion() != personne.getVersion()) { throw new DaoException("L'original de la personne [" + personne + "] a changé depuis sa lecture initiale", 3); } // on attend 10 ms //wait(10); // c'est bon - on fait la modification original.setVersion(original.getVersion()+1); original.setNom(personne.getNom()); original.setPrenom(personne.getPrenom()); original.setDateNaissance((personne.getDateNaissance())); original.setMarie(personne.getMarie());

Les bases du développement web MVC en Java, par l'exemple

142/264

90. original.setNbEnfants(personne.getNbEnfants()); 91. } 92. 93. // suppression d'une personne 94. public void deleteOne(int id) { 95. // on cherche la personne 96. int i = getPosition(id); 97. // a-t-on trouvé ? 98. if (i == -1) { 99. throw new DaoException("Personne d'id [" + id + "] inconnue", 2); 100. } else { 101. // on supprime la personne 102. personnes.remove(i); 103. } 104. } 105. 106. // générateur d'id 107. private int getNextId() { 108. id++; 109. return id; 110. } 111. 112. // rechercher une personne 113. private int getPosition(int id) { 114. int i = 0; 115. boolean trouvé = false; 116. // on parcourt la liste des personnes 117. while (i < personnes.size() && !trouvé) { 118. if (id == ((Personne) personnes.get(i)).getId()) { 119. trouvé = true; 120. } else { 121. i++; 122. } 123. } 124. // résultat ? 125. return trouvé ? i : -1; 126. } 127. 128. // vérification d'une personne 129. private void check(Personne p) { 130. // personne p 131. if (p == null) { 132. throw new DaoException("Personne null", 10); 133. } 134. // id 135. if (p.getId() != -1 && p.getId() < 0) { 136. throw new DaoException("Id [" + p.getId() + "] invalide", 11); 137. } 138. // date de naissance 139. if (p.getDateNaissance() == null) { 140. throw new DaoException("Date de naissance manquante", 12); 141. } 142. // nombre d'enfants 143. if (p.getNbEnfants() < 0) { 144. throw new DaoException("Nombre d'enfants [" + p.getNbEnfants() 145. + "] invalide", 13); 146. } 147. // nom 148. if (p.getNom() == null || p.getNom().trim().length() == 0) { 149. throw new DaoException("Nom manquant", 14); 150. } 151. // prénom 152. if (p.getPrenom() == null || p.getPrenom().trim().length() == 0) { 153. throw new DaoException("Prénom manquant", 15); 154. } 155. } 156. 157. // attente 158. private void wait(int N) { 159. // on attend N ms 160. try { 161. Thread.sleep(N); 162. } catch (InterruptedException e) { 163. // on affiche la trace de l'exception 164. e.printStackTrace(); 165. return; 166. } 167. } 168.}

Nous n’allons donner que les grandes lignes de ce code. Nous passerons cependant un peu de temps sur les parties les plus délicates.
Les bases du développement web MVC en Java, par l'exemple

143/264

• •

ligne 13 : l’objet [ArrayList] qui va contenir le groupe de personnes ligne 16 : l’identifiant de la dernière personne ajoutée. A chaque nouvel ajout, cet identifiant va être incrémenté de 1.

La classe [DaoImpl] va être instanciée en un unique exemplaire. C’est ce qu’on appelle un singleton. Une application web sert ses utilisateurs de façon simultanée. Il y a à un moment donné plusieurs threads exécutés par le serveur web. Ceux-ci se partagent les singletons : • celui de la couche [dao] • celui de la couche [service] • ceux des différents contrôleurs, validateurs de données, ... de la couche web Si un singleton a des champs privés, il faut tout de suite se demander pourquoi il en a. Sont-ils justifiés ? En effet, ils vont être partagés entre différents threads. S’ils sont en lecture seule, cela ne pose pas de problème s’ils peuvent être initialisés à un moment où on est sûr qu’il n’y a qu’un thread actif. On sait en général trouver ce moment. C’est celui du démarrage de l’application web alors qu’elle n’a pas commencé à servir des clients. S’ils sont en lecture / écriture alors il faut mettre en place une synchronisation d’accès aux champs sinon on court à la catastrophe. Nous illustrerons ce problème lorsque nous testerons la couche [dao].
• • • •

la classe [DaoImpl] n’a pas de constructeur. C’est donc son constructeur par défaut qui sera utilisé. lignes 19-38 : la méthode [init] sera appelée au moment de l’instanciation du singleton de la couche [dao]. Elle crée une liste de trois personnes. lignes 41-43 : implémente la méthode [getAll] de l’interface [IDao]. Elle rend une référence sur la liste des personnes. lignes 46-55 : implémente la méthode [getOne] de l’interface [IDao]. Son paramètre est l’id de la personne cherchée. Pour la récupérer, on fait appel à une méthode privée [getPosition] des lignes 113-126. Cette méthode rend la position dans la liste, de la personne cherchée ou -1 si la personne n’a pas été trouvée. Si la personne a été trouvée, la méthode [getOne] rend une référence (ligne 51) sur une copie de cette personne et non sur la personne elle-même. En effet, lorsqu’un utilisateur va vouloir modifier une personne, les informations sur celle-ci vont-être demandées à la couche [dao] et remontées jusqu’à la couche [web] pour modification, sous la forme d’une référence sur un objet [Personne]. Cette référence va servir de conteneur de saisies dans le formulaire de modification. Lorsque dans la couche web, l’utilisateur va poster ses modifications, le contenu du conteneur de saisies va être modifié. Si le conteneur est une référence sur la personne réelle du [ArrayList] de la couche [dao], alors celle-ci est modifiée alors même que les modifications n’ont pas été présentées aux couches [service] et [dao]. Cette dernière est la seule habilitée à gérer la liste des personnes. Aussi faut-il que la couche web travaille sur une copie de la personne à modifier. Ici la couche [dao] délivre cette copie. Si la personne cherchée n’est pas trouvée, une exception de type [DaoException] est lancée avec le code d’erreur 2 (ligne 53).

lignes 94-104 : implémente la méthode [deleteOne] de l’interface [IDao]. Son paramètre est l’id de la personne à supprimer. Si la personne à supprimer n’existe pas, une exception de type [DaoException] est lancée avec le code d’erreur 2. lignes 58-91 : implémente la méthode [saveOne] de l’interface [IDao]. Son paramètre est un objet [Personne]. Si cet objet à un id=-1, alors il s’agit d’un ajout de personne. Sinon, il s’agit de modifier la personne de la liste ayant cet id avec les valeurs du paramètre.

ligne 60 : la validité du paramètre [Personne] est vérifiée par une méthode privée [check] définie aux lignes 129-155. Cette méthode fait des vérifications basiques sur la valeur des différents champs de [Personne]. A chaque fois qu’une anomalie est détectée, une [DaoException] avec un code d’erreur spécifique est lancé. Comme la méthode [saveOne] ne gère pas cette exception, elle remontera à la méthode appelante. lignes 62 : si le paramètre [Personne] a son id égal à -1, alors il s’agit d’un ajout. L’objet [Personne] est ajouté à la liste interne des personnes (ligne 66), avec le 1er id disponible (ligne 64), et un n° de version égal à 1 (ligne 65). si le paramètre [Personne] a un [id] différent de -1, il s’agit de modifier la personne de la liste interne ayant cet [id]. Tout d’abord, on vérifie (lignes 70-75) que la personne à modifier existe. Si ce n’est pas le cas, on lance une exception de type [DaoException] avec le code d’erreur 2. si la personne est bien présente, on vérifie que sa version actuelle est la même que celle du paramètre [Personne] qui contient les modifications à apporter à l’original. Si ce n’est pas le cas, cela signifie que celui qui veut faire la modification de la personne n’en détient pas la dernière version. On le lui dit en lançant une exception de type [DaoException] avec le code d’erreur 3 (lignes 79-80). si tout va bien, les modifications sont faites sur l’original de la personne (lignes 85-90) 144/264

Les bases du développement web MVC en Java, par l'exemple

On sent bien que cette méthode doit être synchronisée. Par exemple, entre le moment où on vérifie que la personne à modifier est bien là et celui où la modification va être faite, la personne a pu être supprimée de la liste par quelqu’un d’autre. La méthode devrait être donc déclarée [synchronized] afin de s’assurer qu’un seul thread à la fois l’exécute. Il en est de même pour les autres méthodes de l’interface [IDao]. Nous ne le faisons pas, préférant déplacer cette synchronisation dans la couche [service]. Pour mettre en lumière les problèmes de synchronisation, lors des tests de la couche [dao] nous arrêterons l’exécution de [saveOne] pendant 10 ms (ligne 83) entre le moment où on sait qu’on peut faire la modification et le moment où on la fait réellement. Le thread qui exécute [saveOne] perdra alors le processeur au profit d’un autre. Nous augmentons ainsi nos chances de voir apparaître des conflits d’accès à la liste des personnes.

14.5

Tests de la couche [dao]

Un test JUnit est écrit pour la couche [dao] :

[TestDao] est le test JUnit. Pour mettre en évidence les problèmes d’accès concurrents à la liste des personnes, des threads de type [ThreadDaoMajEnfants] sont créés. Ils sont chargés d’augmenter de 1 le nombre d’enfants d’une personne donnée. [TestDao] a cinq tests [test1] à [test5]. Nous ne présentons que deux d’entre-eux, le lecteur étant invité à découvrir les autres dans le code source associé à cet article.
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. 38. 39. 40. 41. 42. 43. 44. 45. package istia.st.springmvc.personnes.tests; import java.text.ParseException; ... public class TestDao extends TestCase { // couche [dao] private DaoImpl dao; // constructeur public TestDao() { dao = new DaoImpl(); dao.init(); } // liste des personnes private void doListe(Collection personnes) { Iterator iter = personnes.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } } // test1 public void test1() throws ParseException { ... } // modification-suppression d'un élément inexistant public void test2() throws ParseException { ... } // gestion des versions de personne public void test3() throws ParseException, InterruptedException { ... } // optimistic locking - accès multi-threads public void test4() throws Exception { ... } // tests de validité de saveOne

Les bases du développement web MVC en Java, par l'exemple

145/264

46. public void test5() throws ParseException { 47. ... 48. }

• •

ligne 9 : référence sur l'implémentation de la couche [dao] testée lignes 12-15 : le constructeur du test JUnit. Il crée une instance de type [DaoImpl] de la couche [dao] à tester et l'initialise.

La méthode [test1] teste les quatre méthodes de l’interface [IDao] de la façon suivante :
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. 38. } public void test1() throws ParseException { // liste actuelle Collection personnes = dao.getAll(); int nbPersonnes = personnes.size(); // affichage doListe(personnes); // ajout d'une personne Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat( "dd/MM/yyyy").parse("01/02/2006"), true, 1); dao.saveOne(p1); int id1 = p1.getId(); // vérification - on aura un plantage si la personne n'est pas trouvée p1 = dao.getOne(id1); assertEquals("X", p1.getNom()); // modification p1.setNom("Y"); dao.saveOne(p1); // vérification - on aura un plantage si la personne n'est pas trouvée p1 = dao.getOne(id1); assertEquals("Y", p1.getNom()); // suppression dao.deleteOne(id1); // vérification int codeErreur = 0; boolean erreur = false; try { p1 = dao.getOne(id1); } catch (DaoException ex) { erreur = true; codeErreur = ex.getCode(); } // on doit avoir une erreur de code 2 assertTrue(erreur); assertEquals(2, codeErreur); // liste des personnes personnes = dao.getAll(); assertEquals(nbPersonnes, personnes.size());

• •

ligne 3 : on demande la liste des personnes ligne 6 : on affiche celle-ci
[1,1,Joachim,Major,13/01/1984,true,2] [2,1,Mélanie,Humbort,12/01/1985,false,1] [3,1,Charles,Lemarchand,01/01/1986,false,0]

Le test ensuite ajoute une personne, la modifie et la supprime. Ainsi les quatre méthodes de l’interface [IDao] sontelles utilisées.
• • •

• • • • •

lignes 8-10 : on ajoute une nouvelle personne (id=-1). ligne 11 : on récupère l’id de la personne ajoutée car l’ajout lui en a donné un. Avant elle n’en avait pas. ligne 13-14 : on demande à la couche [dao] une copie de la personne qui vient d’être ajoutée. Il faut se rappeler que si la personne demandée n’est pas trouvée, la couche [dao] lance une exception. On aura alors un plantage ligne 13. On aurait pu gérer ce cas plus proprement. Ligne 14, on vérifie le nom de la personne retrouvée. lignes 16-17 : on modifie ce nom et on demande à la couche [dao] d’enregistrer les modifications. lignes 19-20 : on demande à la couche [dao] une copie de la personne qui vient d’être ajoutée et on vérifie son nouveau nom. ligne 22 : on supprime la personne ajoutée au début du test. lignes 23-34 : on demande à la couche [dao] une copie de la personne qui vient d’être supprimée. On doit obtenir une [DaoException] de code 2. lignes 36-37 : la liste des personnes est redemandée. On doit obtenir la même qu’au début du test.

La méthode [test4] cherche à mettre en lumière les problèmes d’accès concurrents aux méthodes de la couche [dao]. Rappelons que celles-ci n’ont pas été synchronisées. Le code du test est le suivant :
1. public void test4() throws Exception { Les bases du développement web MVC en Java, par l'exemple

146/264

IDao. 22. 6. 12. 21. Le constructeur de ce type a trois paramètres : 1.length.start(). Au final. 29.personnes. 23.mvc. 24.getOne(id1).Date. 30. true. 7.getCode(). package istia. le nom donné au thread. 14. 2. 17. i < taches. 35.2. 15. 23. l’id de la personne sur laquelle le thread doit travailler Le type [ThreadDaoMajEnfants] est le suivant : 1.Personne. lignes 7-13 : on lance N threads. // l'id de la personne sur qui on va travailler private int idPersonne.getNbEnfants()). codeErreur). for (int i = 0. import java. une référence sur la couche [dao] afin que le thread y ait accès 3. 9. 5. 13. la personne P devra avoir N enfants.length. 11.mvc. par l'exemple 147/264 . assertEquals(2. 5.idPersonne = idPersonne. 21. // vérification boolean erreur = false. 3.tests.personnes. // constructeur public ThreadDaoMajEnfants(String name. 6. 28. 31. 0). Ligne 11. 22. 8. 30. 29. taches[i].mvc. 33. 3. 27. 7.parse("01/02/2006"). lignes 15-17 : la méthode [test4] qui a lancé les N threads attend qu’ils aient terminé leur travail avant de regarder le nouveau nombre d’enfants de la personne P. 9. On note son [id] (ligne 6). 36. 10. 10. 26. codeErreur = ex.util. 34. lignes 18-21 : on récupère la personne P et on vérifie que son nombre d’enfants est N. // on boucle tant qu'on n'a pas réussi à incrémenter de 1 // le nbre d'enfants de la personne idPersonne boolean fini = false. 19. dao. new SimpleDateFormat( "dd/MM/yyyy"). p1. 16.getId()). } // on attend la fin des threads for (int i = 0. "X". this. 32. 16. 17. 19.st. // référence sur la couche [dao] private IDao dao. int id1 = p1. 18. 27. 26. } // on récupère la personne p1 = dao.join(). 25. 20. } // on doit avoir une erreur de code 2 assertTrue(erreur). 4. 12. } catch (DaoException ex) { erreur = true. 15.personnes.entites. public class ThreadDaoMajEnfants extends Thread { // nom du thread private String name. i < taches.dao. import istia. 24.personnes.name = name. ceci pour le suivre au moyen de logs 2. 4. 11.getOne(p1. // suppression personne p1 dao. // elle doit avoir N enfants assertEquals(N.getId(). i++) { taches[i] = new ThreadDaoMajEnfants("thread n° " + i. dao. 18. } // coeur du thread public void run() { // suivi suivi("lancé"). } // ajout d'une personne Personne p1 = new Personne(-1. IDao dao. import istia.dao = dao.mvc.getId()). id1). on voit que les threads sont de type [ThreadDaoMajEnfants]. 8. i++) { taches[i]. try { p1 = dao. Chacun d’eux va incrémenter le nombre d’enfants de la personne P de 1 unité. // création de N threads de mise à jour du nombre d'enfants final int N = 10. int codeErreur = 0.st. 28. this. Thread[] taches = new Thread[N]. int idPersonne) { this.DaoException. 14. 20. Les bases du développement web MVC en Java. • • • • • lignes 3-6 : on ajoute dans la liste une personne P avec aucun enfant.saveOne(p1). "X". lignes 22-35 : la personne P est supprimée puis on vérifie qu’elle n’existe plus dans la liste.deleteOne(p1. import istia. 13.st. 25.st.dao.

getTime()+ "] : " + message). C’est pourquoi. Dans celle-ci. // suivi 37. 42.getMessage()). [TH2] qui le suivait fait la même chose et obtient la même version V1 de la personne P. try { 53. } 72. 148/264 Les bases du développement web MVC en Java. suivi("début attente").println(name + " [" + new Date(). 69.on recommence tout 71.getOne(idPersonne). 48. Thread. la méthode [run] (ligne 25) de celui-ci est exécutée : • • • • lignes 78-81 : la méthode privée [suivi] permet de faire des logs écran.getVersion()). 77. La méthode [run] en use pour permettre le suivi du thread dans son exécution. } catch (DaoException ex) { 60. throw ex. le thread va chercher à incrémenter de 1 le nombre d’enfants de la personne P d’identifiant [id]. // on essaie de modifier l'original 56. par l'exemple . [TH2] est interrompu. } 76. // suivi 78. Sa mise à jour de P sera refusée car il détient une copie de P de version V1 alors que l’original P a désormais la version V2. } else { 67. suivi("fin attente"). Cette mise à jour peut nécessiter plusieurs tentatives. 38. } 70. 59. suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version "+personne. 44. System. 57. Nous savons qu’alors. // on s'interrompt pour laisser le processeur 43. 62. celles-ci sont sauvegardées et que la version de P va passer à V2. 81. . } • • ligne 9 : [ThreadDaoMajEnfants] est bien un thread lignes 18-22 : le constructeur qui initialise le thread avec trois informations 1. } 82. // on récupère le code erreur 61. 32.toString()). le nom [name] donné au thread 2. if (codeErreur != 3) { 65. [TH1] est interrompu. incrémente le nombre d’enfants de P et sauvegarde ses modifications. int codeErreur = 0. [TH1] demande une copie de la personne P à la couche [dao]. [TH2] doit alors reprendre tout le cycle [lecture -> mise à jour -> sauvegarde]. nbEnfants = personne.setNbEnfants(nbEnfants + 1). // suivi 45. Il l’obtient et constate qu’elle a la version V1. 36. 3. 46. [TH1] a fini son travail. suivi("a terminé et passé le nombre d'enfants à " + (nbEnfants + 1)). Prenons deux threads [TH1] et [TH2]. personne. l’identifiant [id] de la personne sur laquelle le thread doit travailler Lorsque [test4] lance un thread [ThreadDaoMajEnfants] (ligne 12 de test4). int nbEnfants = 0.getNbEnfants(). [TH2] reprend la main.getCode(). try { 40. } catch (Exception ex) { 47. // on récupère une copie de la personne d'idPersonne 34. le thread : demande une copie de la personne P à modifier (ligne 34) attend 10 ms (ligne 43). 35.sleep(10). [TH2] reprend la main et fait de même. // attente terminée . // incrémente de 1 le nbre d'enfants de cette copie 54. Personne personne = dao.on essaie de valider la copie 50. } 73. 55.sinon on relance 63. // suivi 41. Ceci est artificiel et vise à interrompre le thread entre la lecture de la personne P et sa mise à jour effective dans la liste des personnes afin d’augmenter la probabilité de conflits. nous trouvons la boucle des lignes 3272. dao. private void suivi(String message) { 79.saveOne(personne). // suivi 74. } 49. // suivi 68. On notera qu’une nouvelle fois. une référence [dao] sur la couche [dao]. 52.31. throw new RuntimeException(ex. while (!fini) { 33. // l'original a changé . codeErreur = ex. 75. nous travaillons avec le type de l’interface [IDao] et non celui de l’implémentation [DaoImpl]. // doit être une erreur de version 3 . // on est passé . 66. // attente de 10 ms pour abandonner le processeur 39. fini = true. // l'exception 64.l'original a été modifié 58. // entre-temps d'autres threads ont pu modifier l'original 51. suivi(ex.out 80.

On obtient les résultats suivants : Le test [test4] a échoué. page 142).• incrémente le nombre d’enfants de P (ligne 54) et sauvegarde P (ligne 56). une exception sera déclenchée par la couche [dao]. alors la mise à jour a été faite et le travail du thread est terminé. Que donnent les tests ? Dans la première configuration testée : • on commente l’instruction d’attente dans la méthode [saveOne] de [DaoImpl] (ligne 83. On obtient les résultats suivants : Les cinq tests ont été réussis. Les bases du développement web MVC en Java. page 142). Si le thread n’a pas la bonne version de P. // création de N threads de mise à jour du nombre d'enfants final int N = 2. Dans la seconde configuration testée : • on décommente l’instruction d’attente dans la méthode [saveOne] de [DaoImpl] (ligne 83. • la méthode [test4] crée 2 threads (ligne 8. Si on n’a pas d’exception. // création de N threads de mise à jour du nombre d'enfants final int N = 100. // on attend 10 ms wait(10). Si ce n’est pas le cas. On récupère alors le code de l’exception (ligne 61) pour vérifier que c’est bien le code 3 (mauvaise version de P). Si on a l’exception de code 3. On a créé deux threads chargés chacun d’incrémenter de 1 le nombre d’enfants d’une personne P qui au départ en avait 0. page 146). par l'exemple 149/264 . // on attend 10 ms //wait(10). au final la méthode de test [test4]. on relance l’exception à destination de la méthode appelante. or on n’en a qu’un. On attendait donc 2 enfants après exécution des deux threads. page 146). • la méthode [test4] crée 100 threads (ligne 8. alors on recommence le cycle [lecture -> mise à jour -> sauvegarde].

il a été obligé de lâcher le processeur.a. // c'est bon . public void saveOne(Personne personne) { 2.... 9. Il a fallu forcer le thread n° 0 à perdre le processeur pour le faire apparaître avec simplement deux threads.. 8. 4. Le test [test4] avait été réussi. .} • • • • le thread n° 0 a exécuté [saveOne] et est allé jusqu’à la ligne 8 où là. 11. à son tour. Puis la méthode [run] du thread n° 0 s’est terminée et le thread a affiché le log qui disait qu’il avait passé le nombre d’enfants à 1 (ligne 9). A partir de la ligne 9. 5. Entre-temps. Tout d’abord. c’est le thread n° 0 qui en a hérité. simple à mettre en oeuvre. Puis la méthode [run] du thread n° 1 s’est terminée et le thread a affiché le log qui disait qu’il avait passé le nombre d’enfants à 1 (ligne 10). Pourquoi 1 ? Parce qu’il détient une copie de P avec un nombre d’enfants à 0. A partir de la ligne 9.. on peut remarquer une anomalie entre les lignes 7 et 8 : il semblerait que le thread n° 0 ait perdu le processeur entre ces deux lignes au profit du thread n° 1.. Ce cas de figure est peu probable mais pas impossible. 9. exécuté [saveOne] et est allé jusqu’à la ligne 8 où là il a été obligé de lâcher le processeur.Suivons les logs écran de [test4] pour comprendre ce qui s’est passé : 1. L’une d’elles. 3. c’est le thread n° 1 qui en a hérité. il a lu la version de la personne P et c’était 1 parce que la personne P n’avait toujours pas été mise à jour. c’est le thread n° 1 qui en a hérité. c. il a fait sa mise à jour et passé le nombre d’enfants à 1.. 5.sleep(10)] de sa méthode [run] et s’arrête donc au temps [1145536368171] (ms) ligne 4 : le thread n° 1 récupère alors le processeur et commence son travail ligne 5 : il a récupéré une copie de la personne P et trouve son nombre d’enfants à 0 ligne 6 : il rencontre le [Thread. 2. Celle-ci a le squelette suivant (cf page 142) : 1. il a lu la version de la personne P et c’était 1 parce que la personne P n’avait pas encore été mise à jour. le processeur étant devenu libre..d. // a-t-on la bonne version de l'original ? 6.. Il a. D’où vient le problème ? Il vient du fait que le thread n° 0 n’a pas eu le temps de valider sa modification et donc de changer la version de la personne P avant que le thread n° 1 n’essaie de lire cette version pour savoir si la personne P avait changé. 3. le processeur étant devenu libre. // on attend 10 ms 8. 7. C’est le log (ligne 5) qui le dit. 16 ms après l’avoir perdu... 10. le processeur étant devenu libre.on cherche la personne 4. Entretemps. 7. 6. la configuration précédente n’avait pas réussi à faire apparaître ce même cas avec 100 threads. il a fait sa mise à jour et passé le nombre d’enfants à 1. // modification .on fait la modification 10. Que faisait-il à ce moment ? Il exécutait la méthode [saveOne] de la couche [dao]. thread thread thread thread thread thread thread thread thread thread n° n° n° n° n° n° n° n° n° n° 0 0 0 1 1 1 0 1 0 1 [1145536368171] [1145536368171] [1145536368171] [1145536368171] [1145536368171] [1145536368171] [1145536368187] [1145536368187] [1145536368187] [1145536368187] : : : : : : : : : : lancé 0 -> 1 pour la version 1 début attente lancé 0 -> 1 pour la version 1 début attente fin attente fin attente a terminé et passé le nombre d'enfants à 1 a terminé et passé le nombre d'enfants à 1 • • • • • • • • • • ligne 1 : le thread n° 0 commence son travail ligne 2 : il a récupéré une copie de la personne P et trouve son nombre d’enfants à 0 ligne 3 : il rencontre le [Thread. par l'exemple 150/264 .sleep(10)] de sa méthode [run] et s’arrête donc ligne 7 : le thread n° 0 récupère le processeur au temps [1145536368187] (ms).. ligne 8 : idem pour le thread n° 1 ligne 9 : le thread n° 0 a fait sa mise à jour et passé le nombre d’enfants à 1 ligne 10 : le thread n° 1 a fait de même La question est de savoir pourquoi le thread n° 1 a-t-il pu faire sa mise à jour alors que normalement il ne détenait plus la bonne version de la personne P qui venait d’être mise à jour par le thread n° 0. Sans cet artifice. Quelle est la solution ? Il y en a sans doute plusieurs. wait(10). est de synchroniser la méthode [saveOne] : public synchronized void saveOne(Personne personne) Les bases du développement web MVC en Java.

dao = dao. import java. A cela plusieurs raisons : • • nous faisons l’hypothèse que l’accès à la couche [dao] se fait toujours au travers d’une couche [service].personnes. 15. 3. 12.Le mot clé [synchronized] assure qu’un seul thread à la fois peut exécuter la méthode. import istia. Nous décidons cependant de garder cette couche telle qu’elle a été décrite et de reporter la synchronisation sur la couche [service]. } Elle est identique à l’interface [IDao]. public synchronized Collection getAll() { 23. 15. 14. 2. public void setDao(IDao dao) { 18. 12. par l'exemple 151/264 . // supprimer une personne void deleteOne(int id). C’est le cas dans notre application web. 21. public IDao getDao() { 14. Si on est assurés que : • tout accès à la couche [dao] passe par la couche [service] • qu’un unique thread à la fois utilise la couche [service] alors on est assurés que les méthodes de la couche [dao] ne seront pas exécutés par deux threads en même temps. // liste des personnes 22.personnes. 2.Collection.springmvc. public class ServiceImpl implements IService { 9. 3. 7. Ainsi le thread n° 1 ne sera-t-il autorisé à exécuter [saveOne] que lorsque le thread n° 0 en sera sorti. Ce sont les quatres méthodes de la couche [dao] qu’il faudrait synchroniser.util. 13.springmvc. Les bases du développement web MVC en Java. 6.entites.st.entites.springmvc. Sa mise à jour sera alors refusée car il n’aura pas la bonne version de P. package istia. Nous découvrons maintenant la couche [service].getAll().Collection. import istia. 7. 17. 10. // la couche [dao] 11. return dao. 4.springmvc. Dans ce cas.service. 5. 13. public interface IService { // liste de toutes les personnes Collection getAll().util. import istia.personnes. L’implémentation [ServiceImpl] de l’interface [IService] est la suivante : 1.st. On est alors sûr que la version de la personne P aura été changée lorsque le thread n° 1 va entrer dans [saveOne].st.springmvc.Personne. this. 14. import java. il est inutile de synchroniser les méthodes de la couche [dao]. il peut être nécessaire de synchroniser également l’accès aux méthodes de la couche [service] pour d’autres raisons que celles qui nous feraient synchroniser celles de la couche [dao]. } 16. 16. // ajouter/modifier une personne void saveOne(Personne personne). 4. package istia.dao. private IDao dao. 8. 10.st.st. 11. 19. } 20. 9.Personne. 6. 5.personnes.service.personnes. return dao. // obtenir une personne particulière Personne getOne(int id).6 La couche [service] La couche [service] est constituée des classes et interfaces suivantes : • • [IService] est l’interface présentée par la couche [dao] [ServiceImpl] est une implémentation de celle-ci L’interface [IService] est la suivante : 1. 8.IDao.

Le squelette de [TestService] est le suivant : 1. // affichage doListe(personnes). 25.. 7. 29. } • • • • • • lignes 10-19 : l’attribut [IDao dao] est une référence sur la couche [dao]. } 30. 18. 26. 31.springmvc. 12. 6. public synchronized void deleteOne(int id) { 38. 23. } 25. } // test1 public void test1() throws ParseException { // liste actuelle Collection personnes = service.tests. Les tests faits sont strictement identiques à ceux faits pour la couche [dao]. 28. // ajouter ou modifier une personne 32. // suppression d'une personne 37. public synchronized Personne getOne(int id) { 28. lignes 32-34 : implémentation de la méthode [saveOne] de l’interface [IService]. package istia. 34.personnes. int nbPersonnes = personnes. 5. 11.st. } 40.24. 24. 21. 36. 15. 3. 8..getOne(id). service. dao. 14. 14. La méthode se contente de déléguer la demande à la couche [dao]. lignes 37-39 : implémentation de la méthode [deleteOne] de l’interface [IService]. 10. 17.deleteOne(id). dao. // obtenir une personne en particulier 27. La méthode se contente de déléguer la demande à la couche [dao]. Il sera initialisé par Spring IoC. 2.setDao(dao).saveOne(personne). DaoImpl dao=new DaoImpl(). toutes les méthodes sont synchronisées (mot clé synchronized) assurant qu’un seul thread à la fois pourra utiliser la couche [service] et donc la couche [dao]. La méthode se contente de déléguer la demande à la couche [dao].getAll(). par l'exemple 152/264 . 27. 13. La méthode se contente de déléguer la demande à la couche [dao]. 4. } 35. 20. return dao. Les bases du développement web MVC en Java. public class TestService extends TestCase { // couche [service] private ServiceImpl service.. } // liste des personnes private void doListe(Collection personnes) { . 19. 16.. 22.size(). .7 Tests de la couche [service] Un test JUnit est écrit pour la couche [service] : [TestService] est le test JUnit. lignes 22-24 : implémentation de la méthode [getAll] de l’interface [IService]. 26. // constructeur public TestService() { service = new ServiceImpl(). 39. 9. public synchronized void saveOne(Personne personne) { 33. lignes 27-29 : implémentation de la méthode [getOne] de l’interface [IService].

. Thread[] taches = new Thread[N]. 55.service.. 34.idPersonne = idPersonne. int id1 = p1. this. 58. lignes 11-15 : le constructeur du test JUnit crée une instance de la couche [service] à tester (ligne 12). public ThreadServiceMajEnfants(String name. 36. 62. La méthode [test4] cherche à mettre en lumière les problèmes d’accès concurrents aux méthodes de la couche [service].mvc.personnes..name = name. 7. 17. true. identique à la méthode de test [test4] de la couche [dao]. 54. 35) plutôt qu’à la couche [dao].. 14. là encore. new SimpleDateFormat( "dd/MM/yyyy").getId(). 67. 8. // création de N threads de mise à jour du nombre d'enfants final int N = 100. public class ThreadServiceMajEnfants extends Thread { // nom du thread private String name..personnes. 6. } // ajout d'une personne Personne p1 = new Personne(-1. 3. import istia. taches[i].st. 51. 45. "X".tests. par l'exemple 153/264 . 2. 42. import istia.mvc. for (int i = 0. 15. 9. int id1 = p1.. new SimpleDateFormat( "dd/MM/yyyy"). 41. } } • • lignes 9 : la couche [service] testée de type [ServiceImpl]. 68.st. "X". 48.on aura un plantage si la personne n'est pas trouvée p1 = service. "X". 30. 38.saveOne(p1). 56. Elle est. 1). id1).st.getNom()).length. Simplement. 4.st.personnes.springmvc.. i++) { taches[i] = new ThreadServiceMajEnfants("thread n° " + i. } // tests de validité de saveOne public void test5() throws ParseException { . i < taches. 11. 44. } // gestion des versions de personne public void test3() throws ParseException. import istia. 40.29. 31.Personne. service.service = service. service.... 39. 53. La méthode [test1] teste les quatre méthodes de l’interface [IService] de façon identique à la méthode de test de la couche [dao] de même nom.parse("01/02/2006").entites. } Les bases du développement web MVC en Java. on accède à la couche [service] (lignes 25. service. 20. 12. 47. 5. InterruptedException { . // l'id de la personne sur qui on va travailler private int idPersonne. 71.dao. 72.parse("01/02/2006").mvc. 16. 60. 70. 61. int idPersonne) { this. p1. 63. 69. 35. 46.accès multi-threads public void test4() throws Exception { // ajout d'une personne Personne p1 = new Personne(-1.start(). 64. 13. 0). // modification-suppression d'un élément inexistant public void test2() throws ParseException { . crée une instance de la couche [dao] (ligne 13) et indique à la couche [service] qu'elle doit utiliser cette couche [dao] (ligne 14).getOne(id1). 43. true. 49. } . 32. 32. 19. 59. 52. 57.DaoException. IService service.personnes. } // optimistic locking . 65. Il y a cependant quelques détails qui changent : • on s’adresse à la couche [service] plutôt qu’à la couche [dao] (ligne 55) • on passe aux threads une référence à la couche [service] plutôt qu’à la couche [dao] (ligne 61) Le type [ThreadServiceMajEnfants] est lui aussi quasi identique au type [ThreadDaoMajEnfants] au détail près qu’il travaille avec la couche [service] et non la couche [dao] : 1.saveOne(p1).IService. this. 66. 33. . 37.getId(). 18. // référence sur la couche [service] private IService service. 10. 50. assertEquals("X". // vérification . package istia. "X".

99]] a changé depuis sa lecture initiale thread n° 93 [1145541451703] : 99 -> 100 pour la version 100 thread n° 93 [1145541451703] : début attente thread n° 44 [1145541451703] : a terminé et passé le nombre d'enfants à 99 thread n° 93 [1145541451718] : fin attente thread n° 93 [1145541451718] : a terminé et passé le nombre d'enfants à 100 Les dernières lignes des logs écran Tous les tests ont été réussis C’est la synchronisation des méthodes de la couche [service] qui a permis le succès du test [test4].out. 31. par l'exemple . } 30. System. } 25.println(name + " : " + message). // création de N threads de mise à jour du nombre d'enfants final int N = 100. private void suivi(String message) { 28. public void run() { 23. 22.21.99. // suivi 27. . } • ligne 12 : le thread travaille avec la couche [service] Nous faisons les tests avec la configuration qui a posé problème à la couche [dao] : • on décommente l’instruction d’attente dans la méthode [saveOne] de [DaoImpl] (ligne 83. page 142).X. 24.true. page 153).01/02/2006. 29. 14.X.. 26.8 La couche [web] Rappelons l’architecture 3tier de notre application : Application web couche [web] Servlet Utilisateur JSP1 JSP2 JSPn La couche [web] va offrir des écrans à l’utilisateur pour lui permettre de gérer le groupe de personnes : • • • • couche [metier] Modèles couche [dao] Données liste des personnes du groupe ajout d’une personne au groupe modification d’une personne du groupe suppression d’une personne du groupe 154/264 Les bases du développement web MVC en Java. • la méthode [test4] crée 100 threads (ligne 65. Les résultats obtenus sont les suivants : thread n° 93 [1145541451687] : 98 -> 99 pour la version 99 thread n° 93 [1145541451687] : début attente thread n° 44 [1145541451687] : fin attente thread n° 93 [1145541451687] : fin attente thread n° 93 [1145541451703] : L'original de la personne [[4. // on attend 10 ms wait(10)..

11.8. 10. <?xml version="1.com/xml/ns/j2ee" xmlns:xsi="http://www. par l'exemple 155/264 .1 Configuration de l’application web Le projet Eclipse de l’application est le suivant : • • • dans le paquetage [istia.xml] Le fichier [web.sun.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun. les pages JSP / JSTL sont dans [WEB-INF/vues]. 12.personnes.com/xml/ns/j2ee/webapp_2_4.personnes.Pour cela.Application </servlet-class> <init-param> Les bases du développement web MVC en Java.mvc. 2.0" encoding="UTF-8"?> <web-app id="WebApp_ID" version="2. nous allons présenter successivement : • • • • sa configuration ses vues son contrôleur quelques tests 14.st. le dossier [lib] contient les archives tierces nécessaires à l’application. Pour décrire la couche web. 13. 5. 7.ServletPersonne --> <servlet> <servlet-name>personnes</servlet-name> <servlet-class> istia.4" xmlns="http://java. Nous avons déjà présenté les écrans gérés par la couche [web] (page 137).w3.com/xml/ns/j2ee http://java.sun. 9. 6. elle va s’appuyer sur la couche [service] qui elle même fera appel à la couche [dao]. [web. 4.st. Son contenu est le suivant : 1. on trouve le contrôleur [Application].xsd"> <display-name>mvc-personnes-01</display-name> <!-.web]. Elles sont visibles dans le dossier [Web App Libraries]. 3. 8.mvc.web.xml] est le fichier exploité par le serveur web pour charger l’application.

<location>/WEB-INF/vues/exception. par l'exemple 156/264 . <exception-type>java. [index. <welcome-file>index. <param-name>urlList</param-name> 23.Page d'erreur inattendue --> 36. <init-param> 18. D'où l'intérêt de la balise <error-page> dans le fichier [web. donc toutes les exceptions. lignes 36-39 : l’application a une page d’erreurs par défaut qui est affichée lorsque le serveur web récupère une exception non gérée par l'application. </init-param> 25. urlErreurs] identifiant les Url des pages JSP des vues [list.lang. </servlet> 26. <!-.Mapping ServletPersonne--> 27.jsp] qui se trouve à la racine du dossier de l’application web.jsp</param-value> 16.8.charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html. 3.Exception] et dérivé. ici [/personnes-01]. <param-name>urlEdit</param-name> 15.jsp</location> 39. <param-value>/WEB-INF/vues/edit. Son contenu est le suivant : 1. une classe que nous allons construire. </welcome-file-list> 35.jsp] Cette page est présentée si un utilisateur demande directement le contexte de l’application sans préciser d’url. </init-param> 21. 14. </web-app> • • • • • lignes 27-30 : les url [/do/*] seront traitées par la servlet [personnes] lignes 9-12 : la servlet [personnes] est une instance de la classe [Application].jsp</param-value> 24.fichiers d'accueil --> 32. edit.2 Les pages JSP / JSTL de l’application La vue [list. c. erreurs]. <param-name>urlErreurs</param-name> 19.Exception</exception-type> 38.lang.jsp</welcome-file> 34.jsp</param-value> 20.a. <param-value>/WEB-INF/vues/erreurs. lignes 32-34 : l’application a une page d’entrée par défaut [index.xml]. <welcome-file-list> 33. <url-pattern>/do/*</url-pattern> 30. </error-page> 40. <error-page> 37. 4. L'exception e survenue est disponible à cette page dans un objet nommé exception si la page a la directive : <%@ page isErrorPage="true" %> • si <exception-type> précise un type T1 et qu'une exception de type T2 non dérivé de T1 remonte jusqu'au serveur web. <param-value>/WEB-INF/vues/list. <!-. <!-. ici le type [java. 2. urlEdit. <init-param> 22.14. </servlet-mapping> 31. • ligne 38 : la balise <location> indique la page JSP à afficher lorsqu'une exception du type défini par <exception-type> se produit. Cette url affiche la liste des personnes du groupe.tld" prefix="c" %> <c:redirect url="/do/list"/> [index. • ligne 37 : la balise <exception-type> indique le type d'exception gérée par la directive <error-page>.d. <servlet-mapping> 28.jsp] Elle sert à afficher la liste des personnes : Les bases du développement web MVC en Java.jsp] redirige le client vers l’url [/do/list]. celui-ci envoie au client une page d'exception propriétaire généralement peu conviviale. <servlet-name>personnes</servlet-name> 29. </init-param> 17. lignes 13-24 : définissent trois paramètres [urlList.

37. 23.marie}"/></td> <td><c:out value="${personne. 26. 21. 25. on utilise la balise <dt> de la bibliothèque de balise [DateTime] du projet Apache [Jakarta Taglibs] : • • • • Les bases du développement web MVC en Java. 22. 16.nbEnfants}"/></td> <td><a href="<c:url value="/do/edit?id=${personne. 39. 13.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne. 29. 20. 28.charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c. 24.Personnes</title> </head> <body background="<c:url value="/ressources/standard. 40. 31. 2.dateNaissance. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html. 12. 5. 30.Son code est le suivant : 1. 6. 36. 34. ligne 28 : pour afficher la date de naissance de la personne sous la forme JJ/MM/AAAA. 11. 18.id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html> • cette vue reçoit un élément dans son modèle : • l'élément [personnes] associé à un objet de type [ArrayList] d’objets de type [Personne] lignes 22-34 : on parcourt la liste ${personnes} pour afficher un tableau HTML contenant les personnes du groupe.tld" prefix="c" %> <%@ taglib uri="/WEB-INF/taglibs-datetime. 4.time}</dt:format></td> <td><c:out value="${personne.tld" prefix="dt" %> <html> <head> <title>MVC . 27.</th> <th>Nombre d'enfants</th> <th></th> </tr> <c:forEach var="personne" items="${personnes}"> <tr> <td><c:out value="${personne. 19.nom</th> <th>Nom</th> <th>Date de naissance</th> <th>Mari&eacute. 32. 38.jpg"/>"> <h2>Liste des personnes</h2> <table border="1"> <tr> <th>Id</th> <th>Version</th> <th>Pr&eacute. 17. 15. ligne 32 : il est fait de même pour le lien [Supprimer].id}"/></td> <td><c:out value="${personne. 8.prenom}"/></td> <td><c:out value="${personne. 9. 3. 35. 10.nom}"/></td> <td><dt:format pattern="dd/MM/yyyy">${personne.version}"/></td> <td><c:out value="${personne. ligne 31 : l’url pointée par le lien [Modifier] est paramétrée par le champ [id] de la personne courante afin que le contrôleur associé à l’url [/do/edit] sache quelle est la personne à modifier. par l'exemple 157/264 . 7. 14. 33.

/ressources/standard. 5. 16. • ligne 37 : le lien [Ajout] d'ajout d'une nouvelle personne a pour cible l'url [/do/edit] comme le lien [Modifier] de la ligne 31. 27. 20. 18. 14. 4.Personnes</title> </head> <body background=".Le fichier de description de cette bibliothèque de balises est défini ligne 3. 17. 8. 22.jsp] Le code de la vue [edit. 19. 7. 21. 29. par l'exemple 158/264 . 12. La vue [edit. 10. 6. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html..jpg"> <h2>Ajout/Modification d'une personne</h2> <c:if test="${erreurEdit != ''}"> <h3>Echec de la mise à jour :</h3> L'erreur suivante s'est produite : ${erreurEdit} <hr> </c:if> <form method="post" action="<c:url value="/do/validate"/>"> <table border="1"> <tr> <td>Id</td> <td>${id}</td> </tr> <tr> <td>Version</td> <td>${version}</td> </tr> <tr> <td>Pr&eacute. 28.jsp] la vue [edit.tld" prefix="c" %> <%@ taglib uri="/WEB-INF/taglibs-datetime. 13. 9. 11. 26. 25.nom</td> <td> <input type="text" value="${prenom}" name="prenom" size="20"> Les bases du développement web MVC en Java.jsp] Elle sert à afficher le formulaire d’ajout d’une nouvelle personne ou de modification d’une personne existante : la vue [list. C'est la valeur -1 du paramètre [id] qui indique qu'on a affaire à un ajout plutôt qu'une modification.charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c. 2. 15. 3.jsp] est le suivant : 1. 23. 24.tld" prefix="dt" %> <html> <head> <title>MVC .

</tr> 62. </td> 38. • la vue [edit. <input type="submit" value="Valider"> 74. par l'exemple . <td>Date de naissance (JJ/MM/AAAA)</td> 42. <c:choose> 51. </tr> 47. <td> 50. <c:when test="${marie}"> 52. <td>${erreurNbEnfants}</td> 68. </tr> 69. <td>Mari&eacute.</td> 49. </c:when> 55. <td> 36. <input type="radio" name="marie" value="true">Oui 57. </body> 77. reçoit les éléments suivants dans son modèle : attribut GET identifiant de la personne mise idem à jour idem sa version son prénom son nom sa date de naissance son état marital son nombre d'enfants vide vide vide vide prénom saisi nom saisi date de naissance saisie état marital saisi nombre d'enfants saisi un message d'erreur signalant un échec de l'ajout ou de la modification au moment du POST provoqué par le bouton [Envoyer]. Si le POST échoue. <br> 71. </td> 31.jsp] affichée aussi bien sur un GET que sur un POST qui échoue. <tr> 41. <td>${erreurNom}</td> 39. <td>${erreurPrenom}</td> 32. sinon la vue [list. <td> 65.30.jsp] est affichée. <input type="hidden" value="${id}" name="id"> 72. Le bouton [Valider] (ligne 73) provoque le POST du formulaire à l'url [/do/validate] (ligne 16). </html> Cette vue présente un formulaire d'ajout d'une nouvelle personne ou de mise à jour d'une personne existante. <td>${erreurDateNaissance}</td> 46. <tr> 63. </td> 45. </c:choose> 60. Vide si pas d'erreur. </form> 76. <a href="<c:url value="/do/list"/>">Annuler</a> 75. <input type="radio" name="marie" value="false">Non 54.jsp] est réaffichée avec la ou les erreurs qui se sont produites. </td> 67. <c:otherwise> 56. </tr> 40. nous utiliserons l'unique terme de [mise à jour]. <td> 43. </c:otherwise> 59. Par la suite et pour simplifier l'écriture. la vue [edit. <td>Nombre d'enfants</td> 64. <tr> 34. </table> 70. <input type="text" value="${nbEnfants}" name="nbEnfants"> 66. <input type="text" value="${nom}" name="nom" size="20"> 37. </td> 61. <td>Nom</td> 35. signale un prénom erroné – vide sinon signale un nom erroné – vide sinon signale une date de naissance erronée – vide sinon 159/264 POST id version prenom nom dateNaissance marie nbEnfants erreurEdit erreurPrenom erreurNom erreurDateNaissance Les bases du développement web MVC en Java. </tr> 33. <tr> 48. <input type="radio" name="marie" value="false" checked>Non 58. <input type="hidden" value="${version}" name="version"> 73. <input type="text" value="${dateNaissance}" name="dateNaissance"> 44. <input type="radio" name="marie" value="true" checked>Oui 53.

7. 2.personnes</h2> L'exception suivante s'est produite : <%= exception.tld" prefix="c" %> <%@ page isErrorPage="true" %> <% %> response. 18. ligne 72 : un champ HTML caché nommé [version] et ayant pour valeur le champ [id] de la personne en cours de mise à jour.Personnes</title> </head> <body background="<c:url value="/ressources/standard. 6. on aura [erreurEdit!=''] et un message d'erreur sera affiché.setStatus(200). Il a été libellé [Annuler] parce qu’il permet de quitter le formulaire sans le valider. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html. 16. on réaffiche la valeur saisie ${prenom} ainsi que le message d’erreur éventuel ${erreurPrenom} lignes 33-39 : saisie du nom de la personne lignes 40-46 : saisie de la date de naissance de la personne lignes 47-61 : saisie de l’état marié ou non de la personne avec un bouton radio. 4. lignes 62-68 : saisie du nombre d’enfants de la personne ligne 71 : un champ HTML caché nommé [id] et ayant pour valeur le champ [id] de la personne en cours de mise à jour.jsp] . 17. <html> <head> <title>MVC . supprimons une personne qui n’existe pas dans le groupe : la vue [list. la liste</a> </body> </html> Les bases du développement web MVC en Java. ligne 16 : le formulaire sera posté à l’url [/do/validate] ligne 20 : l'élément [id] du modèle est affiché ligne 24 : l'élément [version] du modèle est affiché lignes 26-32 : saisie du prénom de la personne : • lors de l’affichage initial du formulaire (GET). 9. par l'exemple 160/264 . 19. 10.jsp] – on a demandé la suppression de la personne d’id=7 en tapant à la main l’url dans le navigateur.getMessage()%> <br><br> <a href="<c:url value="/do/list"/>">Retour &agrave. On utilise la valeur du champ [marie] de l’objet [Personne] pour savoir lequel des deux boutons radio doit être coché. 5. 21.jsp] Elle sert à afficher une page signalant qu’il s’est produit une exception non gérée par l’application et qui est remontée jusqu’àu serveur web. -1 pour un ajout. • en cas d’erreur après le POST. 13. la vue [exception.charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.attribut erreurNbEnfants GET vide POST signale un nombre d'enfants erroné – vide sinon • • • • • • • • • • • • • lignes 11-15 : si le POST du formulaire se passe mal. ${prenom} affiche la valeur actuelle du champ [prenom] de l’objet [Personne] mis à jour et ${erreurPrenom} est vide.il n’y a pas de personne d’id=7 Le code de la vue [exception. La vue [exception. Par exemple. 12. 15.jsp] est le suivant : 1. 14. 3. 11. 8. autre chose pour une modification.jpg"/>"> <h2>MVC . ligne 73 : le bouton [Valider] de type [Submit] du formulaire ligne 74 : un lien permettant de revenir à la liste des personnes. 20.

C'est le premier entête HTTP de la réponse.personnes. 4.DaoException.personnes. En effet.3 Le contrôleur de l’application Le contrôleur [Application] est défini dans le paquetage [istia.d. 10. 14. c.mvc. ligne 16 : le texte de l’exception est affiché ligne 18 : on propose à l’utilisateur un lien pour revenir à la liste des personnes • • • La vue [erreurs. 16. 12. 7.dao.• cette vue reçoit une clé dans son modèle l'élément [exception] qui est l’exception qui a été interceptée par le serveur web. 15. Le code 200 signifie au client que sa demande a été honorée. 2. il faut que la page ait défini la balise de la ligne 3. 13.web] : Structure et initialisation du contrôleur Le squelette du contrôleur [Application] est le suivant : 1. C'est le cas ici.st. Pour que cet élément soit inclus dans le modèle de la page JSP par le serveur web.jsp] Elle sert à afficher une page signalant les erreurs d'initialisation de l'application. import istia. il aura ici la valeur 500 qui signifie qu'il s'est produit une erreur.mvc.tld" prefix="c" %> <html> <head> <title>MVC . La réaction au code HTTP 500 diffère selon les navigateurs : Firefox affiche le document HTML qui peut accompagner cette réponse alors qu'IE ignore ce document et affiche sa propre page.8. C'est pour cette raison que nous avons remplacé le code 500 par le code 200. 17. ligne 6 : on fixe à 200 le code d'état HTTP de la réponse. les erreurs détectées lors de l'exécution de la méthode [init] de la servlet du contrôleur. Si on ne fixe pas à 200 le code d'état HTTP de la réponse.Personnes</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach> </ul> </body> </html> La page reçoit dans son modèle un élément [erreurs] qui est un objet de type [ArrayList] d'objets [String].st.a. Ils sont affichés par la boucle des lignes 13-15.personnes. 6. Ce peut être par exemple l'absence d'un paramètre dans le fichier [web. @SuppressWarnings("serial") Les bases du développement web MVC en Java. 5. 8. 9.. 5. par l'exemple 161/264 . 4. <%@ page language="java" contentType="text/html.mvc. 18. 3. 11. 3. ces derniers étant des messages d'erreurs. le serveur web ayant intercepté une exception non gérée trouve cette situation anormale et le signale par le code 500. 6.xml] comme le montre l'exemple ci-dessous : Le code de la page [erreurs. .jsp] est le suivant : 1.web. charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ taglib uri="/WEB-INF/c. 14. 2. package istia. Généralement un document HTML a été intégré dans la réponse du serveur.st..

44. } } Les bases du développement web MVC en Java. 20. 84. HttpServletResponse response. 24. 73. 76. IOException { . HttpServletResponse response) throws IOException.put(paramètres[i]. 47. par l'exemple 162/264 . } // validation modification / ajout d'une personne public void doValidatePersonne(HttpServletRequest request. } // validation modification / ajout d'une personne private void doDeletePersonne(HttpServletRequest request. 10. 75. for (int i = 0.7. 78. ServletException { // on passe la main au GET doGet(request. 71. 19. 52. 29. } } // l'url de la vue [erreurs] a un traitement particulier urlErreurs = config. 74. String erreurEdit) throws ServletException. 79. 92.. 85. HttpServletResponse response) throws IOException. "urlEdit". ServletException { . 72. 17. 58. service. 18. HttpServletResponse response) throws ServletException. } else { // on mémorise la valeur du paramètre params. IOException { . // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(). private Map params = new HashMap<String.. 69.length. 83. 63. if (urlErreurs == null) throw new ServletException( "Le paramètre [urlErreurs] n'a pas été initialisé"). 88. 68. 16. HttpServletResponse response) throws ServletException. 15. 50. private String[] paramètres = { "urlList".add("Le paramètre [" + paramètres[i] + "] n'a pas été initialisé"). String>(). 70. } // modification / ajout d'une personne private void doEditPersonne(HttpServletRequest request. 49. // service ServiceImpl service=null... i < paramètres. 57. 38. public class Application extends HttpServlet { // paramètres d'instance private String urlErreurs = null. 43. private ArrayList erreursInitialisation = new ArrayList<String>(). "urlErreurs" }. 65. 48. 25. // init @SuppressWarnings("unchecked") public void init() throws ServletException { // on récupère les paramètres d'initialisation de la servlet ServletConfig config = getServletConfig(). HttpServletResponse response) throws ServletException. 67. 91.. 80. 12. 90. 39. 28. 35. 55. 81. 89. 21. // on traite les autres paramètres d'initialisation String valeur = null. } // affichage formulaire pré-rempli private void showFormulaire(HttpServletRequest request. IOException { . 82. 61. dao. response).. 23. HttpServletResponse response) throws ServletException.. 51. i++) { // valeur du paramètre valeur = config. 62. 30. 59. 77. 14. 87. // paramètre présent ? if (valeur == null) { // on note l'erreur erreursInitialisation. 32.. 13. 36. 46. // instanciation de la couche [service] service = new ServiceImpl(). 31. 40.. 64. 60. 26. 66.. 45. 54. 9. 56. 11.setDao(dao). 86.getInitParameter("urlErreurs"). } // affichage liste des personnes private void doListPersonnes(HttpServletRequest request. 42. } // GET @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request. 22. 37. } // post public void doPost(HttpServletRequest request. 34.init(). valeur). 41.. IOException { ..getInitParameter(paramètres[i]). 33.. IOException{ . 53. 27. 8.

setAttribute("erreurs".jsp] est donc affichée : Le lien [Retour à la liste] ci-dessus est inopérant.getMethod(). } 28. Les bases du développement web MVC en Java. // fin 12. String action = request. // on vérifie comment s'est passée l'initialisation de la servlet 7. if (erreursInitialisation. response). 18. lignes 39-41 : le paramètre [urlErreurs] doit être obligatoirement présent car il désigne l'url de la vue [erreurs] capable d'afficher les éventuelles erreurs d'initialisation. // on passe la main à la page d'erreurs 9. // on récupère l'action à exécuter 17. doDeletePersonne(request. getServletContext().equals("/list")) { 24. // action ? 19. 13. return.equals("/delete")) { 29. 27.• • lignes 20-36 : on récupère les paramètres attendus dans le fichier [web. La vue [exception. 6. Son code est le suivant : 1. L'utiliser redonne la même réponse tant que l'application n'a pas été modifiée et rechargée. // liste des personnes 25. if (action == null) { 20. response). doListPersonnes(request.getPathInfo(). action = "/list".forward(request. // on récupère la méthode d'envoi de la requête 15. erreursInitialisation). if (méthode. // suppression d'une personne 30. les méthodes de celui-ci disposent d'une référence [service] sur la couche [service] (ligne 15) qu'elles vont utiliser pour exécuter les actions demandées par l'utilisateur.equals("get") && action. ServletException { 5. request. 11.equals("get") && action. return. Il est utile pour d'autres types d'exceptions comme nous l'avons déjà vu. } 14.getRequestDispatcher(urlErreurs). // exécution action 23. Cette exception va remonter au serveur web et être gérée par la balise <error-page> du fichier [web. • • • • ligne 43 : crée une instance [DaoImpl] implémentant la couche [dao] ligne 44 : initialise cette instance (création d'une liste initiale de trois personnes) ligne 46 : crée une instance [ServiceImpl] implémentant la couche [service] ligne 47 : initialise la couche [service] en lui donnant une référence sur la couche [dao] Après l'initialisation du contrôleur. 26. on interrompt l'application en lançant une [ServletException] (ligne 40).xml].xml].size() != 0) { 8. throws IOException.// GET 2. 21. String méthode = request. Celles-ci vont être interceptées par la méthode [doGet] qui va les faire traiter par une méthode particulière du contrôleur : Url /do/list /do/edit /do/validate /do/delete Méthode HTTP méthode contrôleur doListPersonnes GET GET POST GET doEditPersonne doValidatePersonne doDeletePersonne La méthode [doGet] Cette méthode a pour but d'orienter le traitement des actions demandées par l'utilisateur vers la bonne méthode. 10. par l'exemple 163/264 . } 22. public void doGet(HttpServletRequest request. 16. HttpServletResponse response) 4.toLowerCase(). response). if (méthode. S'il n'existe pas. @SuppressWarnings("unchecked") 3.

par l'exemple 164/264 . 45. 8. 36. ligne 7 : on fait afficher la vue [list. IOException { // le modèle de la vue [list] request. 6. La méthode [doDeletePersonne] Cette méthode traite la requête [GET /do/delete?id=XX] qui demande la suppression de la personne d'id=XX. 2. } • • ligne 5 : on demande à la couche [service] la liste des personnes du groupe et on met celle-ci dans le modèle sous la clé " personnes ".setAttribute("personnes". on fait afficher la vue [erreurs(erreurs)] qui va signaler la ou les erreurs.forward(request. 44. lignes 33-37 : traitement de la requête [GET /do/edit] qui demande le formulaire de mise à jour d'une personne. // affichage de la vue [list] getServletContext() .equals("/edit")) { // présentation formulaire ajout / modification d'une personne doEditPersonne(request. } return. service. 5.equals("/validate")) { // validation formulaire ajout / modification d'une personne doValidatePersonne(request. ligne 15 : on récupère la méthode [get] ou [post] que le client a utilisée pour faire sa requête. Si ce n'est pas le cas. 42.jsp] : Les bases du développement web MVC en Java. 32. } if (méthode. 38. } if (méthode. } // autres cas doListPersonnes(request. • • • • • • • • lignes 7-13 : on vérifie que la liste des erreurs d'initialisation est vide. 4. La méthode [doListPersonnes] Cette méthode traite la requête [GET /do/list] qui demande la liste des personnes : Son code est le suivant : 1. response). alors on fait comme si c'était [GET /do/list]. HttpServletResponse response) throws ServletException. 33. lignes 38-42 : traitement de la requête [POST /do/validate] qui demande la validation de la personne mise à jour. return. // affichage liste des personnes private void doListPersonnes(HttpServletRequest request. 35. 9. 3.get("urlList")).getRequestDispatcher((String) params. 7. response). 34. L'url [/do/delete?id=XX] est celle des liens [Supprimer] de la vue [list.31.equals("post") && action. response). 41.equals("get") && action. ligne 17 : on récupère la valeur du paramètre [action] de la requête.getAll()). response). 37. lignes 28-32 : traitement de la requête [GET /do/delete] qui demande la suppression d'une personne. 40. ligne 44 : si l'action demandée n'est pas l'une des cinq précédentes. lignes 23-27 : traitement de la requête [GET /do/list] qui demande la liste des personnes. 39.jsp] décrite page 156. return. 43.

IOException { // on récupère l'id de la personne int id = Integer. L'url [/do/edit?id=XX] est celle des liens [Modifier] et celui du lien [Ajout] de la vue [list. Son code est le suivant : 1. // on supprime la personne service. 3.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne. 13... 5. 8. 4. Si la personne qu’on cherche à supprimer n’existe pas. 7. ligne 7 : on demande à la couche [service] la suppression de la personne ayant l’id obtenu. 7. on demande au client de se rediriger vers l'Url relative [list].. <html> <head> <title>MVC . 19. // on redirige vers la liste des personnes response. La méthode [doEditPersonne] Cette méthode traite la requête [GET /do/edit?id=XX] qui demande le formulaire de mise à jour de la personne d'id=XX. Comme celle qui vient d'être traitée est [/do/delete]. dans le contrôleur. 2. // validation modification / ajout d'une personne private void doDeletePersonne(HttpServletRequest request.. l'Url de redirection sera [/do/list]. 12.deleteOne(id). 9. 10. décrite page 160 : • ligne 9 : si la suppression a eu lieu (pas d’exception). 15. Nous ne la gérons pas non plus ici. 17. On récupère la valeur [XX] du paramètre [id]. 10. 6.jsp].dont le code est le suivant : 1. 4. 9.sendRedirect("list"). 18. la couche [dao] lance une exception que laisse remonter la couche [service]. 8. 11.jpg"/>"> . 5. Elle remontera donc jusqu’au serveur web qui par configuration fera afficher la page [exception. Le navigateur sera donc amené à faire un [GET /do/list] qui provoquera l'affichage de la liste des personnes.id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html> Ligne 12. 3. <c:forEach var="personne" items="${personnes}"> <tr> . } • • ligne 5 : l’url traitée est de la forme [/do/delete?id=XX]. HttpServletResponse response) throws ServletException.parseInt(request. on voit l’url [/do/delete?id=XX] du lien [Supprimer]. par l'exemple 165/264 . <td><a href="<c:url value="/do/edit?id=${personne.. 6.. 2. La méthode [doDeletePersonne] qui doit traiter cette url doit supprimer la personne d’id=XX puis faire afficher la nouvelle liste des personnes du groupe.jsp] : dont le code est le suivant : Les bases du développement web MVC en Java. Nous ne faisons aucune vérification. . 14. 16.getParameter("id")).personnes</title> </head> <body background="<c:url value="/ressources/standard.

setAttribute("id".. 14..1. 16. HttpServletResponse response) throws ServletException.setAttribute("dateNaissance". 28.getId()). 19. <td><a href="<c:url value="/do/edit?id=${personne.setAttribute("dateNaissance". personne. } // on met l'objet [Personne] dans le modèle de la vue [edit] request.on récupère la personne à modifier personne = service. 15. 12. 12. <html> <head> <title>MVC . 9.jpg"/>"> . 10. 5. 3. 17. } request.get("urlEdit")). 9. 13. personne. l'url [/do/edit?id=-1] du lien [Ajout]. // ajout ou modification ? Personne personne = null. 10. . La méthode [doEditPersonne] doit faire afficher le formulaire d’édition de la personne d’id=XX ou s'il s’agit d’un ajout présenter un formulaire vide.getPrenom()). Date dateNaissance = personne. 5. par l'exemple 166/264 .setAttribute("nbEnfants". 29. 4. 15. request. if (dateNaissance != null) { request.personnes</title> </head> <body background="<c:url value="/ressources/standard. personne. 16. 7. if (id != -1) { // modification . personne. 3. 18.getMarie()). request. 22. Le code de la méthode [doEditPersonne] est le suivant : 1.getVersion()). 20. 26.getOne(id).getNom()).setAttribute("prenom". 32.setId(-1). 13. 2. // affichage de la vue [edit] getServletContext() . 19.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne. 8.setAttribute("erreurEdit". 25.getDateNaissance(). 17. 2.on crée une personne vide personne = new Personne(). } Les bases du développement web MVC en Java. 23. IOException { // on récupère l'id de la personne int id = Integer. 7.getNbEnfants()). ""). personne. } else { request. new SimpleDateFormat( "dd/MM/yyyy"). } else { // ajout . response). on voit l’url [/do/edit?id=XX] du lien [Modifier] et ligne 17.setAttribute("version".. 30. 11. 21. 6. 33.parseInt(request.forward(request. request.setAttribute("marie".id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html> Ligne 11.setAttribute("nom". 14. ""). 27. 34. <c:forEach var="personne" items="${personnes}"> <tr> .. request.. 11. personne.format(dateNaissance)). // modification / ajout d'une personne private void doEditPersonne(HttpServletRequest request. 31. 6.getParameter("id")).getRequestDispatcher((String) params. 8.. personne. 18. 4. request. 24.

IOException { 4. la bibliothèque JSTL affichera une chaîne vide pour leur valeur. Ce modèle comprend les éléments suivants [erreurEdit. Elle est traitée par la méthode [doValidatePersonne] suivante : 1.. 6. 7. une fois le modèle prêt. boolean erreur. nom. 10. • • La méthode [doValidatePersonne] Cette méthode traite la requête [POST /do/validate] qui valide le formulaire de mise à jour.trim(). // on récupère les éléments postés 5... On sait qu'en leur absence dans le modèle.. Alors il s’agit d’une modification et il faut afficher un formulaire pré-rempli avec les informations de la personne à modifier. 3. nom.. id.. prenom. il est néanmoins initialisé car un test est fait sur sa valeur dans la page [edit. <input type="text" value="${prenom}" name="prenom" size="20"> . Ligne 5. erreurPrenom. une personne vide est créée lignes 13-14. nous récupérons la valeur de [id]. 14. 7. public void doValidatePersonne(HttpServletRequest request. boolean formulaireErroné = false.. erreurDateNaissance.. HttpServletResponse response) throws ServletException. Ligne 10. 13. erreurNom.jsp]. id est différent de -1. Pour cela. 4. version. erreurNom. <input type="text" value="${nom}" name="nom" size="20"> . 15. Bien que l'élément [erreurEdit] ait également pour valeur une chaîne vide.// validation modification / ajout d'une personne 2. id. id est égal à -1. 2.. 6.jsp] décrite page 158. String prenom = request. dateNaissance. 11. <input type="text" value="${dateNaissance}" name="dateNaissance"> . <input type="text" value="${nbEnfants}" name="nbEnfants"> . 16. 3. 2. 8.. erreurNbEnfants]. nbEnfants. Alors il s’agit d’un ajout et il faut afficher un formulaire vide. marie. le contrôle est passé la page [edit. nbEnfants. 12.getParameter("prenom"). marie. erreurDateNaissance.. <input type="hidden" value="${id}" name="id"> <input type="hidden" value="${version}" name="version"> <input type="submit" value="Valider"> </form> La requête POST contient les paramètres [prenom. 9. l'objet [Personne] obtenu est placé dans le modèle de la page [edit. version] et est postée à l'url [/do/validate] (ligne 1).. par l'exemple 167/264 . // le prénom 8.. lignes 32-33. Ensuite il y a deux cas : 1. Ces éléments sont initialisés lignes 17-30 à l'exception de ceux dont la valeur est la chaîne vide [erreurPrenom. cette personne est demandée à la couche [service]. erreurNbEnfants].jsp]. 5. dateNaissance.. <form method="post" action="<c:url value="/do/validate"/>"> .. <input type="radio" name="marie" value="true" checked>Oui . qui va générer la vue [edit]. Ce POST est déclenché par le bouton [Valider] : Rappelons les éléments de saisie du formulaire HTML de la vue ci-dessus : 1..• le GET a pour cible une url du type [/do/edit?id=XX]. Les bases du développement web MVC en Java..

37. 81. // fini 64. 28.setAttribute("nom". // la date de naissance 24. 57. nom. request. } catch (ParseException e) { 29. request. erreur = true. 14. formulaireErroné = true. 25. Personne personne = new Personne(id. if (nbEnfants < 0) { 42. 85.saveOne(personne). 32.getParameter("nom"). } 23. 54. // on note l'erreur 30. service. } 66. request. } 48. "dateNaissance"). if (erreur) { 50. erreur = false. // nombre d'enfants 36. } 55. // on note l'erreur 46. Les bases du développement web MVC en Java.getParameter("version")).setAttribute("id".setAttribute("erreurNom".parseBoolean(request. 68. 89. } 33.getParameter("marie")). // enregistrement 72. // le formulaire est correct .9. throws ServletException.getParameter( 94.getParameter("id")). 52. IOException { 87. if (nom.setAttribute("erreurEdit". } catch (DaoException ex) { 74. erreur = true. // le formulaire est-il erroné ? 60. // on note l'erreur 20. request. formulaireErroné = true. return. // le nom 16. dateNaissance. personne. 92. // on réaffiche le formulaire avec les messages d'erreurs 62. nbEnfants). 13. response. 31.getMessage()). . request. Date dateNaissance = null. 76. 73. prenom. private void showFormulaire(HttpServletRequest request. 93. 90. // prénom valide ? 18.setAttribute("erreurPrenom". int id = Integer.getParameter("marie")). String erreurEdit) 86. request. erreurEdit). request. } 44. } 79.getParameter("nom"). showFormulaire(request. 43. "Nombre d'enfants incorrect"). request. // état marital 34. } catch (NumberFormatException ex) { 45. 47. nbEnfants = Integer. 22.parseInt(request. 63. response. // on note l'erreur 12.sendRedirect("list"). request. formulaireErroné = true.parseLong(request.setAttribute("marie". request. "Le nom est obligatoire"). . request. 65. 70.setAttribute("erreurNbEnfants".trim()). // id de la personne 56.trim()). // on redirige vers la liste des personnes 80. boolean marie = Boolean.on enregistre la personne 67. // fini 77. showFormulaire(request. int nbEnfants = 0.getParameter("dateNaissance"). 78. request. if (formulaireErroné) { 61. HttpServletResponse response. request. String nom = request.getParameter("nbEnfants") 40. if (prenom. try { 39.setAttribute("version". // on réaffiche le formulaire avec le message de l'erreur survenue 75.getParameter("prenom").getParameter("version")). response.setVersion(version). 38. // nombre d'enfants erroné ? 49. dateNaissance = new SimpleDateFormat("dd/MM/yyyy").length() == 0) { 11. 41. try { 26. 59. 95.setAttribute("erreurDateNaissance". } 82. request. // on signale l'erreur 51. long version = Long. ex.getParameter("id")). 91. "Date incorrecte"). par l'exemple 168/264 .trim()). ""). 83. // on prépare le modèle de la vue [edit] 88. 53.length() == 0) { 19.setAttribute("prenom". request. 69. 17. // version 58. marie.setAttribute("dateNaissance". // affichage formulaire pré-rempli 84.trim()).parseInt(request.trim()). formulaireErroné = true. } 15.parse(request 27.trim(). "Le prénom est obligatoire"). request. // prénom valide ? 10. try { 71. return. 21. request. 35.

La sauvegarde peut échouer.forward(request. On ne fait pas de vérification sur sa validité parce qu'à priori il provient de la valeur d'un bouton radio. par l'exemple 169/264 . ligne 75 : s’il y a eu exception lors de la sauvegarde. une exception en sortira qui aboutira à l'envoi de la page [exception. Ce fonctionnement nous convient. 100. Ceci dit. Celui-ci demande l’url [http://localhost:8080/personnes-01] : [IE] sera le navigateur de l’utilisateur U2. la conversion du paramètre [marie] en booléen échoue ligne 34. Si donc. page 137.getRequestDispatcher((String) params. lignes 34-54 : on récupère le paramètre [nbEnfants] et on vérifie sa valeur.getParameter(" .jsp] avec les valeurs saisies (request.jsp] au client. 97. S'il s'avère incorrect. ")). on redirige le client vers l’url [/do/list] pour lui présenter le nouvel état du groupe. lignes 16-22 : on opère de façon similaire pour le paramètre [nom] lignes 24-32 : on opère de façon similaire pour le paramètre [dateNaissance] ligne 34 : on récupère le paramètre [marie]. Nous invitons le lecteur à les rejouer. Celui-ci demande la même Url : Les bases du développement web MVC en Java. request.9 Les tests de l’application web Un certain nombre de tests ont été présentés au paragraphe 14.jsp] est affichée lignes 99-100. 99. On se rappelle que les messages d'erreurs ont déjà été placés dans le modèle par la méthode [doValidatePersonne]. La méthode [showFormulaire] (lignes 84-101) construit le modèle nécessaire à la page [edit. 14. on redemande le réaffichage du formulaire initial en lui passant le message d'erreur de l'exception (3ième paramètre). la couche [dao] va lancer une exception qu’on gère ici. Nous montrons ici d’autres copies d’écran qui illustrent les cas de conflits d’accès aux données dans un cadre multi-utilisateurs : [Firefox] sera le navigateur de l’utilisateur U1.jsp] si le contrôleur ne les gère pas lui-même.getParameter("nbEnfants") .. ligne 80 : s’il n’y a pas eu d’exception. request. on construit un nouvel objet [Personne] avec les éléments du formulaire lignes 70-78 : la personne est sauvegardée. la personne à modifier a pu être supprimée ou bien déjà modifiée par quelqu’un d’autre.96.1. Ici. il est réaffiché avec les messages d'erreurs construits précédemment lignes 67-69 : s'il est valide.. 98. } • • • • • • • • • • • • lignes 8-14 : le paramètre [prenom] de la requête POST est récupéré et sa validité vérifiée. response).get("urlEdit")). Dans ce cas. 101.setAttribute("nbEnfants". on se repose sur notre gestion des exceptions qui provoquent l'affichage de la page [exception. rien n'empêche un programme de faire un [POST /personnes-01/do/validate] accompagné d'un paramètre [marie] fantaisiste. La page [edit. // affichage de la vue [edit] getServletContext() . Dans un cadre multi-utilisateurs.trim()). l'élément [erreurPrenom] est initialisé avec un message d'erreur et placé dans les attributs de la requête. ligne 56 : on récupère le paramètre [id] sans vérifier sa valeur ligne 58 : on fait de même pour le paramètre [version] lignes 60-65 : si le formulaire est erroné. Nous devrions donc tester la validité de ce paramètre.

L’utilisateur U1 entre en modification de la personne [Lemarchand] : L’utilisateur U2 fait de même : L’utilisateur U1 fait des modifications et valide : Les bases du développement web MVC en Java. par l'exemple 170/264 .

par l'exemple 171/264 .réponse du serveur validation L’utilisateur U2 fait de même : validation le conflit de version a été détecté L’utilisateur U2 revient à la liste des personnes avec le lien [Annuler] du formulaire : Il trouve la personne [Lemarchand] telle que U1 l’a modifiée. Maintenant U2 supprime [Lemarchand] : Les bases du développement web MVC en Java.

par l'exemple 172/264 .. metier. Dans la version étudiée.10 Conclusion Nous avons mis en oeuvre l'architecture MVC dans une architecture 3tier [web.org) ainsi que de définir et utiliser la notion d'IoC (Inversion of Control).. Cela nous a permis d’utiliser les concepts qui avaient été présentés dans les précédentes sections. la liste des personnes était maintenue en mémoire. 15 Spring IoC 15. 14.springframework.1 Introduction Nous nous proposons de découvrir les possibilités de configuration et d'intégration du framework Spring (http://www.réponse du serveur . nous allons introduire un outil appelé Spring IoC. Nous étudierons prochainement des versions où cette liste sera maintenue dans une table de base de données. qui facilite l'intégration des différentes couches d'une application ntier.réponse du serveur : il n’a pas trouvé [Lemarchand] U1 utilise le lien [Retour à la liste] pour voir de quoi il retourne : Il découvre qu’effectivement [Lemarchand] ne fait plus partie de la liste..modification . Mais auparavant.Suppression U1 a toujours sa propre liste et veut modifier [Lemarchand] de nouveau : . dao] sur un exemple basique de gestion d’une liste de personnes. également appelée injection de dépendance (Dependency Injection) Les bases du développement web MVC en Java.

5. // init @SuppressWarnings("unchecked") public void init() throws ServletException { . Appelons cette classe [DaoBD] pour l'exemple. // instanciation de la couche [service] service = new ServiceImpl(). 2. page 161) : 1.. 3. l'interface [IDao] sera implémentée par une classe gérant une liste des personnes placée en base de données. par l'exemple 173/264 . dao. service. 8.. 1. respectivement les interfaces [IDao] et [IService]. 7.. . La ligne 9 ci-dessus montre qu'elle est également dépendante de la couche [service].Considérons l'application 3-tier que nous venons de construire : Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Pour répondre aux demandes de l'utilisateur.. Dans des versions à venir.d.3. 4. 6. celle-ci était une instance de type [DaoImpl]. Remplacer l'implémentation [DaoImpl] de la couche [dao) par l'implémentation [DaoBD] va nécessiter une recompilation de la couche [web]. // service ServiceImpl service=null. 5. 10.8. 4. le contôleur [Application] doit s'adresser à la couche [service]. 9. Dans notre exemple. @SuppressWarnings("serial") public class Application extends HttpServlet { . Spring IoC va nous permettre de créer une application 3tier où les couches sont indépendantes des autres. 2.. le contôleur [Application] a obtenu une référence sur la couche [service] dans sa méthode [init] (paragraphe 14.. En effet. la ligne 6 ci-dessus qui instancie la couche [dao] avec un type [DaoImpl] doit désormais l'instancier avec un type [DaoBD]. 11. Notre couche [web] est donc dépendante de la couche [dao]. que changer l'une ne nécessite pas de changer les autres. // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(). 3. c. 6.a. } • • ligne 6 : la couche [dao] a été instanciée par la création explicite d'une instance [DaoImpl] ligne 9 : la couche [service] a été instanciée par la création explicite d'une instance [ServiceImpl] Rappelons que les classes [DaoImpl] et [ServiceImpl] implémentent des interfaces.init(). 7.setDao(dao). L'architecture précédente va évoluer de la façon suivante : Les bases du développement web MVC en Java. Cela apporte une grande souplesse dans l'évolution de l'application.

Changer l'implémentation d'une couche induira un changement dans ce fichier de configuration mais pas dans le contrôleur. 15.springframework.2. par l'exemple 174/264 . dans sa méthode [init]. 3. Présentons maintenant les possibilités de [Spring IoC] à l'aide d'exemples.2 15. 2.Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC Avec [Spring IoC].1 Spring IoC par la pratique Spring [Spring IoC] est une partie d'un projet plus large disponible à l'url [http://www. il va demander à la couche [Spring IoC] de lui donner une référence sur la couche [service] [Spring IoC] va alors exploiter un fichier XML de configuration qui lui indique quelle classe doit être instanciée et comment elle doit être initialisée. L'avantage de cette solution est que désormais le nom des classes instanciant les différentes couches n'est plus codé en dur dans la méthode [init] du contrôleur mais simplement présent dans un fichier de configuration. [Spring IoC] rend au contrôleur [Application] la référence de la couche [service] créée.org/] (mai 2006) : Les bases du développement web MVC en Java. le contrôleur [Application] va obtenir la référence dont il a besoin sur la couche [service] de la façon suivante : 1.

jar. Il faut télécharger la version avce dépendances afin d'éviter d'être obligés ensuite de télécharger les bibliothèques des outils tiers.a.jar] seront trouvés dans le dossier [dist] de la distribution de Spring et [commons-logging.d. c. spring-beans. Ils seront tous dans le projet Eclipse suivant : Le projet [springioc-exemples] est configuré pour que les fichiers source et les classes compilées soient à la racine du dossier du projet : 1 • • • • • • 2 3 4 5 6 [1] : l'arborescence du dossier du projet [Eclipse] [2] : les fichiers de configuration de Spring sont à la racine du projet. donc dans le Classpath de l'application [3] : les classes de l'exemple 1 [4] : les classes de l'exemple 2 [5] : les classes de l'exemple 3 [6] : les bibliothèques du projet [spring-core. [4.jar.jar].5] : les archives des outils tiers 15. L'aspect [IoC] de Spring est assuré par les archives [spring-core. par l'exemple 175/264 .2 Projets Eclipse des exemples Nous allons construire trois exemples illustrant l'utilisation de Spring IoC. les archives .jar du projet Spring lui-même sans ses dépendances. Les bases du développement web MVC en Java. [2] : l'arborescence du fichier zippé téléchargé [3] : la distribution [Spring].jar] dans le dossier [lib/jakarta-commons].1 • • • • 2 3 4 5 [1] : Spring utilise diverses technologies tierces appelées ici dépendances. Ces trois archives ont été incluses dans le Classpath de l'application. spring-beans.2.

public void init() { 16.println("destroy personne [" + this. System. 26.println("init personne [" + this.springioc01. } 39.out. 2. // getters-setters 24. 6. public void setNom(String nom) { 37. } 18. 40.age = age.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Simon" /> <property name="age" value="40" /> </bean> <bean id="personne2" class="istia. return age.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www. 21. } 31. 2. 17.nom + "].toString() + "]"). 10. 9.springioc01.org/dtd/spring-beans. return "nom=[" + this. 7. public int getAge() { 25. public void close() { 20. 12 : la balise <beans> est la balise racine des fichiers de configuration Spring. A l'intérieur de cette balise. 8. 34. 30.st. <?xml version="1. package istia.nom = nom. } 13. 12. une méthode close qui sera appelée à la destruction de l'objet Pour instancier des objets de type [Personne] à l'aide de Spring. 28. this.lignes 23-38 : les méthodes de lecture (get) et d'écriture (set) de ces deux champs .st. 38. 11.lignes 15-21 : une méthode init qui sera appelée par Spring à la création de l'objet. } 27. private String nom.springframework. } La classe présente : . } 35. System.dtd"> <beans> <bean id="personne1" class="istia. 7. 19. private int age. par l'exemple . // init-close 15. public void setAge(int age) { 29.out.st.xml] suivant : 1. nous utiliserons le fichier [spring-config-01.springioc01.15. 4.age + "]".lignes 6-7 : deux champs privés nom et age . } 22.toString() + "]"). public class Personne { 4. age=[" + this. 32. this. 12. // affichage Personne 10. lignes 4-7 : définition d'un bean 176/264 Les bases du développement web MVC en Java. public String toString() { 11. 5. 3. 8. return nom. la balise <bean> sert à définir les différents objets à créer. 3. // caractéristiques 6. 14. 23.3 Exemple 1 Les éléments de l'exemple 1 ont été placés dans le paquetage [springioc01] du projet : La classe [Personne] est la suivante : 1. 9.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Brigitte" /> <property name="age" value="20" /> </bean> </beans> • • lignes 3. public String getNom() { 33.lignes 10-12 : une méthode toString pour récupérer la valeur de l'objet [Personne] sous la forme d'une chaîne de caractères . 36. 5.2.

3. 12. init personne [nom=[Simon].core. age=[20]] Commentaires : . 16. 20.st.lignes 18-19 : on redemande le bean nommé [personne2]. .springioc01.lignes 15-16 : on fait de même pour le bean nommé [personne2]. Il faut donc que cette méthode existe. L'objet [ClassPathResource] sert à rechercher une ressource dans le [ClassPath] d'une application. age=[20]] personne2=nom=[Brigitte].getBean("XX").toString()).beans. Parce que dans la définition du bean [personne1] on avait écrit [initmethod="init"]. // récupération du bean [personne2] une nouvelle fois personne2 = (Personne) bf. System.ClassPathResource. 6.toString()). age=[40] init personne [nom=[Brigitte]. 8. Le fichier [spring-config01. dans l'un des répertoires explorés par la machine virtuelle Java lorsqu'elle cherche une classe référencée par l'application.xml]. 7.a. Pour faire cette initialisation. 5.getBean("personne2"). 23. 9.ligne 21 : on supprime tous les beans de [bf] c. 7. Spring utilisera la méthode [setNom].toString()). . La méthode [init] de l'instance sera appelée une fois celle-ci créée (attribut init-method) et la méthode [close] de l'instance sera appelée avant sa destruction (attribut destroy-method).la ligne 1 a été obtenue par l'exécution de la ligne 12 de [Main]. age=[20] destroy personne [nom=[Simon].lignes 3-4 : le même phénomène se répète pour le bean nommé [personne2].ligne 2 : la ligne 13 de [Main] a fait afficher la valeur de l'objet [Personne] créé. age=[40]] personne1=nom=[Simon].println("personne1=" + personne1. 13. c. 2.st.xml].a. .ligne 10 : pour obtenir les beans définis dans le fichier [spring-config-01. 2.destroySingletons(). import org.• ligne 4 : le bean s'appelle [personne1] (attribut id) et est une instance de la classe [istia. . 4. System.println("personne2=" + personne2. L'opération Personne personne1 = (Personne) bf.println("personne2=" + personne2. System. ici le fichier [spring-config-01. ceux créés à partir du fichier [spring-config-01.out. // récupération du bean [personne2] Personne personne2 = (Personne) bf. 10.Personne] (attribut class). 19.springframework. L'exécution de la classe [Main] donne les résultats suivants : 1. par l'exemple 177/264 . 5.ligne 13 : on affiche la valeur de l'objet [Personne] correspondant. import org. System.toString()).out.getBean("personne1"). C'est le cas ici.d. 4. L'objet [bf] obtenu (Bean Factory) permet d'obtenir la référence d'un bean nommé "XX" par l'instruction bf.xml")).d. 6.getBean("personne2"). age=[40]] destroy personne [nom=[Brigitte]. // on supprime tous les beans bf. . } } Commentaires : . lignes 8-11 : définition analogue d'un bean nommé [personne2] • La classe de test [Main] est la suivante : 1. Les bases du développement web MVC en Java. // récupération du bean [personne1] Personne personne1 = (Personne) bf.getBean("personne1"). .factory. 17. 14. • ligne 5 : définissent la valeur à donner à la propriété [nom] (attribut name) de l'instance [Personne] créée.xml] sera placé dans le [ClassPath] de l'application.xml].XmlBeanFactory. a forcé la création du bean [personne1]. 3.xml. public class Main { public static void main(String[] args) { // exploitation fichier de configuration spring final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-01. 11. 18. 21. .println("personne2=" + personne2. nous utilisons un objet de type [XmlBeanFactory] qui permet d'instancier les beans définis dans un fichier XML.ligne 5 : l'opération des lignes 18-19 de [Main] personne2 = (Personne) bf.out.ligne 12 : on demande une référence sur le bean nommé [personne1] dans le fichier [spring-config-01.getBean("personne2"). Le message correspondant est affiché.springioc01. package istia. • ligne 6 : idem pour la propriété [age]. la méthode [init] de l'objet [Personne] créé a été exécutée. 22. age=[20] personne2=nom=[Brigitte]. .io.xml]. 15.springframework.out.

44. 15. la méthode [close] des deux beans est exécutée et provoque l'affichage des lignes 6-7. 48. this. this. 9. 3. Les bases d'une configuration Spring étant maintenant acquises. } 45. par l'exemple 178/264 . on aurait eu l'affichage de la méthode [init]. public void setPropriétaire(Personne propriétaire) { 39. personne2].marque = marque. package istia. 23.type = type. 13.st. par défaut. private String type. // constructeurs 10. return marque. return "Voiture : marque=[" + this. // caractéristiques 5. Spring. public Voiture() { 11. private String marque. // toString 20. donc les beans [personne1. ne crée qu'un seul exemplaire des beans de son fichier de configuration. public Personne getPropriétaire() { 35. public void setMarque(String marque) { 31. Parce que ces deux beans ont l'attribut [destroy-method="close"]. } 49. 30. // getters-setters 26. } 41. Les bases du développement web MVC en Java. + "] propriétaire=[" + this. 7. Spring se contente d'en donner une référence. } 12. } 24. String type. setType(type). setPropriétaire(propriétaire). Si on lui demande la référence d'un objet non encore créé. il le crée et en rend une référence. Spring se contente d'en rendre une référence.propriétaire + "]". public String toString() { 21. Si cela avait été le cas. 25. public Voiture(String marque. 17. 38. public void setType(String type) { 47. 34. C'est un service de références d'objet. } 33. this.4 Exemple 2 Les éléments de l'exemple 2 sont placés dans le paquetage [springioc02] du projet : Le paquetage [springioc02] est d'abord obtenu par copier / coller du paquetage [springioc01] puis ensuite on y ajoute la classe [Voiture] et on adapte la classe [Main] au nouvel exemple. private Personne propriétaire. public class Voiture { 4. } 18. 2. public String getType() { 43. 16. 6. 8.marque + "] type=[" + this. 46. 19.2. } 29. } 37.propriétaire = propriétaire. 42. public String getMarque() { 27. return type.springioc02. Ici [personne2] ayant déjà été créé. 40.type 22. Personne propriétaire) { 14. nous serons désormais un peu plus rapides dans nos explications. C'est le principe du singleton. 36. les affichages des lignes 6-7 ont été provoqués par la ligne 21 de [Main] qui demande la destruction de tous les beans référencés par l'objet [XmlBeanFactory bf]. setMarque(marque). 15. 28. 32. Si l'objet a déjà été créé. return propriétaire.- n'a pas provoqué la création d'un nouvel objet de type [Personne]. La classe [Voiture] est la suivante : 1.

10.toString() + "]"). 17. String. 7.springframework. String. par l'exemple 179/264 .xml")). 8.println("Voiture1=" + Voiture1. 2. public void close() { 56. . <?xml version="1.st.lignes 5-7 : trois champs privés type. La classe possède également un constructeur sans arguments afin de suivre la norme JavaBean.Voiture" init-method="init" destroy-method="close"> <constructor-arg index="0" value="Peugeot" /> <constructor-arg index="1" value="307" /> <constructor-arg index="2"> <ref local="personne2" /> </constructor-arg> </bean> </beans> Ce fichier ajoute aux beans définis dans [spring-config-01.xml. Pour initialiser ce bean.core. <property name="type" value="307"/> 4. 14. 11.toString() + "]").io. on aurait pu écrire : 1.out. <bean id="voiture1" class="istia. Pour nos tests. } 54.springframework. de sa classe. 5. // init-close 51.lignes 20-23 : une méthode toString pour récupérer la valeur de l'objet [Voiture] sous la forme d'une chaîne de caractères . Personne) défini lignes 13-17. 6. 8.ClassPathResource. 11. System.springframework. Personne)].Personne" init-method="init" destroy-method="close"> <property name="nom" value="Brigitte" /> <property name="age" value="20" /> </bean> <bean id="voiture1" class="istia. On lui fournit comme valeur.XmlBeanFactory.</bean> Plutôt que de choisir cette méthode déjà présentée dans l'exemple 1. • • • • ligne 12 : définition du nom du bean. import org. 18. 9.println("init voiture [" + this.domain.springioc02. Les bases du développement web MVC en Java. Ils peuvent être également initialisés à l'aide du constructeur Voiture(String. nous utiliserons le fichier Spring [spring-config-02. 3. la référence (balise ref) du bean [personne2] défini dans le même fichier (attribut local).xml] un bean de clé "voiture1" de type [Voiture] (lignes 12-17). 2.st.out. package istia. nous utiliserons la classe [Main] suivante : 1. ligne 14 : valeur du 2ième paramètre du constructeur [Voiture(String. String. 12.xml] suivant : 1.org/dtd/spring-beans.50. de la méthode à exécuter après son instanciation. } La classe présente : . System. 12.springioc02. 16.dtd"> <beans> <bean id="personne1" class="istia. 6.println("destroy voiture [" + this. 15. 13. Personne)].st.lignes 51-57 : une méthode init qui sera appelée par Spring juste après la création de l'objet. 7. import org. 4.out. 55. Personne) de la classe. <property name="marque" value="Peugeot"/> 3. 5. } 58. 9.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www. System.springioc02. lignes 15-17 : valeur du 3ième paramètre du constructeur [Voiture(String. marque et propriétaire.st.toString()). Ce paramètre est de type [Personne].factory. </property> 7. 3. <ref local="personne2"/> 6.beans.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Simon" /> <property name="age" value="40" /> </bean> <bean id="personne2" class="istia. // récupération du bean [voiture1] Voiture Voiture1 = (Voiture) bf. public void init() { 52.springioc02. 13. String. nous avons choisi d'utiliser ici le constructeur Voiture(String.getBean("voiture1").Voiture" init-method="init" destroy-method="close"> 2. 53. ligne 13 : valeur du 1er paramètre du constructeur [Voiture(String. une méthode close qui sera appelée à la destruction de l'objet Pour créer des objets de type [Voiture]. Personne)].st. 10.springioc. de la méthode à exécuter après sa suppression. 19. <property name="propriétaire"> 5. 4. Ces champs peuvent être initialisés et lus par des méthodes publiques de beans get et set des lignes 26-48. 57. public class Main { public static void main(String[] args) { // exploitation fichier de configuration spring final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-02. String.

14. // on supprime les beans 15. bf.destroySingletons(); 16. } 17. }

La méthode [main] demande la référence du bean [voiture1] (ligne 12) et l'affiche (ligne 13). Les résultats sont les suivants :
1. 2. 3. 4. 5. init personne [nom=[Brigitte], age=[20]] init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]] Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]] destroy voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]] destroy personne [nom=[Brigitte], age=[20]]

Commentaires : 1. la méthode [main] demande une référence sur le bean [voiture1] (ligne 12). Spring commence la création du bean [voiture1] car ce bean n'a pas encore été créé (singleton). Parce que le bean [voiture1] référence le bean [personne2], ce dernier bean est construit à son tour. Le bean [personne2] a été créé. Sa méthode [init] est alors exécutée (ligne 1) des résultats. Le bean [voiture1] est ensuite instancié. Sa méthode [init] est alors exécutée (ligne 2) des résultats. 2. la ligne 3 des résultats provient de la ligne 13 de [main] : la valeur du bean [voiture1] est affichée. 3. la ligne 15 de [main] demande la destruction de tous les beans existants, ce qui provoque les affichages des lignes 4 et 5 des résultats.

15.2.5

Exemple 3

Les éléments de l'exemple 3 sont placés dans le paquetage [springioc03] du projet :

Le paquetage [springioc03] est d'abord obtenu par copier / coller du paquetage [springioc01] puis ensuite on y ajoute la classe [GroupePersonnes], on supprime la classe [Voiture] et on adapte la classe [Main] au nouvel exemple. La classe [GroupePersonnes] est la suivante :
1. package istia.st.springioc03; 2. 3. import java.util.Map; 4. 5. public class GroupePersonnes { 6. 7. // caractéristiques 8. private Personne[] membres; 9. private Map groupesDeTravail; 10. 11. // getters - setters 12. public Personne[] getMembres() { 13. return membres; 14. } 15. 16. public void setMembres(Personne[] membres) { 17. this.membres = membres; 18. } 19. 20. public Map getGroupesDeTravail() { 21. return groupesDeTravail; 22. } 23. 24. public void setGroupesDeTravail(Map groupesDeTravail) { 25. this.groupesDeTravail = groupesDeTravail; 26. } 27. 28. // affichage 29. public String toString() { 30. String liste = "membres : "; 31. for (int i = 0; i < this.membres.length; i++) { 32. liste += "[" + this.membres[i].toString() + "]"; 33. } 34. return liste + ", groupes de travail = " 35. + this.groupesDeTravail.toString(); 36. } 37. 38. // init-close 39. public void init() { Les bases du développement web MVC en Java, par l'exemple

180/264

40. System.out.println("init GroupePersonnes [" + this.toString() + "]"); 41. } 42. 43. public void close() { 44. System.out.println("destroy GroupePersonnes [" + this.toString() + "]"); 45. } 46. }

Ses deux membres privés sont : ligne 8 : membres : un tableau de personnes membres du groupe ligne 9 : groupesDeTravail : un dictionnaire affectant une personne à un groupe de travail On remarquera ici que la classe [GroupePersonnes] ne définit pas de constructeur sans argument. On rappelle qu'en l'absence de tout constructeur, il existe un constructeur "par défaut" qui est le constructeur sans arguments et qui ne fait rien. On cherche ici, à montrer comment Spring permet d'initialiser des objets complexes tels que des objets possédant des champs de type tableau ou dictionnaire. Le fichier des beans [spring-config-03.xml] de l'exemple 3 est le suivant :
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. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="personne1" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Simon" /> <property name="age" value="40" /> </bean> <bean id="personne2" class="istia.st.springioc03.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Brigitte" /> <property name="age" value="20" /> </bean> <bean id="groupe1" class="istia.st.springioc03.GroupePersonnes" init-method="init" destroy-method="close"> <property name="membres"> <list> <ref local="personne1" /> <ref local="personne2" /> </list> </property> <property name="groupesDeTravail"> <map> <entry key="Brigitte" value="Marketing" /> <entry key="Simon" value="Ressources humaines" /> </map> </property> </bean> </beans>

• •

lignes 14-17 : la balise <list> permet d'initialiser un champ de type tableau ou implémentant l'interface List avec différentes valeurs. lignes 20-23 : la balise <map> permet de faire la même chose avec un champ implémentant l'interface Map.

Pour nos tests, nous utiliserons la classe [Main] suivante :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. package istia.st.springioc03; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class Main { public static void main(String[] args) { // exploitation fichier de configuration spring final XmlBeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config-03.xml")); // récupération du bean [groupe1] GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1"); System.out.println("groupe1=" + groupe1.toString()); // on supprime les beans bf.destroySingletons(); }

}

lignes 12-13 : on demande à spring une référence sur le bean [groupe1] et on affiche la valeur de celui-ci.

Les résultats obtenus sont les suivants :
1. 2. 3. 4. 5. 6. init personne [nom=[Simon], age=[40]] init personne [nom=[Brigitte], age=[20]] init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}] groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines} destroy GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}] destroy personne [nom=[Simon], age=[40]]

Les bases du développement web MVC en Java, par l'exemple

181/264

7.

destroy personne [nom=[Brigitte], age=[20]]

Commentaires :

• •

ligne 12 de [Main], on demande une référence du bean [groupe1]. Spring commence la création de ce bean. Parce que le bean [groupe1] référence les beans [personne1] et [personne2], ces deux beans sont créés (lignes 1 et 2) des résultats. Le bean [groupe1] est ensuite instancié et sa méthode [init] exécutée (ligne 3 des résultats). la ligne 13 de [Main] fait afficher la ligne 4 des résultats. la ligne 15 de [Main] fait afficher les lignes 5-7 des résultats.

15.3

Configuration d'une application n tier avec Spring

Considérons une application 3-tier ayant la structure suivante :

utilisateur

Couche interface utilisateur [ui]

Couche métier [metier]

Couche d'accès aux données [dao]

Données

Nous nous proposons de montrer ici l'intérêt de Spring pour construire une telle architecture. • • les trois couches seront rendues indépendantes grâce à l'utilisation d'interfaces Java l'intégration des trois couches sera réalisée par Spring

La structure de l'application sous Eclipse pourrait être la suivante :

1 6 2 3 5 4

• • •

[1] : la couche [dao] : • [IDao] : l'interface de la couche • [Dao1, Dao2] : deux implémentations de cette interface [2] : la couche [metier] : • [IMetier] : l'interface de la couche • [Metier1, Metier2] : deux implémentations de cette interface [3] : la couche [ui] : • [IUi] : l'interface de la couche • [Ui1, Ui2] : deux implémentations de cette interface [4] : les fichiers de configuration Spring de l'application. Nous configurerons l'application de deux façons. [5] : les bibliothèques nécessaires à l'application. Ce sont celles utilisées dans les exemples précédents. [6] : le paquetage des tests. [Main1] utilisera la configuration [spring-config-01.xml] et [Main2] la configuration [spring-config-02.xml]. 182/264

Les bases du développement web MVC en Java, par l'exemple

Le but de cet exemple est de montrer que nous pouvons changer l'implémentation d'une ou plusieurs couches de l'application avec un impact zéro sur les autres couches. Tout se passe dans le fichier de configuration de Spring. La couche [dao] La couche [dao] implémente l'interface [IDao] suivante :
1. 2. 3. 4. 5. package istia.st.springioc.troistier.dao; public interface IDao { public int doSomethingInDaoLayer(int a, int b); }

L'implémentation [Dao1] sera la suivante :
1. 2. 3. 4. 5. 6. 7. 8. package istia.st.springioc.troistier.dao; public class Dao1 implements IDao { public int doSomethingInDaoLayer(int a, int b) { return a+b; }

}

L'implémentation [Dao2] sera la suivante :
1. 2. 3. 4. 5. 6. 7. 8. package istia.st.springioc.troistier.dao; public class Dao2 implements IDao { public int doSomethingInDaoLayer(int a, int b) { return a-b; }

}

La couche [métier] La couche [métier] implémente l'interface [IMetier] suivante :
1. 2. 3. 4. 5. package istia.st.springioc.troistier.metier; public interface IMetier { public int doSomethingInBusinessLayer(int a, int b); }

L'implémentation [Metier1] sera la suivante :
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 istia.st.springioc.troistier.metier; import istia.st.springioc.troistier.dao.IDao; public class Metier1 implements IMetier { // couche [dao] private IDao dao = null; public IDao getDao() { return dao; } public void setDao(IDao dao) { this.dao = dao; } public int doSomethingInBusinessLayer(int a, int b) { a++; b++; return dao.doSomethingInDaoLayer(a, b); } }

L'implémentation [Metier2] sera la suivante :
1. 2. 3. 4. package istia.st.springioc.troistier.metier; import istia.st.springioc.troistier.dao.IDao;

Les bases du développement web MVC en Java, par l'exemple

183/264

5. public class Metier2 implements IMetier { 6. 7. // couche [dao] 8. private IDao dao = null; 9. 10. public IDao getDao() { 11. return dao; 12. } 13. 14. public void setDao(IDao dao) { 15. this.dao = dao; 16. } 17. 18. public int doSomethingInBusinessLayer(int a, int b) { 19. a--; 20. b--; 21. return dao.doSomethingInDaoLayer(a, b); 22. } 23. 24. }

La couche [ui] La couche [ui] implémente l'interface [IUi] suivante :
1. 2. 3. 4. 5. package istia.st.springioc.troistier.ui; public interface IUi { public int doSomethingInUiLayer(int a, int b); }

L'implémentation [Ui1] sera la suivante :
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 istia.st.springioc.troistier.ui; import istia.st.springioc.troistier.metier.IMetier; public class Ui1 implements IUi { // couche [business] private IMetier metier = null; public IMetier getMetier() { return metier; } public void setMetier(IMetier business) { this.metier = business; } public int doSomethingInUiLayer(int a, int b) { a++; b++; return metier.doSomethingInBusinessLayer(a, b); } }

L'implémentation [Ui2] sera la suivante :
1. package istia.st.springioc.troistier.ui; 2. 3. import istia.st.springioc.troistier.metier.IMetier; 4. 5. public class Ui2 implements IUi { 6. 7. // couche [business] 8. private IMetier metier = null; 9. 10. public IMetier getMetier() { 11. return metier; 12. } 13. 14. public void setMetier(IMetier business) { 15. this.metier = business; 16. } 17. 18. public int doSomethingInUiLayer(int a, int b) { 19. a--; 20. b--; 21. return metier.doSomethingInBusinessLayer(a, b); 22. } 23. Les bases du développement web MVC en Java, par l'exemple

184/264

springioc.springframework. 4. 6.springioc.beans. 16.troistier. 4.troistier. par l'exemple 185/264 .dao. Dao1] des couches.springioc.troistier.factory. 5.IUi.st. 15. 11.la classe UI --> <bean id="ui" class="istia.springframework.out. 7.st. } } Le programme [Main1] utilise le fichier de configuration [spring-config-01. // on utilise la classe int a = 10.ClassPathResource.ClassPathResource.Metier2"> <property name="dao"> <ref local="dao" /> </property> </bean> <!-.st. 10. 7.springframework. 16. 2. 19.springioc. 3.xml.st.ui.XmlBeanFactory.main.core. Les résultats obtenus sur la console Eclipse : ui(10.Dao1"/> <!-. 5. 12." + b + ")=" + res).springioc. public class Main2 { Les bases du développement web MVC en Java. 4. 8.la classe métier --> <bean id="metier" class="istia. 13. 10. 9. import org. 15. 20.factory. 12. b). <?xml version="1.IUi. 18.st.ui. 16.la classe dao --> <bean id="dao" class="istia.dtd"> <beans> <!-. 3.st. 6.springioc. 10.st.XmlBeanFactory.ui. 11. 15.springframework.24.springframework.beans.org/dtd/spring-beans.st. 17. <?xml version="1.xml. 7. import istia.core.xml] : 1. 14.springioc.ui.troistier. int res = ui.xml] et donc les implémentations [Ui1.troistier.springioc.io.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.metier.main. 6. 5. 14. 6. 3.getBean("ui").st. 19. import istia.Ui1"> <property name="metier"> <ref local="metier" /> </property> </bean> </beans> Le second [spring-config-02. 11. 18.println("ui(" + a + ". 9.troistier. 18.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www. 14.springframework.st. import org.metier. 2.io. 8. import org. Metier1.Ui2"> <property name="metier"> <ref local="metier" /> </property> </bean> </beans> Les programmes de test Le programme [Main1] est le suivant : 1. } Les fichiers de configuration Spring Le premier [spring-config-01. 12.la classe métier --> <bean id="metier" class="istia.la classe UI --> <bean id="ui" class="istia. 5.springioc. b = 20. 17. 2.xml"))). // on affiche le résultat System.doSomethingInUiLayer(a.troistier. 4.la classe dao --> <bean id="dao" class="istia.org/dtd/spring-beans. package istia. 13.springioc.troistier. 13. package istia.20)=34 Le programme [Main2] est le suivant : 1. 17.troistier. 9. public class Main1 { public static void main(String[] args) { // on récupère une implémentation de l'interface IUi IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01. import org. 2.dtd"> <beans> <!-.troistier.dao. 9. 3. 7.Metier1"> <property name="dao"> <ref local="dao" /> </property> </bean> <!-.xml] : 1. 8. 8.Dao2"/> <!-.

On y change l'implémentation d'une couche par simple configuration. Il permet d'ajouter. 15. } 19. Ceci est obtenu grâce au concept IoC qui est l'un des deux piliers de Spring. public static void main(String[] args) { 11. Cela va nous amener à changer la couche [dao]. du "comportement" à une méthode de classe sans modifier le code de celle-ci.10.doSomethingInUiLayer(a. System. Dao2] des couches. int res = ui. 16. 20. Nous souhaitons faire évoluer l'application vers un environnement plus réaliste où la liste des personnes serait mémorisée dans une table de bases de données. int a = 10. par l'exemple 186/264 .4 Conclusion L'application que nous avons construite possède une grande souplesse d'évolution. Metier2. // on affiche le résultat 17. IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.getBean("ui"). } Le programme [Main2] utilise le fichier de configuration [spring-config-02.println("ui(" + a + ". Pour profiter de l'indépendance des couches amenée par Spring IoC. 18.xml")))." + b + ")=" + res). Les résultats obtenus sur la console Eclipse : ui(10. nous allons reprendre l'application [personnes-01] et la configurer avec Spring IoC : Les bases du développement web MVC en Java. Le code des autres couches reste inchangé. Cela nous a permis de ne pas nous apesantir sur les couches [dao] et [service] pour nous concentrer sur la couche [web].1 Introduction Nous avons écrit l'application [personnes-01] ayant la structure suivante : Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données La couche [dao] implémentait la liste des personnes gérée à l'aide d'un objet [ArrayList]. // on utilise la classe 14. b). 13. L'autre pilier est AOP (Aspect Oriented Programming) que nous n'avons pas présenté. Cela va impacter les deux autres couches.out. 16 Application web MVC dans une architecture 3tier – Exemple 2 16.20)=-10 15. // on récupère une implémentation de l'interface IUi 12. b = 20.xml] et donc les implémentations [Ui2. également par configuration.

<beans> 4. par l'exemple 187/264 . </beans> • • • ligne 5 : définit le bean nommé [dao] comme une instance de la classe [DaoImpl].la classe dao --> 5. lignes 8-10 : la propriété [dao] de l'instance [DaoImpl] est initialisée avec la référence de la couche [dao] créée ligne 5. <bean id="dao" class="istia. C'est cela que nous recherchons. </bean> 12. nous savons que nous pourrons changer les couches [dao] et [service] sans changement du code de la couche [web]. nous voyons apparaître le fichier de configuration dans lequel nous allons définir les beans des couches [dao] et [service]. 3.dtd"> 3. 9.ServiceImpl"> 8. 5. Une fois qu'elle aura été écrite. 6.personnes. 4. page 78 : 1 En [1].st.st.DaoImpl" init-method="init"/> 6. 2. public class ServiceImpl implements IService { // la couche [dao] private IDao dao. Son contenu est le suivant : 1. <?xml version="1. <bean id="service" class="istia. public IDao getDao() { return dao.service.Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC La nouvelle application s'appellera [personnes-02]. Rappelons que la classe [ServiceImpl] a bien la propriété [dao] et le setter qui va avec : 1. <property name="dao"> 9. </property> 11. 7.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www. 2.personnes.la classe service --> 7. la méthode [init] de l'instance est exécutée. } Les bases du développement web MVC en Java.dao.mvc. <!-.2. 8.springframework. Nous créons un nouveau projet Eclipse [personnes-02] par copier / coller du projet [personnes-01] comme il a été expliqué au paragraphe 6.org/dtd/springbeans. <ref local="dao" /> 10. lignes 7-10 : définissent le bean nommé [service] comme une instance de la classe [ServiceImpl].mvc. <!-. Après instanciation.

service = (IService) new XmlBeanFactory(new ClassPathResource("spring-config. par l'exemple 188/264 .init().a. public void setDao(IDao dao) { 11. @SuppressWarnings("serial") 2. 8. c. } En ligne 5. // service 7. Rappelons la version précédente de la méthode [init] du contrôleur : 1. // paramètres d'instance 4. dao. 14. 15. ligne 14 : initialisation du champ [service] à partir du fichier de configuration [spring-config. lançons celui-ci puis demandons l'Url [http://localhost:8080/personnes-02] : 16.xml].2 Mise en archives de l’application web Nous avons développé un projet Eclipse / Tomcat d’une application 3tier : Les bases du développement web MVC en Java. 8. Intégrons cette nouvelle application dans Tomcat. // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(). // init 10.. this. 17. . private IService service = null. 5.. @SuppressWarnings("unchecked") 11. 13... // service ServiceImpl service = null. 6. // instanciation de la couche [service] service = new ServiceImpl(). 16.10. 9. 3.. 9.getBean("service"). .. nous avions été obligés de nommer explicitement la classe d'implémentation de la couche [service] et en ligne 12. 6. Avec Spring IoC.dao = dao. Ce sont les seules modifications à faire. public void init() throws ServletException { 12. @SuppressWarnings("serial") public class Application extends HttpServlet { . 4. // init @SuppressWarnings("unchecked") public void init() throws ServletException { . public class Application extends HttpServlet { 3.xml")).d. La couche [web] n'est donc plus liée à une implémentation particulière de cette interface. 11. . } 13. 7. 12. du type de l'interface de la couche [service]. celle de la couche [dao]. Seul ce fichier ne fait rien bien sûr. 12. 5.. // instanciation de la couche [service] 14.. 10.setDao(dao).. service. Le contrôleur [Application] va l'utiliser dans sa méthode [init] pour instancier la couche [service]. 13. 15. la méthode [init] devient la suivante : 1.. 2. } • • ligne 7 : le champ privé [service] n'est plus de type [ServiceImpl] mais de type [IService].

Il contenait auparavant également le code source des classes Java. La couche [web] va elle rester inchangée.xml]. par l'exemple 189/264 . service. Ces éléments ont disparu. La couche [service] va être également modifiée. Actuellement. Cela. entites] ont été mises dans des archives . le groupe de personnes va être placée dans une table de base de données. copie du projet précédent [mvc-personnes-02] mais où les couches [web.jar] montrés en [1] : Les bases du développement web MVC en Java. remplacés par leurs éléments compilés mis dans les archives [personnes-*. Afin de faciliter le passage d’une version à l’autre. dao. la couche [service] aura encore l’unique rôle de synchronisation des accès mais celle-ci sera assurée par des transactions de base de données plutôt que par une synchronisation de méthodes Java. son unique rôle est d’assurer un accès synchronisé aux données gérées par la couche [dao].jar : 1 Le dossier [src] ne contient désormais que le fichier de configuration Spring [spring-config. nous créons un nouveau projet Eclipse [mvc-personnes-02B]. Dans ce but. Nous avons expliqué pourquoi cette synchronisation était placée dans cette couche plutôt que dans la couche [dao].Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC Dans une version à venir. on peut aisément le comprendre. Dans la nouvelle version. • • • Cela va induire une réécriture de la couche [dao]. nous avons synchronisé toutes les méthodes de la couche [service].

com/divers/sql-firebird/] des informations pour installer et gérer ce SGBD. La base de données s’appelle [dbpersonnes. Nous n’aurons pas à toucher aux archives de la couche [web] et de la couche [entites].. Nous déployons le projet web [mvc-personnes-02B] au sein de Tomcat : Pour tester le projet.gdb].couche [dao] . nous allons changer les couches [service] et [dao]. Dans la version avec base de données. les copies d’écran proviennent d’ IBExpert.jar] par les nouvelles archives pour que notre application fonctionne désormais avec une base de données.jar ] dans son ClassPath.jar] et [personnes-service.developpez.couche [entites] . de remplacer dans le projet précédent les archives [personnes-dao.couche [web] Le projet [mvc-personnes-02B] a été configuré pour inclure les archives [personnes-*. Nous voulons montrer qu’il suffira alors.couche [service] . un client d’administration des SGBD Interbase et Firebird. Dans ce qui suit.1 La base de données Firebird Dans cette nouvelle version. On trouvera dans le document [http://tahe. par l'exemple 190/264 . nous lançons Tomcat puis demandons l’url [http://localhost:8080/personnes02B] : Le lecteur est invité à faire des tests complémentaires. nous allons installer la liste des personnes dans une table de base de données Firebird. 17 Application web MVC dans une architecture 3tier – Exemple 3 – Sgbd Firebird 17. Elle contient une table [PERSONNES] : Les bases du développement web MVC en Java.

destinée à sauvegarder des objets de type [Personne].La table [PERSONNES] contiendra la liste des personnes gérée par l’application web. 10. 14. reflète la structure de cet objet. par l'exemple 191/264 . • • • lignes 2-10 : la structure de la table [PERSONNES]. Elle a été construite avec les ordres SQL suivants : 1. le champ [MARIE] (ligne 8) a été déclaré de type [SMALLINT]. Prenons un exemple pour illustrer son fonctionnement : . un entier. NOM VARCHAR(30) NOT NULL. Le type booléen n’existant pas dans Firebird. "VERSION" INTEGER NOT NULL. CHK_ENFANTS_PERSONNES check (NBENFANTS>=0).double-cliquer sur [GEN_PERSONNES_ID] Les bases du développement web MVC en Java. 4. 2. DATENAISSANCE DATE NOT NULL. ligne 19 : le champ ID est clé primaire de la table [PERSONNES] La table [PERSONNES] pourrait avoir le contenu suivant : La base [dbpersonnes. lignes 13-16 : des contraintes d’intégrité qui reflètent celles du validateur de données [ValidatePersonne]. ALTER ALTER ALTER ALTER TABLE TABLE TABLE TABLE PERSONNES PERSONNES PERSONNES PERSONNES ADD ADD ADD ADD CONSTRAINT CONSTRAINT CONSTRAINT CONSTRAINT CHK_PRENOM_PERSONNES check (PRENOM<>''). 11. 5. Ce générateur délivre des nombres entiers successifs que nous utiliserons pour donner sa valeur. MARIE SMALLINT NOT NULL. CREATE TABLE PERSONNES ( ID INTEGER NOT NULL. ALTER TABLE PERSONNES ADD CONSTRAINT PK_PERSONNES PRIMARY KEY (ID). 18. outre la table [PERSONNES].gdb] a. 17. 12. 6. 8. 3. 16. 7.le générateur a actuellement la valeur 96 . un objet appelé générateur et nommé [GEN_PERSONNES_ID]. Sa valeur sera 0 (pas marié) ou 1 (marié). CHK_MARIE_PERSONNES check (MARIE=0 OR MARIE=1). NBENFANTS SMALLINT NOT NULL ). 19. 15. 9. 13. PRENOM VARCHAR(30) NOT NULL. CHK_NOM_PERSONNES check (NOM<>''). à la clé primaire [ID] de la classe [PERSONNES].

2 Le projet Eclipse des couches [dao] et [service] Pour développer les couches [dao] et [service] de notre application avec base de données. GEN_ID est une fonction interne de Firebird et [RDB$DATABASE].la valeur obtenue est l’ancienne valeur du générateur +1 On peut constater que la valeur du générateur [GEN_PERSONNES_ID] a changé (double-clic dessus + F5 pour rafraîchir) : L’ordre SQL SELECT GEN_ID ( GEN_PERSONNES_ID. Cette couche n’a donc pas à être écrite.1 ) FROM RDB$DATABASE permet donc d’avoir la valeur suivante du générateur [GEN_PERSONNES_ID].émettons l’ordre SQL ci-dessus (F12) -> . pas un projet web Tomcat. Rappelons que la version 2 de notre application va utiliser la couche [web] de la version 1.. par l'exemple 192/264 . une table système de ce SGBD. Dossier [src] Ce dossier contient les codes source des couches [dao] et [service] : On y trouve différents paquetages : Les bases du développement web MVC en Java. 17. nous utiliserons le projet Eclipse [mvc-personnes-03] suivant : Le projet est un simple projet Java.

44. [dbpersonnes. 45. 17. 0.st. 'Mélanie'. 'Lemarchand'. 11. NOM. 1). 1. par l'exemple 193/264 .service] : contient la classe [service] [istia. 'Humbort'. 10. 33. "VERSION".sql] est le script SQL de génération de la base : /******************************************************************************/ /*** Generated by IBExpert 2006. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_ENFANTS_PERSONNES check (NBENFANTS>=0). 19. 26.dao] : contient la couche [dao] [istia.st. 48. 0.mvc.07 27/04/2006 10:27:11 ***/ /******************************************************************************/ SET SQL DIALECT 3. 2. 36.personnes. 34. 29. 28. COMMIT WORK. NOM.mvc. SET NAMES NONE. DATENAISSANCE.gdb] est la base de données. DATENAISSANCE. 12. 39. 38. 56. 23. SET GENERATOR GEN_PERSONNES_ID TO 787. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_MARIE_PERSONNES check (MARIE=0 OR MARIE=1). 6. 5. 1. INSERT INTO PERSONNES (ID. 2).personnes. 21. NOM. 14. 13. MARIE. /******************************************************************************/ /*** Generators ***/ /******************************************************************************/ CREATE GENERATOR GEN_PERSONNES_ID. INSERT INTO PERSONNES (ID. 1. CREATE DATABASE 'C:\data\2005-2006\webjava\dvp-spring-mvc\mvc-38\database\DBPERSONNES. MARIE. MARIE. INSERT INTO PERSONNES (ID. NBENFANTS) VALUES (1. '1986-03-01'. 35. PRENOM. /******************************************************************************/ /*** Tables ***/ /******************************************************************************/ CREATE TABLE PERSONNES ( ID INTEGER NOT NULL. MARIE SMALLINT NOT NULL.GDB' USER 'SYSDBA' PASSWORD 'masterkey' PAGE_SIZE 16384 DEFAULT CHARACTER SET NONE. 37. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_NOM_PERSONNES check (NOM<>''). Dossier [database] Ce dossier contient la base de données Firebird des personnes : • • 1. '1984-11-13'. 47. DATENAISSANCE DATE NOT NULL. 27.personnes. 32. Les bases du développement web MVC en Java. 57.03. PRENOM. 22. "VERSION". 30. PRENOM VARCHAR(30) NOT NULL. 46. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_PRENOM_PERSONNES check (PRENOM<>''). 31.entites] : contient la classe [Personne] [istia. 55.st. DATENAISSANCE. 4. 43. 16. 1. 53. 'Charles'. PRENOM. 40.mvc. 9.st. 20. [dbpersonnes. "VERSION".tests] : contient les tests JUnit des couches [dao] et [service] ainsi que des fichiers de configuration qui doivent être dans le ClassPath de l’application.mvc. /******************************************************************************/ 60. NBENFANTS) VALUES (3. 15. 25. /* Check constraints definition */ 50. 'Joachim'. 8. NOM VARCHAR(30) NOT NULL. "VERSION" INTEGER NOT NULL. 54. 24. '1985-02-12'. 'Major'.personnes. 7. 42.• • • • [istia. 41. 51. NBENFANTS) VALUES (2. 49. 52. /*** Primary Keys ***/ 59. /******************************************************************************/ 58. 18. 0). NBENFANTS SMALLINT NOT NULL ). 3.

Dossier [lib] Ce dossier contient les archives nécessaires à l’application : On notera la présence du pilote JDBC [firebirdsql-full. lancées par la couche [dao].3 17.3. On peut aussi n'utiliser que les seules archives nécessaires au projet. [DaoException] est le type des exceptions non contrôlées.jar] : archive de la couche [service] 17.mvc. [DaoImplFirebird] est une classe dérivée de [DaoImplCommon] pour gérer spécifiquement une base Firebird. ALTER TABLE PERSONNES ADD CONSTRAINT PK_PERSONNES PRIMARY KEY (ID). Toutes ces archives du dossier [lib] ont été placées dans le Classpath du projet. [DaoImplCommon] regroupe des fonctionnalités indépendantes du SGBD. par l'exemple 194/264 .1 La couche [dao] Les composantes de la couche [dao] La couche [dao] est constituée des classes et interfaces suivantes : • • • • [IDao] est l’interface présentée par la couche [dao] [DaoImplCommon] est une implémentation de celle-ci où le groupe de personnes se trouve dans une table de base de données.personnes.st.jar] du SGBD Firebird ainsi que d'un certain nombre d'archives [spring*. Nous aurions pu utiliser l'unique archive [spring.jar] que l'on trouve dans le dossier [dist] de la distribution et qui contient la totalité des classes de Spring. Dossier [dist] Ce dossier contiendra les archives issues de la compilation des classes de l’application : • • [personnes-dao. package istia.dao. C'est ce que nous avons fait ici en nous laissant guider par les erreurs de classes absentes signalées par Eclipse et les noms des archives partielles de Spring.61. Cette classe est celle de la version 1.jar]. Les bases du développement web MVC en Java. L’interface [IDao] est la suivante : 1.jar] : archive de la couche [dao] [personnes-service.

. package istia. 47. } • • • lignes 8-9 : la classe [DaoImpl] implémente l’interface [IDao] et donc les quatre méthodes [getAll. 23.support. 8.util. 5. 43. 26. 195/264 Les bases du développement web MVC en Java. 36. getOne.Personne.. 6. 21.Collection. par l'exemple . 9. } // ajouter ou modifier une personne public void saveOne(Personne personne) { // le paramètre personne est-il valide ? check(personne). 3. } else { updatePersonne(personne). 15. La classe [DaoImplCommon] implémentant cette interface sera la suivante : 1. 34.ibatis. 49. // obtenir une personne particulière Personne getOne(int id). 37. 41. lignes 27-37 : la méthode [saveOne] utilise deux méthodes internes [insertPersonne] et [updatePersonne] selon qu'on doit faire un ajout ou une modification de personne..personnes. Nous ne reviendrons pas dessus. 16.mvc. 14. 17. 29. 33. 38. 15.springframework. 13. 6. 8. 1. 10. 7.mvc. 20. import istia. 24.st. 12. 48. 11. ligne 50 : la méthode privée [check] est celle de la version précédente.entites.. } • l’interface a les mêmes quatre méthodes que dans la version précédente. 5. 28. public interface IDao { // liste de toutes les personnes Collection getAll().SqlMapClientDaoSupport..entites. // supprimer une personne void deleteOne(int id). 22. import java. } } // ajouter une personne protected void insertPersonne(Personne personne) { .. 18. 35. } // modifier une personne protected void updatePersonne(Personne personne) { .. 25. 19. 4. 14. 2. } // vérification validité d'une personne private void check(Personne p) { . 53. saveOne..mvc.dao. 4.Collection.st.. // ajout ou modification ? if (personne. 51. 39. 32. 54.. 12.personnes.orm. } // suppression d'une personne public void deleteOne(int id) { .Personne. 13... 42. 31. import java. public class DaoImplCommon extends SqlMapClientDaoSupport implements IDao { // liste des personnes public Collection getAll() { . } . } // obtenir une personne en particulier public Personne getOne(int id) { . 44. import org.2. 9. 3. 11. 46. // ajouter/modifier une personne void saveOne(Personne personne). 27. import istia. 45. 50. 30.personnes.. deleteOne].util. 7.. 16. 40. 10.st. 52.getId() == -1) { // ajout insertPersonne(personne).

• ligne 8 : pour implémenter [SqlMapClientDaoSupport].2 La couche d’accès aux données [iBATIS] La classe Spring [SqlMapClientDaoSupport] utilise un framework tierce [Ibatis SqlMap] disponible à l’url [http://ibatis. l'architecture de la couche d'accès aux données est la suivante : utilisateur Couche d'accès aux données [dao] Couche d'accès aux données [iBATIS] Couche d'accès aux données [JDBC] Données [iBATIS] s'insère entre la couche [dao] de l'application et le pilote JDBC de la base de données.org/] : [iBATIS] est un projet Apache qui facilite la construction de couches [dao] s’appuyant sur des bases de données. Il existe des alternatives à [iBATIS] telle. la classe [DaoImpl] dérive de la classe Spring 17. l’interface [IDao]. par l'exemple 196/264 . Avec [iBATIS]. par exemple. l'alternative [Hibernate] : Les bases du développement web MVC en Java.apache.3.

par l'exemple .d. il implémente la couche [iBATIS] de notre architecture : utilisateur Couche d'accès aux données [SqlMapDaoClientSupport : DaoImplCommon] Couche d'accès aux données [SqlMapClient] Couche d'accès aux données [JDBC] Données Une séquence typique d’actions avec cet objet est la suivante : 1. des parties de code qu’on retrouve dans toutes les couche [dao] utilisant l'outil [iBATIS]. l’une d’elles permet de configurer le client [iBATIS] avec lequel on va exploiter la base de données : L’objet [SqlMapClient sqlMapClient] est l’objet [IBATIS] utilisé pour accéder à une base de données. La classe [SqlMapClientDaoSupport] est définie comme suit : Parmi les méthodes de cette classe.utilisateur Couche d'accès aux données [dao] Couche d'accès aux données [Hibernate] Couche d'accès aux données [JDBC] Données L’utilisation du framework [iBATIS] nécessite deux archives [ibatis-common.a. c. demander une connexion à un pool de connexions 197/264 Les bases du développement web MVC en Java. A lui tout seul. Pour écrire la partie non générique du code. il suffit de dériver la classe [SqlMapClientDaoSupport]. c’est à dire ce qui est spécifique à la couche [dao] que l’on écrit. C’est ce que nous faisons ici. ibatis-sqlmap] qui ont été toutes deux placées dans le dossier [lib] du projet : La classe [SqlMapClientDaoSupport] encapsule la partie générique de l’utilisation du framework [iBATIS].

5. déléguant l’opération 3 à sa classe dérivée. on fait référence à un objet nommé " sqlMapClient " qui reste à construire. Seule l’opération 3 est spécifique à une couche [dao]. Pour pouvoir fonctionner. <bean id="dao" class="istia. un type [iBATIS] : [SqlMapClient] est une interface. <ref local="sqlMapClient"/> 5. Ce n’est apparemment pas le cas de la classe [SqlMapClientFactoryBean]. C’est donc elle qui est en réalité initialisée ici.dao. Celle-ci a la méthode [getObject()] suivante : Lorsqu’on demande à Spring une instance d’un objet implémentant l’interface [FactoryBean]. il : • • crée une instance [I] de la classe .getObject() va rendre ici un objet implémentant l’interface [SqlMapClient]. Spring offre la classe [SqlMapClientFactoryBean] pour obtenir un objet implémentant cette interface : Rappelons que nous cherchons à instancier un objet implémentant l’interface [SqlMapClient].getObject() . Elle l’est par la méthode [setSqlMapClient] de la classe [DaoImpl]. par l'exemple 198/264 . 4. Ils sont identifiés par un code dans un fichier de configuration et l’objet [SqlMapClient sqlMapClient] utilise ce code pour faire exécuter un ordre SQL particulier. le résultat de la méthode [I].</bean> Ici la propriété [sqlMapClient] (ligne 3) de la classe [DaoImplCommon] (ligne 2) est initialisée.DaoImplCommon"> 3. Maintenant ligne 4. Celui-ci. la classe [SqlMapClientDaoSupport] a besoin d’une référence sur l’objet iBATIS [SqlMapClient sqlMapClient] qui va assurer le dialogue avec la base de données.personnes.st.2.mvc.la méthode [SqlMapClientFactoryBean]. La classe Spring [SqlMapClientDaoSupport] assurera elle-même les opérations 1. 3. les autres opérations étant génériques. C’est sa classe parent [SqlMapClientDaoSupport] qui l’a. 4 et 5. En effet. 2. </property> 6. on l’a dit.ici il crée une instance de type [SqlMapClientFactoryBean]. ici la classe [DaoImplCommon]. Cet objet a besoin de deux choses pour fonctionner : • • un objet [DataSource] connecté à la base de données auprès duquel il va demander des connexions un (ou des) fichier de configuration où sont externalisés les ordres SQL à exécuter. ouvrir une transaction exécuter une série d’ordres SQL mémorisée dans un fichier de configuration fermer la transaction rendre la connexion au pool Si notre implémentation [DaoImplCommon] travaillait directement avec [iBATIS]. rend à la méthode appelante.la classes d'accè à la couche [dao] --> 2. <property name="sqlMapClient"> 4. Les bases du développement web MVC en Java. ceux-ci ne sont pas dans le code Java. Un embryon de configuration de notre couche [dao] qui refléterait l'architecture ci-dessus serait le suivant : 1. Cette classe n’a pas cette méthode. elle devrait faire cette séquence de façon répétée. est de type [SqlMapClient]. Celle-ci implémente l’interface [FactoryBean] (cf ci-dessus). <!-.

Pour résoudre ces deux problèmes (limiter à la fois le nombre de connexions ouvertes à un moment donné. nous obtenons un objet implémentant l’interface iBATIS [SqlMapClient]. </property> 10. </bean> • • • lignes 2-3 : le bean " sqlMapClient " est de type [SqlMapClientFactoryBean]. Le rôle d’une telle classe est de fournir à une application. et limiter le coût d’ouverture / fermeture de celles-ci. Ces N connexions vont rester tout le temps ouvertes et forment un pool de connexions disponibles pour les threads de l’application. nous savons que lorsque nous demandons à Spring une instance de ce bean. <value>classpath:sql-map-config-firebird. N a en général une valeur par défaut et peut le plus souvent être défini dans un fichier de configuration. à : • • • • • ouvrir une connexion commencer une transaction émettre des ordres SQL fermer la transaction fermer la connexion Ouvrir et fermer des connexions de façon répétée est coûteux en temps.<!-. Un SGBD ne peut maintenir ouvertes simultanément un grand nombre de connexions. Si on regarde le paramètre attendu par la méthode [setDataSource] de [SqlMapClientFactoryBean]. par l'exemple 199/264 . </bean> 11. on voit qu’il est de type [DataSource] : On a de nouveau affaire à une interface dont il nous faut trouver une classe d’implémentation. </property> 7. pour chaque échange avec la base. <!-. <bean id="dao" class="istia. la classe [SqlMapClientFactoryBean] a besoin de deux informations nécessaires à cet objet : • • un objet [DataSource] connecté à la base de données auprès duquel il va demander des connexions un (ou des) fichier de configuration où sont externalisés les ordres SQL à exécuter La classe [SqlMapClientFactoryBean] possède les méthodes set pour initialiser ces deux propriétés : Nous progressons.SqlMapCllient --> 2.DaoImplCommon"> 13. La méthode [SqlMapClientFactoryBean]. Les bases du développement web MVC en Java. des connexions à une base de données particulière. N connexions avec la base de données visée.. Pour diminuer le nombre de connexions ouvertes à un moment donné. class="org.xml</value> 9.mvc.ibatis.xml " et qu’il doit être cherché dans le ClassPath de l’application. de façon efficace. lignes 4-6 : nous initialisons la propriété [dataSource] de [SqlMapClientFactoryBean] avec sa méthode [setDataSource].SqlMapClientFactoryBean"> 4.. les classes implémentant l’interface [DataSource] procèdent souvent de la façon suivante : • elles ouvrent dès leur instanciation.dao. <property name="dataSource"> 5.springframework.personnes. on est amenés. lignes 7-9 : nous indiquons que le fichier de configuration nécessaire à l’objet iBATIS [SqlMapClient] s’appelle " sqlmap-config-firebird. <ref local="sqlMapClient"/> 15. Ligne 5. Notre fichier de configuration se précise et devient : 1.st. C’est ce dernier objet qui sera donc obtenu en ligne 14. <property name="configLocation"> 8. nous faisons référence à un bean appelé " dataSource " qui reste à construire.setConfigLocation est ici utilisée. <property name="sqlMapClient"> 14.orm.la classes d'accè à la couche [dao] --> 12.Pour pouvoir rendre un objet implémentant l’interface [SqlMapClient]. De ce qui vient d’être expliqué. <ref local="dataSource"/> 6. <bean id="sqlMapClient" 3. </property> 16.

2. 2. Il existe diverses implémentations de l’interface [DataSource] disponibles librement. 3.FBDriver</value> Les bases du développement web MVC en Java. cette dernière n’est en réalité pas fermée mais simplement remise dans le pool des connexions disponibles.dtd"> <beans> <!-. 3.gdb] de notre application.initialisé avec [setUrl] l’identifiant de l’utilisateur propriétaire de la connexion – initialisé avec [setUsername] (et non pas setUserName comme on aurait pu s'y attendre) son mot de passe .springframework. 4. s’il en reste de disponibles. 5.apache.0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www.org/commons/dbcp/] : L’utilisation de l’outil [commons DBCP] nécessite deux archives [commons-dbcp.firebirdsql.org/dtd/spring-beans. 4.commons. 6. Lorsque l’application ferme la connexion.• lorsqu’un thread de l’application demande une ouverture de connexion. le nom du pilote JDBC à utiliser – initialisé avec [setDriverClassName] le nom de l’url de la base de données à exploiter . Pour cela.initialisé avec [setPassword] Le fichier de configuration de notre couche [dao] pourra être le suivant : 1. Nous allons utiliser ici l’implémentation [commons DBCP] disponible à l’url [http://jakarta. 7.la source de donnéees DBCP --> <bean id="dataSource" class="org. par l'exemple 200/264 .jdbc. <?xml version="1.apache.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>org. commons-pool] qui ont été toutes deux placées dans le dossier [lib] du projet : La classe [BasicDataSource] de [commons DBCP] fournit l’implémentation [DataSource] dont nous avons besoin : Cette classe va nous fournir un pool de connexions pour accéder à la base Firebird [dbpersonnes. 8. l’objet [DataSource] lui donne l’une des N connexions ouvertes au démarrage. il faut lui donner les informations dont elle a besoin pour créer les connexions du pool : 1.dbcp.

la décomposition Les bases du développement web MVC en Java.springframework.dao.DaoImplCommon"> <property name="sqlMapClient"> <ref local="sqlMapClient"/> </property> </bean> </beans> • • • • lignes 7-9 : le nom du pilote JDBC du SGBD Firebird lignes 11-13 : l’url de la base Firebird [dbpersonnes. 13. cela signifie qu’à l’exécution ils seront présents dans le dossier [bin] du projet (non représenté ci-dessus). 15. Les trois fichiers précédents sont dans le dossier [src].SqlMapCllient --> <bean id="sqlMapClient" class="org. [personnes-firebird. 30. lignes 14-16 : le propriétaire de la connexion – ici. Nous allons l’étudier. 11.9. [sysdba] qui est l’administrateur par défaut des distributions Firebird lignes 17-19 : son mot de passe [masterkey] – également la valeur par défaut On a beaucoup progressé mais il reste toujours des points de configuration à élucider : la ligne 28 référence le fichier [sql-mapconfig-firebird.gdb</value> </property> <property name="username"> <value>sysdba</value> </property> <property name="password"> <value>masterkey</value> </property> </bean> <!-. mais ce n’est pas obligatoire. 19. 4. Sous Eclipse.xml] est référencé par [spring-config-test-dao-firebird. Mais on trouve fréquemment des ordres SQL impliquant plusieurs tables. 26. 23.la classes d'accè à la couche [dao] --> <bean id="dao" class="istia. 2.xml] est le fichier de configuration de la couche [dao] que nous venons d’étudier [sql-map-config-firebird.com//DTD SQL Map Config 2.xml] est le suivant : 1. 20. 3. Au final. 33.xml] qui doit configurer le client [SqlMapClient] d’iBATIS. les trois fichiers précédents seront donc bien présents dans le ClassPath de l’application. 14. Le fichier [sql-map-config-firebird.0" encoding="UTF-8" ?> <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS. 29.ibatis. 7. </property> <!-. 34. 12. Dans ce cas. Il y a souvent. 28. 36. 16. montrons l’emplacement de ces fichiers de configuration dans notre projet Eclipse : • • • [spring-config-test-dao-firebird. 35. 5.dtd"> <sqlMapConfig> <sqlMap resource="personnes-firebird.xml]. 22.gdb].com/dtd/sql-map-config-2. 32. 27. 8. 18.xml]. On fera particulièrement attention à l’écriture de celle-ci.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-firebird. 25. 21. Il ne doit y avoir aucun espace entre les balises <value> et l’url.xml] est référencé par [sql-map-config-firebird. 31. <?xml version="1.mvc.attention : ne pas laisser d'espaces entre les deux balises <value> de l’url --> <property name="url"> <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes.st.personnes. Avant d’étudier son contenu. C’est nécessaire. Cela permet de rassembler les ordres SQL sur une table donnée dans un même fichier. 17. un fichier par table. 24. par l'exemple 201/264 • • . Ce dossier fait partie du ClassPath de l’application. 6. 10. 37.0//EN" "http://www.orm.xml</value> </property> </bean> <!-. Nous allons l’étudier.ibatis.xml"/> </sqlMapConfig> ce fichier doit avoir <sqlMapConfig> comme balise racine (lignes 6 et 8) ligne 7 : la balise <sqlMap> sert à désigner les fichiers qui contiennent les ordres SQL à exécuter.

38.gdb]. 10. 29. 17.objet [Personne] --> <resultMap id="Personne. VERSION. 35. 34. Il faut simplement se rappeler que l’ensemble des fichiers désignés par les balises <sqlMap> seront fusionnés. 41. 43.Personne"/> <!-. PRENOM=#prenom#. 39. #version#. 33. 11. 25. #nom#.personnes. 32. 18.map" >select ID. lignes 12-21 : fixe les correspondances entre colonnes de la table [PERSONNES] et champs de l’objet [Personne].springmvc.3 La classe [DaoImplCommon] Revenons sur l’architecture d’accès aux données : utilisateur Couche d'accès aux données [SqlMapDaoClientSupport : DaoImplCommon] Couche d'accès aux données [SqlMapClient] Couche d'accès aux données [JDBC] Données Les bases du développement web MVC en Java.classe"> update PERSONNES set VERSION=#version#+1. #prenom#.xml] décrit les ordres SQL qui vont être émis sur la table [PERSONNES] de la base de données Firebird [dbpersonnes.classe] à la classe [istia.obtenir une personne en particulier --> <select id="Personne.entites.st.xml] vont être expliqués via l’étude de la classe [DaoImplCommon] qui implémente la couche [dao].getOne" resultMap="Personne. 7.mvc.updateOne" parameterClass="Personne. DATENAISSANCE=#dateNaissance#. 16. 42.ajouter une personne --> <insert id="Personne.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//iBATIS. 9. 14. 5.dtd"> <sqlMap> <!-.classe"> <result property="id" column="ID" /> <result property="version" column="VERSION" /> <result property="nom" column="NOM"/> <result property="prenom" column="PRENOM"/> <result property="dateNaissance" column="DATENAISSANCE"/> <result property="marie" column="MARIE"/> <result property="nbEnfants" column="NBENFANTS"/> </resultMap> <!-. 23. NBENFANTS) VALUES(#id#. NOM. 36. 12.entites.1) as "value" FROM RDB$$DATABASE </selectKey> insert into PERSONNES(ID.Personne]. #nbEnfants#) </insert> <!-.st. Son contenu est le suivant : 1. 45. #dateNaissance#.getAll" resultMap="Personne. PRENOM.insertOne" parameterClass="Personne. par l'exemple 202/264 . NBENFANTS=#nbEnfants# WHERE ID=#id# and VERSION=#version#</update> <!-. 6. <?xml version="1. 31. 2.ibatis. DATENAISSANCE.0//EN" "http://www.précédente ne tient pas.alias classe [Personne] --> <typeAlias alias="Personne. NBENFANTS FROM PERSONNES WHERE ID=#value#</select> <!-.personnes. MARIE. 24. Le fichier [personnes-firebird. MARIE.supprimer une personne --> <delete id="Personne. 3.com//DTD SQL Map 2. NBENFANTS FROM PERSONNES</select> <!-. NOM. #marie#. lignes 23-24 : l’ordre SQL [select] pour obtenir toutes les personnes de la table [PERSONNES] lignes 26-27 : l’ordre SQL [select] pour obtenir une personne particulière de la table [PERSONNES] lignes 29-36 : l’ordre SQL [insert] qui insère une personne dans la table [PERSONNES] lignes 38-41 : l’ordre SQL [update] qui met à jour une personne de la table [PERSONNES] lignes 42-44 : l’ordre SQL [delete] qui supprime une personne de la table [PERSONNES] Le rôle et la signification du contenu du fichier [personnes-firebird. DATENAISSANCE. VERSION.map" class="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE ID=#value# </delete> </sqlMap> • • • • • • • • le fichier doit avoir <sqlMap> comme balise racine (lignes 7 et 45) lignes 9-10 : pour faciliter l’écriture du fichier on donne l’alias (synonyme) [Personne. PRENOM. NOM=#nom#. MARIE=#marie#. 20.classe" type="istia.liste de toutes les personnes --> <select id="Personne. 26. 27. MARIE. 15.map" > select ID.mettre à jour une personne --> <update id="Personne. 37. PRENOM. 19. 40. Ces fichiers sont cherchés dans le ClassPath de l’application. 30. 4. 44. 8.classe"> <selectKey keyProperty="id"> SELECT GEN_ID(GEN_PERSONNES_ID.3.com/dtd/sql-map-2. 17. 13. DATENAISSANCE.mapping table [PERSONNES] . 28. 22. NOM. 21. VERSION.

Cette méthode a la signature suivante : Le type [SqlMapClientTemplate] encapsule l’objet [SqlMapClient] de la couche [iBATIS]. C’est cette classe qui a la méthode [getSqlMapClientTemplate()] utilisée ligne 3 ci-dessus.support. 27. 46. 20. 55. 10..orm. public Collection getAll() { 3. } // ajouter ou modifier une personne public void saveOne(Personne personne) { // le paramètre personne est-il valide ? check(personne). 22. 14. par l'exemple 203/264 . 40. return getSqlMapClientTemplate()... 11. Son code est le suivant : 1. 31. 19. 35. 41. import istia.mvc.. 43.dao. 3.Collection. 45. Le type [iBATIS] SqlMapClient pourrait être directement utilisé puisque la classe [SqlMapClientDaoSupport] y a accès : Les bases du développement web MVC en Java. 4. import org.. 47. 23. 18. 50. 2. 6. 34. 15.entites. 16. } . 52. 25.SqlMapClientDaoSupport. 48. 36. 42. 30.. getAll Cette méthode permet d’obtenir toutes les personnes de la liste. } // vérification validité d'une personne private void check(Personne p) { .util. null). } // suppression d'une personne public void deleteOne(int id) { . 12. 8. 5. 39.st.getAll".Personne. C’est par lui qu’on aura accès à la base de données.. 32.. 7.La classe [DaoImplCommon] est la suivante : 1. 26.} Rappelons-nous tout d’abord que la classe [DaoImplCommon] dérive de la classe Spring [SqlMapClientDaoSupport].. } } // ajouter une personne protected void insertPersonne(Personne personne) { . 29. import java. 17. 54..mvc.. 33. } else { updatePersonne(personne). 13. 21.. } Nous allons étudier les méthodes les unes après les autres. 9. 51. 28. // liste des personnes 2. // ajout ou modification ? if (personne. 38. 37.queryForList("Personne. } // modifier une personne protected void updatePersonne(Personne personne) { .personnes. 53.springframework. 4.personnes. 44.st. 24.getId() == -1) { // ajout insertPersonne(personne).. } // obtenir une personne en particulier public Personne getOne(int id) { . public class DaoImplCommon extends SqlMapClientDaoSupport implements IDao { // liste des personnes public Collection getAll() { . package istia. 49.ibatis..

a.xml] : [queryForList] Cette méthode permet d’émettre un ordre [SELECT] et d’en récupérer le résultat sous forme d’une liste d’objets : • • • [statementName] : l’identifiant (id) de l’ordre [select] dans le fichier de configuration [parameterObject] : l’objet " paramètre " pour un [select] paramétré. elles non plus. A l’exécution de l’ordre [select]. Or souvenons-nous que la couche [dao] implémente une interface [IDao] dont les méthodes ne comportent pas d’exceptions dans leurs signatures.L’inconvénient de la classe [iBATIS] SqlMapClient est qu’elle lance des exceptions de type [SQLException]. Ce comportement nous convient. un type d'exception contrôlée. En effet [SqlMapClientTemplate] a été construit pour intercepter les exceptions [SQLException] lancées par la couche [SqlMapClient] et les encapsuler dans un type [DataAccessException] non contrôlé. par l'exemple 204/264 . [queryForObject] Les bases du développement web MVC en Java. si le [SELECT] ne ramène aucune ligne. Il nous faut donc intercepter chaque exception [SQLException] lancée par la couche [iBATIS] et l’encapsuler dans une exception non contrôlée. Le type [DaoException] de notre projet ferait l’affaire pour cette encapsulation. • un dictionnaire : les paramètres de l’ordre [select] sont alors les clés du dictionnaire. Plutôt que de gérer nous-mêmes ces exceptions. On se souviendra simplement que la couche [dao] est désormais susceptible de lancer deux types d’exceptions non contrôlées : • notre type propriétaire [DaoException] • le type Spring [DataAccessException] Le type [SqlMapClientTemplate] est défini comme suit : Il implémente l’interface [SqlMapClientOperations] suivante : Cette interface définit des méthodes capables d’exploiter le contenu du fichier [personnes-firebird. nous allons les confier au type Spring [SqlMapClientTemplate] qui encapsule l’objet [SqlMapClient] de la couche [iBATIS]. ils sont remplacés par les valeurs de ces champs. Les méthodes des classes d’implémentation de l’interface [IDao] ne peuvent donc. qui doit être gérée par un try / catch ou déclarée dans la signature des méthodes qui la lance. A l’exécution de l’ordre [select].d. c. le résultat [List] est un objet vide d'éléments mais pas null (à vérifier). L’objet " paramètre " peut prendre deux formes : • un objet respectant la norme Javabean : les paramètres de l’ordre [select] sont alors les noms des champs du Javabean. avoir d’exceptions dans leurs signatures. celles-ci sont remplacées par leurs valeurs associées dans le dictionnaire.

com//DTD SQL Map 2. 11.0//EN" "http://www. <?xml version="1. Celle-ci va être exécutée. 7.getAll". PRENOM. 22.liste de toutes les personnes --> <select id="Personne. On sait alors que le résultat de la requête va être obtenu sous la forme d’un objet [ResultSet]. [insert] Cette méthode permet d’exécuter un ordre SQL [insert] paramétré par le second paramètre. L'objet rendu est la clé primaire de la ligne qui a été insérée.mvc..getAll ". 4. la ligne 3 de la méthode [getAll] demande l’exécution de la requête [select] appelée " Personne. 10.Cette méthode est identique dans son esprit à la précédente mais elle ne ramène qu’un unique objet. par l'exemple .classe"> <result property="id" column="ID" /> <result property="version" column="VERSION" /> <result property="nom" column="NOM"/> <result property="prenom" column="PRENOM"/> <result property="dateNaissance" column="DATENAISSANCE"/> <result property="marie" column="MARIE"/> <result property="nbEnfants" column="NBENFANTS"/> </resultMap> <!-. 16.. 14. Si le [SELECT] ne ramène aucune ligne.map" > select ID.getAll" resultMap="Personne.alias classe [Personne] --> <typeAlias alias="Personne.mapping table [PERSONNES] . 25. 23. </sqlMap> • • ligne 23 : l’ordre SQL " Personne. le résultat est le pointeur null. 5. null). VERSION.getAll " est exécuté. return getSqlMapClientTemplate(). [delete] Cette méthode permet d’exécuter un ordre SQL [delete] paramétré par le second paramètre. 19.ibatis. NOM. 2. l’attribut [resultMap] de la balise <select> indique à [iBATIS] quel " resultMap " il doit utiliser pour transformer chaque ligne du [ResultSet] obtenu en objet. 3.map] 205/264 Les bases du développement web MVC en Java.com/dtd/sql-map-2.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//iBATIS. C’est le " resultMap " [Personne. Le résultat est le nombre de lignes modifiées par l'ordre SQL [update].getAll " est non paramétré (absence de paramètres dans le texte de la requête). Il n’est pas paramétré et donc l’objet " paramètre " est null. Ligne 23.} • ligne 4 : l’ordre [select] nommé " Personne. 17.queryForList("Personne.xml]. // liste des personnes 2. Dans [personnes-firebird.objet [Personne] --> <resultMap id="Personne.entites. 15.personnes. [update] Cette méthode permet d’exécuter un ordre SQL [update] paramétré par le second paramètre. 24. MARIE. public Collection getAll() { 3.classe" type="istia. 12.Personne"/> <!-. Il n'y a pas d'obligation à utiliser ce résultat. 4.map" class="Personne. 9. [iBATIS] s’appuie sur JDBC. 21. 18. 13. 6. 8.dtd"> <sqlMap> <!-. 26. 20. Revenons à la méthode [getAll] de la classe [DaoImplCommon] : 1. NBENFANTS FROM PERSONNES</select> . l’ordre [select] nommé " Personne. Le résultat est le nombre de lignes supprimées par l'ordre SQL [delete].getAll " est le suivant : 1.st. DATENAISSANCE.

Son code est le suivant : 1. 11. l’essentiel sur l’utilisation d’[iBATIS] ayant été dit dans l’étude de la méthode [getAll]. Nous la laissons remonter. } • ligne 4 : demande l’exécution de l’ordre [select] nommé " Personne. VERSION. On obtiendra donc un type [Personne]. 6. return personne. Double. throw new DaoException( 10. } 10. PERSONNES WHERE ID=#value#</select> L’ordre SQL est paramétré par le paramètre #value# (ligne 4).supprimer une personne --> 2. Celui-ci est le suivant dans le fichier [personnes-firebird. 5. public Personne getOne(int id) { 3.obtenir une personne en particulier --> 2.. 9. public void deleteOne(int id) { 3.getOne" resultMap="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 3. // on rend la personne 13.getOne ".xml] : 1. MARIE. <delete id="Personne. Le résultat de la requête [select] sera à transformer en objet via l’attribut [resultMap="Personne.deleteOne". select ID. } • lignes 4-5 : demande l’exécution de l’ordre [delete] nommé " Personne. alors on rend l’objet [Personne] demandé. NBENFANTS FROM 4. . Celui-ci est le suivant dans le fichier [personnes-firebird.delete("Personne. new Integer(id)). Dans les attributs de la balise <select>. PRENOM. } 12. NOM. Cela signifie qu’on n’a pas trouvé la personne cherchée. Ce changement de type est obligatoire puisque le second paramètre de [queryForList] doit être de type [Object]. on lance une [DaoException] de code 2 (lignes 9-10). ligne 13 : s’il n’y a pas eu d’exception. Dans ce cas. 2). lorsque ce paramètre est de type simple : Integer. ID=#value# </delete> Les bases du développement web MVC en Java.• • défini lignes 12-21 qui indique comment passer d’une ligne de la table [PERSONNES] à un objet de type [Personne]. l’attribut [parameterClass] indique que le paramètre est de type entier (ligne 2). // a-t-on récupéré qq chose ? 7. Nous expliquons les autres méthodes de la classe [AbstractDaoImpl] plus rapidement. on récupère alors le pointeur null en ligne 4. deleteOne Cette méthode permet de supprimer une personne identifiée par son [id].map"] (ligne 2). // obtenir une personne en particulier 2.xml] : 1. • • lignes 7-11 : si la requête [select] n’a ramené aucune ligne. 14. // on supprime la personne 4. Ligne 5 de [getOne]. DATENAISSANCE. // suppression d'une personne 2. la ligne 3 de la méthode [getAll] renvoie alors une collection d’objets [Personne] la méthode [queryForList] peut lancer une exception Spring [DataAccessException]. 6. <select id="Personne. on voit que ce paramètre est l’identifiant de la personne cherchée sous la forme d’un objet Integer. int n = getSqlMapClientTemplate(). L’attribut #value# désigne la valeur du paramètre passé à l’ordre SQL. // on lance une exception 9. [iBATIS] va utiliser ces correspondances pour fournir une liste d’objets [Personne] à partir des lignes de l’objet [ResultSet]. Personne personne = (Personne) getSqlMapClientTemplate() 5. // on la récupère dans la BD 4. .. throw new DaoException("Personne d'id [" + id + "] inconnue". if (n == 0) { 8. new Integer(id)). // a-t-on réussi 7. "La personne d'id [" + id + "] n'existe pas". <!-. if (personne == null) { 8. 2).<!-. String.map" parameterClass="int"> 3. Son code est le suivant : 1. par l'exemple 206/264 .deleteOne ".getOne".queryForObject("Personne. getOne Cette méthode permet d’obtenir une personne identifiée par son [id].

L’ordre SQL est paramétré par le paramètre #value# (ligne 3) de type [parameterClass="int"] (ligne 2). wait(10). // le paramètre personne est-il valide ? 4. par l'exemple 207/264 . NBENFANTS) VALUES(#id#. // ajout 8. La personne est donc valide.pour les tests mettre true au lieu de false 6. protected void insertPersonne(Personne personne) { 3. 2. // ajout ou modification ? 6. insertPersonne(personne). cela signifie que la personne n’existe pas. personne). } • • ligne 4 : on met à 1 le n° de version de la personne que l’on est en train de créer ligne 9 : on fait l’insertion via la requête nommée " Personne.insert("Personne. ligne 1). Les champs de l’objet [Personne] passés en paramètre (ligne 9 de insertPersonne) sont utilisés pour remplir les colonnes de la ligne qui va être insérée dans la table [PERSONNES] (lignes 5-8). On utilise pour cela les lignes 2-4 de la balise <selectKey> ci-dessus. 3. Cette méthode existait déjà dans la version précédente et avait été alors commentée. Son code est le suivant : 1. on a affaire à un ajout (id= -1) ou à une mise à jour (id<> -1). On peut aussi écrire as value mais value est un mot clé de Firebird qui a du être protégé par des guillemets. // on insère la nouvelle personne dans la table de la BD 9.. Son code est le suivant : 1. MARIE. 5. } 13. #nom#. NOM.insertOne " qui est la suivante : 1. check(personne). Deux points sont à noter : • as " value " est obligatoire. lignes 6-11 : selon l’id de la personne. PRENOM. ligne 6 : si on arrive là.getId() == -1) { 7. 10.// ajouter une personne 2.1) as "value" FROM RDB$$DATABASE </selectKey> insert into PERSONNES(ID. // 1ère version 4.. VERSION. On laisse remonter celle-ci. Lors d'une insertion. } else { 10. <insert id="Personne. Il faut remplacer cette valeur par une clé primaire valide. Celle indiquée ici est celle que nous avons présentée au paragraphe 17. 8.classe". getSqlMapClientTemplate(). 9. updatePersonne(personne). #marie#. 7. on fait appel à deux méthodes internes à la classe : • insertPersonne : pour l’ajout • updatePersonne : pour la mise à jour insertPersonne Cette méthode permet d’ajouter une nouvelle personne. 5.page 192. l’objet [Personne] à insérer a son id égal à -1.insertOne". On a un problème à résoudre. if (personne. Elle lance une [DaoException] si la personne est invalide. 8. #prenom#. On lance une [DaoException] de code 2 (ligne 8). if (true) 7. DATENAISSANCE. Elles indiquent : • la requête SQL à exécuter pour obtenir une valeur de clé primaire. 4. 11. saveOne Cette méthode permet d’ajouter une nouvelle personne ou de modifier une personne existante.classe"> <selectKey keyProperty="id"> SELECT GEN_ID(GEN_PERSONNES_ID. personne. public void saveOne(Personne personne) { 3. #version#. Dans les deux cas. // ajouter ou modifier une personne 2.1 . } 12. // on attend 10 ms . #nbEnfants#) </insert> C’est une requête paramétrée et le paramètre est de type [Personne] (parameterClass="Personne. c’est qu’il n’y a pas eu d’exception. lignes 7-8 : si la requête [delete] n’a détruit aucune ligne. Les bases du développement web MVC en Java. 6.insertOne" parameterClass="Personne.. Ce sera l’identifiant de la personne cherchée (ligne 5 de deleteOne) • • ligne 4 : le résultat de la méthode [SqlMapClientTemplate]. #dateNaissance#.delete est le nombre de lignes détruites. • • • ligne 4 : on vérifie la validité de la personne avec la méthode [check].setVersion(1). 5.

1 Tests de la couche [dao] Tests de l'implémentation [DaoImplCommon] Maintenant que nous avons écrit la couche [dao]. C'est l'attribut [keyProperty] de la ligne 2 qui indique ce champ.personnes. personne). 2.update("Personne.getId() 11. la personne à mettre à jour n’existe pas 2. le champ de l'objet [Personne] qu'il faut initialiser avec la valeur récupérée par l'ordre [SELECT]. MARIE=#marie#.mvc. soit elle a changé de version entre-temps.st. ici le champ [id]. Si cette personne est trouvée. 4. par l'exemple 208/264 .mvc.tests. import istia. 2. 3. • • lignes 6-7 : pour le besoin des tests. protected void updatePersonne(Personne personne) { 3. // on attend 10 ms . soit la personne à mettre à jour n’existe pas.4. + "] n'existe pas ou bien a été modifiée".IDao.personnes. nous nous proposons de la tester avec des tests JUnit : Avant de faire des tests intensifs. Mais le caractère $ est interprété par [iBATIS].• la table Firebird s'appelle en réalité [RDB$DATABASE]. DATENAISSANCE=#dateNaissance#. elle est mise à jour avec la personne paramètre et sa version est augmentée de 1 (ligne 3 ci-dessus). int n = getSqlMapClientTemplate() 8.updateOne" parameterClass="Personne.mettre à jour une personne --> <update id="Personne. } • une mise à jour peut échouer pour au moins deux raisons : 1. 17. C’est la classe [MainTestDaoFirebird] : 1. Son code est le suivant : 1. 2). Les bases du développement web MVC en Java. C'est la suivante : 1. lignes 10-11 : si ce nombre est nul. 12.classe").// modifier une personne 2.updateOne". 6. NOM=#nom#. on a la contrainte [WHERE ID=#id# and VERSION=#version#]. VERSION=#version#</update> • • • ligne 2 : la requête est paramétrée et admet pour paramètre un type [Personne] (parameterClass="Personne. C’est pourquoi. package istia. updatePersonne Cette méthode permet de modifier une personne existant déjà dans la table [PERSONNES]. wait(10).updateOne " est exécutée.pour les tests mettre true au lieu de false 4. ceci pour voir s’il y a des conflits entre threads qui voudraient faire en même temps des ajouts.4 17. . la personne à mettre à jour existe mais le thread qui veut la modifier n’a pas la bonne version lignes 7-8 : la requête SQL [update] nommée " Personne. // modification 7. indiquant que. nous pouvons commencer par un simple programme de type [main] qui va afficher le contenu de la table [PERSONNES]. on lance une [DaoException] de code 2. if (true) 5. throw new DaoException("La personne d'Id [" + personne. NBENFANTS=#nbEnfants# WHERE ID=#id# and 5. 9. PRENOM=#prenom#. 3. nous serons amenés à attendre 10 ms avant de faire l'insertion. Il a été protégé en le dédoublant. if (n == 0) 10.classe"> update PERSONNES set VERSION=#version#+1. <!-. Celui-ci est la personne à modifier (ligne 8 – updatePersonne).st. • • ligne 9 : on récupère le nombre de lignes mises à jour. on ne veut modifier que la personne de la table [PERSONNES] ayant le même n° [id] et la même version [version] que le paramètre.dao.

</property> 17. <bean id="dataSource" class="org. utilisé lignes 13-14. par l'exemple 209/264 . <property name="url"> 12. 14.core. 21. 10. <?xml version="1. import org. <value>org.personnes.out. 5. </property> 20. <property name="username"> 15. 11.orm.commons.SqlMapClientFactoryBean"> 24.BasicDataSource" 6.xml"))). <bean id="sqlMapClient" 23.xml] de la couche [dao].FBDriver</value> 9. </bean> 37. <value>sysdba</value> 16.apache. <property name="configLocation"> 28.la classes d'accè à la couche [dao] --> 32.4. 13.iterator(). <!-.println(iter. 17. 15.xml.beans.dbcp.st. est le suivant : 1. </beans> Ce fichier est celui étudié au paragraphe 17.dao. </property> 14.springframework.ClassPathResource. import java. </property> 36.util. </bean> 21. <ref local="dataSource"/> 26. <value>masterkey</value> 19. <!-. 19. Pour le test.getAll().gdb</value> 13.springframework. <property name="dataSource"> 25. 20. 22.springframework. <!-. 6. page 200.util. <property name="password"> 18. while (iter. <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes. </property> 30. import org.ibatis. 9.dtd"> 3.DaoImplCommon"> 33.SqlMapCllient --> 22.xml</value> 29.Iterator. <!DOCTYPE beans SYSTEM "http://www.springframework. <property name="sqlMapClient"> 34.getBean("dao"). <bean id="dao" class="istia.io. import java. </bean> 31. <ref local="sqlMapClient"/> 35.la source de donnéees DBCP --> 5. 12.jdbc.hasNext()) { System.3. public class MainTestDaoFirebird { public static void main(String[] args) { IDao dao = (IDao) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-dao-firebird. // affichage console Iterator iter = personnes. </property> 10. class="org. <!-. </property> 27.XmlBeanFactory. 7.firebirdsql. destroy-method="close"> 7.next()). 23.Collection.factory. Le contenu de la table [PERSONNES] est le suivant : L’exécution du programme [MainTestDaoFirebird] donne les résultats écran suivants : Les bases du développement web MVC en Java. } } } Le fichier de configuration [spring-config-test-dao-firebird. 8. 16.attention : ne pas laisser d'espaces entre les deux balises <value> --> 11. 18.0" encoding="ISO_8859-1"?> 2.mvc. <value>classpath:sql-map-config-firebird. <property name="driverClassName"> 8.2. <beans> 4. // liste actuelle Collection personnes = dao.org/dtd/spring-beans. le SGBD Firebird est lancé.

framework. java.st. 56. 49. 41..st. } Les bases du développement web MVC en Java. 40. } public void setDao(IDao dao) { this. InterruptedException { . 48.factory. 67.SimpleDateFormat. 28. 46. java. 22. 13. 58. 2.On a bien obtenu la liste des personnes.ParseException.dao. 60.st. 6. 45. 20. public IDao getDao() { return dao.. import import import import import import import import import import java. 18.springframework. On peut passer au test JUnit. 14.mvc.TestCase.text. 27.ClassPathResource.dao = dao.accès multi-threads public void test4() throws Exception { . 19.. } // gestion des versions de personne public void test3() throws ParseException. istia. 36.personnes. InterruptedException{ . org.Personne.xml. 54. 63. par l'exemple 210/264 . } // test1 public void test1() throws ParseException { .personnes.springframework..st. } // insertions multi-threads public void test6() throws ParseException. 42. 65. 17. 3. 9.personnes. 25.util. Le test JUnit [TestDaoFirebird] est le suivant : 1. org. 26.mvc. package istia. 61. 7.XmlBeanFactory. 51.. 57. 32. 43. 35.entites. junit. 16. istia.Collection. 12. 44. 50.. 30.util.beans.io. 39. 4. 29. java... 33.getBean("dao"). 47. } // modification-suppression d'un élément inexistant public void test2() throws ParseException { .Iterator. 66. 8. 37.. } // liste des personnes private void doListe(Collection personnes) { ...xml"))).mvc. 59. 53. istia.. 24. 5. 15. 34. 64. 11.DaoException. 31. 21.tests. } // tests de validité de saveOne public void test5() throws ParseException { .IDao.text.. 38. 52. 23. } // optimistic locking . public class TestDaoFirebird extends TestCase { // couche [dao] private IDao dao.mvc. } // constructeur public void setUp() { dao = (IDao) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-dao-firebird. 10.core.. 55. 62.personnes.dao.

21.st.getOne(p1.personnes.personnes. // suppression personne p1 dao. i++) { taches[i] = new ThreadDaoMajEnfants("thread n° " + i. import istia. 15. i < taches. // modification 7.getNbEnfants()). id1). // on attend 10 ms . Le test [test6] est lui nouveau. Les bases du développement web MVC en Java.st.entites.personnes. [test4] [test4] a pour objectif de tester la méthode [updatePersonne . int n = getSqlMapClientTemplate() 8.st. import istia.length. try { p1 = dao. // elle doit avoir N enfants assertEquals(N. Chacun va augmenter de 1 le nombre d'enfants de la personne créée lignes 3-5.mvc. 20.start(). 16.personnes. // nom du thread 11. 28. private String name. 4. codeErreur = ex. 5.DaoException. 31. 12. } // on doit avoir une erreur de code 2 assertTrue(erreur).mvc. 30. 18. 1. personne). wait(10). import istia. int codeErreur = 0. // création de N threads de mise à jour du nombre d'enfants final int N = 100. 17. 9. codeErreur). public void test4() throws Exception { // ajout d'une personne Personne p1 = new Personne(-1. } Les threads sont créés lignes 8-13.parse("01/02/2006"). 24.util. i++) { taches[i]. "X". Les threads [ThreadDaoMajEnfants ] de mise à jour sont les suivants : 1. throw new DaoException("La personne d'Id [" + personne. for (int i = 0. 3. de 1 le nombre d’enfants de la même personne.getId()). par l'exemple 211/264 . 2. 11. 34. 29. + "] n'existe pas ou bien a été modifiée". int id1 = p1. 8. 2).getId(). protected void updatePersonne(Personne personne) { 3. true. public class ThreadDaoMajEnfants extends Thread { 10.IDao. } • lignes 4-5 : on attend 10 ms.getOne(id1). [test4] lance N=100 threads chargés d’incrémenter. 12. en même temps. . 7. dao.tests. private IDao dao.st. // référence sur la couche [dao] 14.mvc.length. 14. } // on attend la fin des threads for (int i = 0. p1. On rappelle le code de celle-ci : 1. 33. 35. new SimpleDateFormat( "dd/MM/yyyy"). } catch (DaoException ex) { erreur = true. 7.getCode(). 25. assertEquals(2. 32. ce qui peut augmenter nos chances de voir des conflits d’accès entre threads concurrents. 13. sauf [test4] qui a légèrement évolué. 0).dao. 36. if (true) 5. On force ainsi le thread qui exécute [updatePersonne] à perdre le processeur. 6.// modifier une personne 2. 10. 8. 19. 12. 22.getId() 11. taches[i].• les tests [test1] à [test5] sont les mêmes que dans la version 1.mvc. 6. 9.update("Personne. On veut voir comment les conflits de version et les conflits d’accès sont gérés. 2. 3. Nous ne commentons que ces deux tests.saveOne(p1). 9.pour les tests mettre true au lieu de false 4. import java.getId()). 13. Thread[] taches = new Thread[N]. package istia. 4.DaoImplCommon].updateOne". } // on récupère la personne p1 = dao. 6.deleteOne(p1.join(). dao. i < taches.dao. if (n == 0) 10. 5. 23. 26. "X". 27.Personne.Date. // vérification boolean erreur = false.

private int idPersonne. 34. ligne 34). 47. // incrémente de 1 le nbre d'enfants de cette copie 57. Le thread sera alors ramené à recommencer la procédure de mise à jour depuis son début (boucle while. public ThreadDaoMajEnfants(String name.getTime() + "] : " 83. 33. this.println(name + " [" + new Date(). // suivi 81.15. 65.name = name. this. + " pour la version " + personne.setNbEnfants(nbEnfants + 1). // entre-temps d'autres threads ont pu modifier l'original 54. 38. + message). // coeur du thread 27. 30. 62. 58. break. } 25. 26. default: 71. on réessaie la mise à jour 66. 24.out. // on boucle tant qu'on n'a pas réussi à incrémenter de 1 31. dao. 16. suivi("" + nbEnfants + " -> " + (nbEnfants + 1) 40. protected void insertPersonne(Personne personne) { Les bases du développement web MVC en Java. // on récupère une copie de la personne d'idPersonne 36. System. 80.on laisse remonter 72. } 74. suivi("version corrompue ou personne inexistante"). throw new RuntimeException(ex. // constructeur 20. Personne personne = dao. suivi("a terminé et passé le nombre d'enfants à " + (nbEnfants + 1)). 60. } 79. } 76. // suivi 77. // suivi 39.idPersonne = idPersonne. 55. } 52. } catch (DaoException ex) { 63. codeErreur = ex. // on est passé . par l'exemple 212/264 . // suivi 29.on essaie de valider la copie 53. // si une erreur d'ID ou de version de code erreur 2. Dans ces deux cas en effet.toString()). // on essaie de modifier l'original 59. la méthode [updatePersonne] lance une [DaoException] de code 2. // on récupère le code erreur 64. // suivi 48. 41. fini = true. personne.getNbEnfants(). Thread. boolean fini = false. } catch (Exception ex) { 50. } 85.DaoImplCommon]. 45. throw ex. int idPersonne) { 21. // le nbre d'enfants de la personne idPersonne 32. suivi("fin attente"). suivi("début attente"). // suivi 44. 78. try { 43. try { 56.getOne(idPersonne). while (!fini) { 35.getCode(). nbEnfants = personne. // on s'interrompt pour laisser le processeur 46.saveOne(personne). Ces deux cas sont ici gérés lignes 67-69. 23. switch (codeErreur) { 67. 51. // attente terminée . 73. this.l'original a été modifié 61. 84. // l'id de la personne sur qui on va travailler 17. IDao dao. // exception non gérée .// ajouter une personne 2. // attente de 10 ms pour abandonner le processeur 42. 19. [test6] [test6] a pour objectif de tester la méthode [insertPersonne . } 75. int codeErreur = 0. case 2: 68. 37. 70. 49. } Une mise à jour de personne peut échouer parce que la personne qu’on veut modifier n’existe pas ou qu’elle a été mise à jour auparavant par un autre thread. private void suivi(String message) { 82. 18. suivi("lancé"). int nbEnfants = 0.sleep(10).dao = dao. public void run() { 28. On rappelle le code de celle-ci : 1.getVersion()). 69. 22.

20.i++){ 10.length. // création de N threads d'insertion .IDao. Ces personnes sont toutes des copies de la personne p créée lignes 4-5. lignes 19-23 : [test6] attend la fin de chacun des 100 threads qu'il a lancés. 2. "X".personnes. dao.mvc.length. 8. // constructeur public ThreadDaoInsertPersonne(String name.deleteOne(personnes[i].setVersion(1). 3. Ces 100 threads vont tous obtenir une clé primaire pour la personne qu’ils doivent insérer puis être interrompus pendant 10 ms (ligne 10 – insertPersonne) avant de pouvoir faire leur insertion. Personne p = new Personne(-1. Chacun d'eux est chargé d'insérer l'une des 100 personnes créée précédemment. personnes[i]).insertOne". // on attend 10 ms .dao. 11. 5.mvc. final int N = 100.entites. for(int i=0. 24. // insertions multi-threads 2. // l'id de la personne sur qui on va travailler private Personne personne. 24. Thread[] taches = new Thread[N]. taches[i] = new ThreadDaoInsertPersonne("thread n° " + i. import istia. i++) { 20. 10. 7. 9. } // 1ère version personne. taches[i]. 22.mvc.personnes. "X". 5. } 25. for (int i = 0. // supression personne 23. taches[i].dao = dao. // thread n° i 21. 14.st. 14.pour les tests mettre true au lieu de false if (true) wait(10).tests. 15. dao.personne = personne.Date.Personne. 17. // qu'on duplique N fois dans un tableau 7. par l'exemple 213/264 .getId()). // création d'une personne 4. import istia.util. Le thread d’insertion [ThreadDaoInsertPersonne] est le suivant : 1. 18. 19. 0).st.3. // référence sur la couche [dao] private IDao dao. true. this. } // coeur du thread Les bases du développement web MVC en Java. InterruptedException{ 3. 9.parse("01/02/2006"). lignes 14-17 : les 100 threads d'insertion sont lancés. 6. Personne[] personnes=new Personne[N]. import java.chaque thread insère 1 personne 13. personnes[i]=new Personne(p). personne). i < taches. Le code de [test6] est le suivant : 1. } 18. il supprime la personne que ce thread vient d'insérer. 23. 10.st. • lignes 6-7 : on attend 10 ms pour forcer le thread qui exécute [insertPersonne] à perdre le processeur et augmenter ainsi nos chances de voir apparaître des conflits dus à des threads qui font des insertions en même temps.. public class ThreadDaoInsertPersonne extends Thread { // nom du thread private String name. for (int i = 0. package istia. "dd/MM/yyyy"). 7.i<personnes. 17. 6.insert("Personne. 4. 22. } 12. • • • lignes 7-11 : un tableau de 100 personnes est créé. 13.length. i++) { 15.name = name. 4. 21. 12.personnes. On veut vérifier que les choses se passent bien et que notamment ils obtiennent bien des valeurs de clé primaire différentes. // on attend la fin des threads 19. public void test6() throws ParseException. 9. 16.} On crée 100 threads qui vont insérer en même temps 100 personnes différentes. // on insère la nouvelle personne dans la table de la BD getSqlMapClientTemplate(). i < taches. 8.join(). 1. 16. Personne personne) { this. Lorsqu'il a détecté la fin du thread n° i. IDao dao. new SimpleDateFormat( 5. 11. this.start(). 8. 6.

UncategorizedSQLException: SqlMapClient operation.xml. 28. --. 335544336.springframework. --. 6.Check the statement (update failed). System.setNbEnfants(nbEnfants + 1). Si une exception se produit. SQL state [HY000]. Tests Aux tests.NestedSQLException: --. 2. 5. Exception in thread "Thread-62" org. 3. --. // insertion 29. deadlock update conflicts with concurrent update. ligne 30 : la personne est insérée. 7. on obtient les résultats suivants : . error code [335544336]. C'est une exception non contrôlée qui a été utilisée pour encapsuler une exception lancée par le pilote JDBC de Firebird.FBSQLException] et de code d'erreur 335544336. suivi("lancé").firebirdsql.le test [test4] échoue. décrite ligne 6.out. dao.saveOne(personne). } 33. elle remonte à [test6]. nested exception is com.println(name + " [" + new Date().xml. --.getTime() + "] : " 37. public void run() { 26. ligne 6 – le pilote JDBC de Firebird a lancé une exception de type [org.jdbc. } 39.jdbc. 2. 9. // suivi 27. 32.firebirdsql.updateOne-InlineParameterMap.The error occurred while applying a parameter map. Le test [test4] échoue donc. --. Le nombre d'enfants est passé à 69 au lieu de 100 attendu.25.common.FBSQLException: GDS Exception.springframework. + message).The error occurred in personnes-firebird. try { // incrémente de 1 le nbre d'enfants de cette copie personne. 3. Les bases du développement web MVC en Java. 4. // suivi 35. 34.jdbc. 30. Ce n'est pas une erreur irrécupérable. Que s'est-il passé ? Examinons les logs écran.le test [test6] réussit.ibatis.The error occurred while applying a parameter map. // suivi 31. private void suivi(String message) { 36.jdbc. Le thread qui intercepte cette exception peut retenter la mise à jour.jdbc. Ils montrent l'existence d'exceptions lancées par Firebird : 1.Check the Personne. • • • ligne 1 – on a eu une exception Spring [org. suivi("a terminé").UncategorizedSQLException]. 8. Il faut pour cela modifier le code de [ThreadDaoMajEnfants] : 1. } • • lignes 19-22 : le constructeur du thread mémorise la personne qu'il doit insérer et la couche [dao] qu'il doit utiliser pour faire cette insertion. ligne 7 : indique qu'on a eu un conflit d'accès entre deux threads qui voulaient mettre à jour en même temps la même ligne de la table [PERSONNES]. 38.The error occurred in personnes-firebird.Cause: org. par l'exemple 214/264 . --. uncategorized SQLException for SQL []. .exception.

11. // on essaie de modifier l'original dao.springframework.l'original a été modifié fini = true. } • ligne 8 : on gère une exception de type [DaoException]. 14. 7. .3 – page 204. Ici il nous faudra vérifier que l'objet de type [Throwable] lancé par le pilote JDBC de Firebird et cause de l'exception [SQLException] lancée par la couche [iBATIS] est bien une exception de type [org.GDSException] et de code Les bases du développement web MVC en Java.3. D'après ce qui a été dit. par l'exemple 215/264 . 13. 10. Il est possible de connaître l'exception qui a été encapsulée dans [UncategorizedSQLException] grâce à sa méthode [getSQLException] : Cette exception de type [SQLException] est celle lancée par la couche [iBATIS] qui elle même encapsule l'exception lancée par le pilote JDBC de la base de données. paragraphe 17. 15. Spring connaît les exceptions émises par les pilotes JDBC d'un certain nombre de SGBD tels Oracle.springframework.jdbc. SQL Server..4. 5. Postgres. on réessaie la mise à jour switch (codeErreur) { case 2: suivi("version corrompue ou personne inexistante"). 17. // on est passé . } catch (DaoException ex) { // on récupère le code erreur codeErreur = ex. mais pas Firebird. 18.getCode(). 9. 12. 19. 6. default: // exception non gérée . il nous faudrait gérer l'exception qui est apparue aux tests. 16. le type [org.UncategorizedSQLException] : On voit ci-dessus. break.jdbc. Aussi toute exception lancée par le pilote JDBC de Firebird se trouve-t-elle encapsulée dans le type Spring [org.UncategorizedSQLException]. On ne peut cependant pas se contenter de gérer ce type qui est un type générique de Spring destiné à encapsuler des exceptions qu'il ne connaît pas. La cause exacte de l'exception de type [SQLException] peut être obtenue par la méthode : On obtient l'objet de type [Throwable] qui a été lancé par le pilote JDBC : Le type [Throwable] est la classe parent de [Exception].. 8.saveOne(personne). DB2.gds.on laisse remonter throw ex.firebirdsql. // si une erreur d'ID ou de version de code ereur 2. que la classe [UncategorizedSQLException] dérive de la classe [DataAccessException] que nous avons évoquée. MySQL.

Le thread [ThreadDaoMajEnfants] va être forcé de recommencer la procédure de mise à jour à son début (ligne 10) Notre couche [dao] doit donc être capable de reconnaître une exception de type " conflit de mise à jour ".. protected void updatePersonne(Personne personne) { 3. 26. le thread [ThreadDaoMajEnfants] pourra-t-il être réécrit comme suit : 1. 20. En effet. // on attend 10 ms .pour les tests mettre true au lieu de false 4. 11. quelque soit le SGBD sous-jacent. 36.GDSException].getNbEnfants(). Pour arriver à ce résultat. 29. 2. 28. 12. 2). } catch (DaoException ex) { // on récupère le code erreur codeErreur = ex. 27.. // modifier une personne 2. 31. Cette exception doit être gérée dans la méthode [updatePersonne] de la classe [DaoImplCommon] : 1. Si nous utilisons dans le code de [ThreadDaoMajEnfants] l'exception [org. 32.getCode(). if (n == 0) 10. 24. 17. il nous faut vérifier que l'exception qui a causé l'échec de la mise à jour est de type [org. // modification 7. 6. nous pourrons utiliser la méthode [getErrorCode()] de la classe [org. on // réessaie la mise à jour switch (codeErreur) { case 2: suivi("version corrompue ou personne inexistante"). 46. 23. package istia. } Les lignes 7-11 doivent être entourées par un try / catch. // attente terminée . if (true) 5...getId() 11.gds. 1. 10. 15. // si une erreur d'ID ou de version 2 ou un deadlock 4.tests. while (!fini) { // on récupère une copie de la personne d'idPersonne Personne personne = dao.. 7.gds. 40. Pour récupérer le code erreur. . 41.firebirdsql. par l'exemple 216/264 . 12.d'erreur 335544336. 14. 8. 38. 9. // coeur du thread public void run() { . 37. wait(10). // on essaie de modifier l'original dao. 19. } • lignes 34-36 : l'exception de type [DaoException] de code 4 est interceptée. 18. 39.on essaie de valider la copie // entre-temps d'autres threads ont pu modifier l'original int codeErreur = 0. 22. 3.on laisse remonter throw ex.gds. 35. 45.GDSException]. } . 4. 13. break. nbEnfants = personne. nous souhaitons que nos tests JUnit restent valables quelque soit le SGBD utilisé. 5. 30. // on est passé .. personne). break. 16.firebirdsql.updateOne".getOne(idPersonne). public class ThreadDaoMajEnfants extends Thread { . 42.firebirdsql.saveOne(personne). 25.. alors ce thread ne pourra travailler qu'avec le SGBD Firebird. try { // incrémente de 1 le nbre d'enfants de cette copie personne. Il en sera de même du test [test4] qui utilise ce thread. Ainsi.. int n = getSqlMapClientTemplate() 8. Si on met Les bases du développement web MVC en Java. . case 4: suivi("conflit de mise à jour"). Nous voulons éviter cela.l'original a été modifié fini = true. 21. 9.mvc. + "] n'existe pas ou bien a été modifiée". 44.update("Personne. 33.GDSException] et a comme code d'erreur 335544336. Celle-ci est émise par un pilote JDBC et lui est spécifique. .setNbEnfants(nbEnfants + 1).. on décide que la couche [dao] lancera une [DaoException] de code 4 lorsqu'une exception de type " conflit de mise à jour " est détectée et ce. Pour le SGBD Firebird. 43. 34.personnes. default: // exception non gérée .st.. throw new DaoException("La personne d'Id [" + personne. 6. } } } // suivi suivi("a terminé et passé le nombre d'enfants à " + (nbEnfants + 1)).

FBSQLException) ex . 34. 6. 29. 5.personnes.firebirdsql. 45.firebirdsql. return. 21.getSQLException(). if (n == 0) throw new DaoException("La personne d'Id [" + personne. 17. le code d'erreur du " deadlock ". 28. Elle n’a d’utilité que pour les tests. 24. Nous sommes prêts pour les tests de la nouvelle couche [dao]. 43.jdbc.st. } catch (InterruptedException e) { // on affiche la trace de l'exception e. 1. 8.isAssignableFrom( org.jdbc.st.dtd"> Les bases du développement web MVC en Java. public class DaoImplFirebird extends DaoImplCommon { // modifier une personne protected void updatePersonne(Personne personne) { // on attend 10 ms .getSQLException().0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www. Si on veut garder un caractère généraliste à la classe [DaoImplCommon]. personne).personnes.mvc.firebirdsql. 14. 27.UncategorizedSQLException ex) { if (ex. lignes 20 : nous interceptons l'exception Spring de type [UncategorizedSQLException] lignes 21-22 : nous vérifions que l'exception sous-jacente de type [SQLException] et lancée par la couche [iBATIS] a pour cause une exception de type [org.mvc. il nous faut la dériver et gérer l'exception dans une classe spécifique à Firebird. 31. 2). <?xml version="1.springframework.jdbc. 10. 2. 17. 38.pour les tests mettre true au lieu de false if (true) wait(10). lignes 26-27 : si toutes ces conditions sont réunies. 13. 46.xml] est modifié pour utiliser l'implémentation [DaoImplFirebird] : 1. 18.4. 25.class)) { org. ce qui n'est évidemment pas souhaitable. lignes 36-44 : la méthode [wait] permet d’arrêter le thread courant de N millisecondes. la classe que nous venons d’étudier.update("Personne.sleep(N). lignes 8-33. if (cause.2 La classe [DaoImplFirebird] Son code est le suivant : 1. 20. 32. 36. 42.FBSQLException cause = (org.FBSQLException] ligne 25 : on vérifie de plus que le code erreur de cette exception Firebird est 335544336. 2. 37. 16.getErrorCode() == 335544336) { throw new DaoException( "Conflit d'accès au même enregistrement". 35.firebirdsql. 7. } } } // attente private void wait(int N) { // on attend N ms try { Thread. 15.jdbc.entites.getId() + "] n'existe pas ou bien a été modifiée". 30. 39. 40. 19. package istia. 26. par l'exemple 217/264 .org/dtd/spring-beans. 23. 17. } } } • • • • • • ligne 5 : la classe [DaoImplFirebird] dérive de [DaoImplCommon].4.springframework. // modification try { // on modifie la personne qui a la bonne version int n = getSqlMapClientTemplate(). 4). 41.getCause(). 4. 11. } catch (org. une [DaoException] de code 4 est lancée.ce type de test dans [DaoImplCommon].updateOne".FBSQLException. } } else { throw ex. import istia. 44. 9.jdbc.dao. 3. Elle redéfinit. 33. 22. on va lier cette classe au SGBD Firebird. la méthode [updatePersonne] qui nous pose problème. C'est ce que nous faisons maintenant. 12.Personne.printStackTrace().getClass().getCause().3 Tests de l'implémentation [DaoImplFirebird] Le fichier de configuration des tests [spring-config-test-dao-firebird.

<property name="username"> 15. <value>classpath:sql-map-config-firebird. 7. <ref local="sqlMapClient"/> 35. 3. 6. </property> 10. Les méthodes de la couche [dao] de la version 1 n’étant pas synchronisées. </bean> 37. des conflits d’accès apparaissaient. <property name="configLocation"> 28.dbcp. </property> 17. 2. <property name="driverClassName"> 8. <value>sysdba</value> 16. </property> 27.st.gdb</value> 13.springframework. <property name="sqlMapClient"> 34. destroy-method="close"> 7.DaoImplFirebird"> 33. <property name="url"> 12.apache. 4. </property> 30. D’autres logs montrent des conflits d’accès lors des mises à jour : 1.commons. </bean> 31.orm. <value>masterkey</value> 19.dao. <property name="password"> 18.BasicDataSource" 6. thread thread thread thread thread thread thread n° n° n° n° n° n° n° 36 75 36 36 36 36 36 [1145977145984] [1145977145984] [1145977146000] [1145977146000] [1145977146000] [1145977146015] [1145977146031] : : : : : : : fin attente a terminé et passé le nombre d'enfants à 99 version corrompue ou personne inexistante 99 -> 100 pour la version 100 début attente fin attente a terminé et passé le nombre d'enfants à 100 La dernière ligne indique que c’est le thread n° 36 qui a terminé le dernier. par l'exemple 218/264 . <!-. Les bases du développement web MVC en Java.la classes d'accè à la couche [dao] --> 32. Nous avons simplement géré les conflits d’accès signalés par Firebird.SqlMapCllient --> 22. La ligne 3 montre un conflit de version qui a forcé le thread n° 36 à reprendre sa procédure de mise à jour de la personne (ligne 4). <bean id="dataSource" class="org. <value>org. Les dernières lignes de logs écran sont les suivantes : 1. class="org. thread n° 52 [1145977145765] : version corrompue ou personne inexistante thread n° 75 [1145977145765] : conflit de mise à jour thread n° 36 [1145977145765] : version corrompue ou personne inexistante La ligne 2 montre que le thread n° 75 a échoué lors de sa mise à jour à cause d’un conflit de mise à jour : lorsque la commande SQL [update] a été émise sur la table [PERSONNES]. nous n’avons pas eu besoin de synchroniser la couche [dao]. <bean id="dao" class="istia. 2. <ref local="dataSource"/> 26. <!-.3.FBDriver</value> 9. </beans> • ligne 32 : la nouvelle implémentation [DaoImplFirebird] de la couche [dao]. </bean> 21. <bean id="sqlMapClient" 23.jdbc.personnes. <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes.attention : ne pas laisser d'espaces entre les deux balises <value> --> 11. la ligne qu’il fallait mettre à jour était verrouillée par un autre thread. </property> 14. <!-.ibatis. </property> 36. 3. Ce conflit d'accès va obliger le thread n° 75 à retenter sa mise à jour. </property> 20.xml</value> 29. <property name="dataSource"> 25.mvc. <beans> 4.la source de donnéees DBCP --> 5.SqlMapClientFactoryBean"> 24.firebirdsql. 5. Ici. Les résultats du test [test4] qui avait échoué précédemment sont les suivants : [test4] a été réussi. Pour terminer avec [test4] on remarquera une différence notable avec les résultats du même test dans la version 1 où il avait échoué à cause de problèmes de synchronisation. <!-.

} • l’interface a les mêmes quatre méthodes que dans la version 1 mais elle en a deux de plus : • saveMany : permet de sauvegarder plusieurs personnes en même temps de façon atomique. 2. 23.util. 4.Collection. 12. 15. Les deux méthodes devront en effet être exécutées au sein d’une transaction pour obtenir l’atomicité désirée. 17. 11. • deleteMany : permet de supprimer plusieurs personnes en même temps de façon atomique. Nous les avons rajoutées pour illustrer la notion de transaction sur une base de données. Soit elles sont toutes sauvegardées. nous la considèrerons comme opérationnelle. 25. // ajouter/modifier une personne void saveOne(Personne personne). Néanmoins. par l'exemple 219/264 . // supprimer une personne void deleteOne(int id).mvc.5. 13. // supprimer plusieurs personnes void deleteMany(int ids[]). public interface IService { // liste de toutes les personnes Collection getAll(). soit aucune ne l’est. 24. 7.personnes. 14.service. Pour la déclarer valide avec une forte probabilité. 5. // sauvegarder plusieurs personnes void saveMany(Personne[] personnes). 22. import istia.5 17. Ces deux méthodes ne seront pas utilisées par l’application web. package istia. 3.st. import java. 9. 21. soit aucune ne l’est. 17.st. 16. 6. 20. il nous faudrait faire davantage de tests. // obtenir une personne particulière Personne getOne(int id).Personne. 8. Soit elles sont toutes supprimées.Exécutons maintenant la totalité du test JUnit de la couche [dao] : Il semble donc qu’on ait une couche [dao] valide.entites. 18.personnes. La classe [ServiceImpl] implémentant cette interface sera la suivante : Les bases du développement web MVC en Java. 10.1 La couche [service] Les composantes de la couche [service] La couche [service] est constituée des classes et interfaces suivantes : • • [IService] est l’interface présentée par la couche [service] [ServiceImpl] est une implémentation de celle-ci L’interface [IService] est la suivante : 1. 19.mvc.

34. } public void setDao(IDao dao) { this. 44. 24. 8. 17. 28. destroy-method="close"> 7. 12.getAll().deleteOne(id). i++) { dao.FBDriver</value> Les bases du développement web MVC en Java. 33. 11.saveOne(personnes[i]).st. Celle-ci. comme dans la version 1.service. 32.apache.la source de donnéees DBCP --> 5. 15. 46.dbcp. } // ajouter ou modifier une personne public void saveOne(Personne personne) { dao. Nous pouvons constater que le code ci-dessus ignore totalement cette notion de transaction. i++) { dao. une par une.entites. 29. } // suppression d'une personne public void deleteOne(int id) { dao. ligne 11. une par une. 13. } // obtenir une personne en particulier public Personne getOne(int id) { return dao. lignes 42-47 : la méthode [saveMany] sauvegarde. 17. 48. 1.personnes. 49. 40.springframework.mvc. <!-. lignes 50-55 : la méthode [deleteMany] supprime. <!DOCTYPE beans SYSTEM "http://www.org/dtd/spring-beans. 42. 16.IDao.dao = dao. les personnes du tableau passé en paramètre.deleteOne(ids[i]). 4. 19.firebirdsql. 36. 25. sera initialisée par Spring au moment de l’instanciation de la couche [service . on voit que l’implémentation [ServiceImpl] détient une référence sur la couche [dao].util.dtd"> 3. 37. insertOne. Le fichier de configuration qui permettra l’instanciation de la couche [service] sera le suivant : 1. 20. public IDao getDao() { return dao.personnes. <?xml version="1. 10. 53. 47.jdbc. 39.dao. <property name="driverClassName"> 8.st.1.getOne(id). import java. package istia. <bean id="dataSource" class="org. 41. 43. i < ids. 23.0" encoding="ISO_8859-1"?> 2. 52. saveOne] font appel aux méthodes de la couche [dao] de même nom.BasicDataSource" 6. } // sauvegarder une collection de personnes public void saveMany(Personne[] personnes) { // on boucle sur le tableau des personnes for (int i = 0. les personnes dont on lui a passé le tableau des id en paramètre Nous avons dit que les méthodes [saveMany] et [deleteMany] devaient se faire au sein d’une transaction pour assurer l’aspect tout ou rien de ces méthodes. <beans> 4. 38.commons.mvc. i < personnes. 54.personnes.Personne. 31. 18. 14.st. 35. Celle-ci n’apparaîtra que dans le fichier de configuration de la couche [service]. 30. 21.length. 7. } // liste des personnes public Collection getAll() { return dao. par l'exemple 220/264 . 5. 51. 55. 27. 3. public class ServiceImpl implements IService { // la couche [dao] private IDao dao.2 Configuration de la couche [service] Ci-dessus. 50. 22.length.5. import istia. getOne. 2. <value>org. 45.ServiceImpl].saveOne(personne). 6.Collection.mvc. import istia. 26. } } // supprimer une collection de personnes public void deleteMany(int[] ids) { // ids : les id des personnes à supprimer for (int i = 0. } } } • • • les méthodes [getAll. 9.

54. 11. Comment se peut-il qu’un type prédéfini puisse implémenter l’interface [IService] qui elle. 58. 35. 48. on peut voir que l’implémentation de la couche [service] est faite par le type [TransactionProxyFactoryBean]. Cette configuration a été expliquée lors de l’étude de la couche [dao] au paragraphe 17. 14.la classes d'accès à la couche [dao] --> <bean id="dao" class="istia. 42. 40.xml</value> </property> </bean> <!-.st.springframework. 45. 46.dao. 65. 43. 31. 15. par l'exemple 221/264 . 36.gestionnaire de transactions --> <bean id="transactionManager" class="org. 23.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <bean class="istia.2 – page 200.mvc. 24.SqlMapCllient --> <bean id="sqlMapClient" class="org. 63. 30.interceptor.ibatis. 37. 57.orm. 22.la classes d'accès à la couche [service] --> <bean id="service" class="org. 34. 20. 50. </property> <property name="url"> <!-. 47. 32.springframework.st. 64. lignes 38-64 : configurent la couche [service] Ligne 46. 25.gdb</value> </property> <property name="username"> <value>sysdba</value> </property> <property name="password"> <value>masterkey</value> </property> </bean> <!-. 55. 33.personnes. 49. 28.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <!-. 12. 19. 38. 21.3. 16. 41. [TransactionProxyFactoryBean] est un type prédéfini de Spring. 26.springframework.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-firebird. On s’attendait à trouver le type [ServiceImpl]. est spécifique à notre application ? Découvrons tout d’abord la classe [TransactionProxyFactoryBean] : Les bases du développement web MVC en Java. 29. 44.attention : ne pas laisser d'espaces entre les deux balises <value> --> <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes. 17. 56. 62. 59.datasource.service. 13. 18.ServiceImpl"> <property name="dao"> <ref local="dao"/> </property> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_SUPPORTS. 27. 52. 61. 60. 39. 53. 10.mvc.9.transaction. 51.DaoImplFirebird"> <property name="sqlMapClient"> <ref local="sqlMapClient"/> </property> </bean> <!-.jdbc.readOnly</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans> • • lignes 1-36 : configuration de la couche [dao].personnes.

AOP vient placer la classe [intercepteur] entre [C1] et [C2]. C’est en fait la méthode M de [intercepteur] qui sera appelée. par exemple. L’appel de [C1] à [C2]. de façon transparente pour les deux classes. A quoi cela peut-il servir ? La documentation Spring donne quelques exemples. Nous savons que lorsqu’une application demande à Spring. Spring rend non pas une instance [I] de ce type. Pour que cela soit possible. On peut vouloir faire. il faut bien sûr que la classe [intercepteur] présente à [C1] la même interface [I2] que [C2]. Vis à vis de [C1]. on aura l’architecture suivante : Les bases du développement web MVC en Java. on peut placer. elle enrichit donc la méthode M de [C2]. pour faire un audit de cette méthode.getObject() : Dans notre cas. Dans [intercepteur]. Cela est possible si [C1] s’adresse à une interface [I2] plutôt qu’à une implémentation particulière de [I2]. la couche [service] va être implémentée par l’objet rendu par [TransactionProxyFactoryBean]. Il suffit alors que [intercepteur] implémente [I2]. Au moment de l’exécution. Quelle est la nature de cet objet ? Nous n’allons pas rentrer dans les détails car ils sont complexes. AOP permet la chose suivante : • on a deux classes C1 et C2. On peut donc voir la technologie AOP comme une façon d’enrichir l’interface présentée par une classe. Ils relèvent de ce qu’on appelle Spring AOP (Aspect Oriented Programming). notre application web aura l’architecture suivante : [web] [service] [ServiceImpl] [dao] [DaoImplCommon] Si on implémente la couche [service] avec une instance [TransactionProxyFactoryBean]. on écrira alors une méthode [M] qui fait ces logs. [C1] appelle la méthode M de [C2].getObject(). C1 utilisant l'interface [I2] présentée par C2 : [C1] [C2] • grâce à AOP. mais l’objet rendu par la méthode [I]. Nous allons tenter d’éclaircir les choses avec de simples schémas. 2. Nous avons déjà rencontré cette interface.Nous voyons qu’elle implémente l’interface [FactoryBean]. la méthode M de [intercepteur] rend un résultat à la méthode appelante de [C1] On voit que la méthode M de [intercepteur] peut faire quelque chose avant et après l’appel de la méthode M de [C2]. la méthode M de [C2] s’exécute et rend son résultat à la méthode M de [intercepteur] qui peut éventuellement ajouter quelque chose à ce qui a été fait en 2. 3. Comment ce concept s’applique-t-il à notre couche [service] ? Si on implémente la couche [service] directement avec une instance [ServiceImpl]. la méthode M de [intercepteur] fait les logs et appelle la méthode M de [C2] visée initialement par [C1]. un intercepteur entre les classes C1 et C2 : [C1] 1 4 [intercepteur] 2 [C2] 3 La classe [C1] a été compilée pour travailler avec l'interface [I2] que [C2] implémente. 4. par l'exemple 222/264 . des logs à l'occasion des appels à une méthode M particulière de [C2]. une instance d’un type implémentant [FactoryBean].M va se passer ainsi (cf schéma ci-dessus) : 1.

18. 15. une instance [ServiceImpl] qui elle aussi implémente l’interface [IService]. 13. Elle seule sait comment travailler avec la couche [dao]. 4. 11. 14. 22. Imaginons que la couche [web] appelle la méthode [saveMany] de l’interface [IService]. 5. La couche [dao] va laisser remonter une exception vers la couche [service].[web] 1 7 [proxy 2 transactionnel] [service] 3 [ServiceImpl] [dao] [DaoImplCommon] 6 5 4 On peut dire que la couche [service] est instanciée avec deux objets : • • l’objet que nous appelons ci-dessus [proxy transactionnel] et qui est en fait l’objet rendu par la méthode [getObject] de [TransactionProxyFactoryBean]. 26. aussi est-elle nécessaire. 21. Nous savons que fonctionnellement.jdbc.springframework.service. 16. Les ordres SQL exécutés à cette occasion le sont dans la transaction commencée en 2. 5. Nous avons maintenant une idée plus précise de l’architecture mise en place par le bean [TransactionProxyFactoryBean]. 24. 19. la méthode [saveMany] de [proxy transactionnel] qui est propriétaire de la transaction fait un [rollback] de celle-ci pour annuler la totalité des mises à jour. 28. 6. 25.personnes.gestionnaire de transactions --> <bean id="transactionManager" class="org. 6. Il implémente par construction l’interface [IService].ServiceImpl"> <property name="dao"> <ref local="dao"/> </property> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED. la méthode [saveMany] de [proxy transactionnel] fait un [commit] de la transaction pour valider la totalité des mises à jour.la classes d'accès à la couche [service] --> <bean id="service" class="org. 2. par l'exemple 223/264 . 2. celle-ci s’exécute.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <bean class="istia. la méthode [saveMany] de [proxy transactionnel] est exécutée.mvc. 8. notamment un objet [DataSource] pour obtenir une connexion au SGBD. Elle fait appel de façon répétée à la couche [dao] pour exécuter les insertions ou les mises à jour. nous avons supposé qu’une des insertions ou des mises à jour échouait. Il faut qu’elle ait les informations suffisantes pour le faire. en l’occurrence la méthode [saveMany] de l’instance [ServiceImpl]. La méthode [saveMany] du [proxy transactionnel] va enrichir la méthode [saveMany] de la classe [ServiceImpl] avec cette notion de transaction. Nous avons présenté la méthode [saveMany] de la classe [ServiceImpl] et nous avons pointé le fait qu’elle n’avait pas la notion de transaction. supposons qu’une de ces opérations échoue. puis laisse remonter l’exception jusqu’à la couche [web] qui sera chargée de la gérer. Puis elle fait appel à la méthode [saveMany] de [ServiceImpl]. 4. <!-. 10.springframework. Suivons le schéma ci-dessus : 1. Dans ce cas.interceptor.readOnly</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> Suivons cette configuration à la lumière de l’architecture qui est configurée : Les bases du développement web MVC en Java. 3. Si ce n’est pas le cas. Revenons sur la configuration de celui-ci : 1. 27. 17. Soit ils réussissent tous. en [5] aucune exception ne remonte.transaction.st. 9. C’est cet objet qui fera l’interface de la couche [service] avec la couche [web].DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <!-. 20. Idem en [6]. 7. soit aucun n’est fait. Elle commence une transaction. 3. les ajouts / mises à jour faits par cette méthode doivent l'être dans une transaction.datasource. 12. la couche [web] appelle la méthode [saveMany] de l’interface [IService]. 23. à réception de l’exception. celle-ci ne fait rien et laisse remonter l’exception jusqu’à la méthode [saveMany] de [proxy transactionnel]. A l’étape 4.

seules les méthodes [saveMany] et [deleteMany] ont besoin de s’exécuter dans une transaction. La classe [DataSourceTransactionManager] a besoin de connaître la source de données auprès de laquelle elle doit demander une connexion pour l’attacher au thread. getAll] s’exécutent dans une transaction d’attribut [PROPAGATION_REQUIRED. Il ne sait pas gérer des transactions distribuées sur plusieurs SGBD. nous n’avons qu’un seul SGBD. Ici. • • • Dans notre couche [service]. lignes 21-27 : indiquent quelles méthodes de [ServiceImpl]. JDBC]. Il s'agit à chaque fois d'une opération constituée d'un unique ordre SELECT.5. le proxy doit intercepter.[web] 1 7 [proxy 2 transactionnel] [service] 3 [ServiceImpl] [dao] [DaoImplCommon] 6 5 4 • • • [proxy transactionnel] va gérer les transactions. page 220). La configuration aurait pu être réduite aux lignes suivantes : 1. sinon une nouvelle est créée et la méthode s’exécute dedans. </props> 6. • lignes 14-19 : l’attribut " target " indique la classe qui doit être interceptée. il va le faire sur une connexion attachée au thread. saveMany] s’exécutent dans une transaction d’attribut [PROPAGATION_REQUIRED]. SqlMapClientTemplate.readOnly] : • PROPAGATION_REQUIRED : la méthode s’exécute dans une transaction s'il y en a déjà une attachée au thread. Celle-ci est définie lignes 4-6 : c’est la même source de données que celle utilisée par la couche [dao] (cf paragraphe 17. par l'exemple 224/264 . C’est cette connexion qui sera utilisée dans toutes les couches qui mènent à la base de données : [ServiceImpl. lignes 2-7 : le gestionnaire de transactions est de type [DataSourceTransactionManager] : [DataSourceTransactionManager] est un gestionnaire de transactions adapté aux SGBD accédés via un objet [DataSource]. Il ne sait gérer que les transactions sur un unique SGBD. ligne 21. Spring offre plusieurs stratégies de gestion de celles-ci. [proxy transactionnel] a besoin d’une référence sur le gestionnaire de transactions choisi. • readOnly : transaction en lecture seule Ici les méthodes [getOne] et [getAll] de [ServiceImpl] s’exécuteront dans une transaction alors qu'en fait ce n'est pas nécessaire. </property> Les bases du développement web MVC en Java. saveMany]. Cette information est nécessaire pour deux raisons : • la classe [ServiceImpl] doit être instanciée puisque c’est elle qui assure le dialogue avec la couche [dao] • [TransactionProxyFactoryBean] doit générer un proxy qui présente à la couche [web] la même interface que [ServiceImpl]. Celui-ci est défini lignes 2 – 7. [saveOne. <property name="transactionAttributes"> 2. <props> 3. ligne 25 : les méthodes [deleteOne] et [deleteMany] de [ServiceImpl] sont configurées de façon identique aux méthodes [saveOne. <prop key="saveMany">PROPAGATION_REQUIRED</prop> 4. indique quelles méthodes de [ServiceImpl] nécessitent une transaction et quels sont les attributs de celle-ci : • ligne 23 : les méthodes dont le nom commencent par get [getOne. DaoImplCommon. L’attribut [transactionAttributes]. Lorsque [proxy transactionnel] va démarrer une transaction. lignes 11 – 13 : définissent l’attribut [transactionManager] du bean [TransactionProxyFactoryBean] avec une référence sur un gestionnaire de transactions. ligne 24 : les méthodes dont le nom commencent par save. ici la classe [ServiceImpl]. On ne voit pas l'utilité de mettre ce SELECT dans une transaction. Aussi ce gestionnaire de transactions convient-il. <prop key="deleteMany">PROPAGATION_REQUIRED</prop> 5.2.

2. 35. 32. 59. 12. 52.. 55. 45. 5. Les bases du développement web MVC en Java. 40. 56. 16. par l'exemple 225/264 . InterruptedException{ . 29. 4. 58. 36. 11...xml] de la couche [service] est celui qui a été décrit au paragraphe 17. 22.personnes. 6.. } // insertions multi-threads public void test6() throws ParseException.. 54. package istia. 53. 46. 25. 37. 60. page 220. 39. public class TestServiceFirebird extends TestCase { // couche [service] private IService service. 34.xml"))).accès multi-threads public void test4() throws Exception { . public IService getService() { return service. 50. 19. 48. } // test1 public void test1() throws ParseException { .getAll()..mvc. 3. 31. 33. 43. 15.6 Tests de la couche [service] Maintenant que nous avons écrit et configuré la couche [service]. 28.. 49. 38. 17.. } // modification-suppression d'un élément inexistant public void test2() throws ParseException { . 21. } // optimistic locking . } // tests de la méthode deleteMany public void test7() throws ParseException { // liste actuelle Collection personnes = service. 10. 42..service = service. 13.2.. 57..st. } // liste des personnes private void doListe(Collection personnes) { ... 14.tests.. 18. 51. 26. nous nous proposons de la tester avec des tests JUnit : Le fichier de configuration [spring-config-test-service-firebird. 9.. Le test JUnit [TestServiceFirebird] est le suivant : 1. 8. 47. 62. 44. 7. . 30. 20. } // tests de validité de saveOne public void test5() throws ParseException { . 61. 27. InterruptedException { . } // setup public void setUp() { service = (IService) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-service-firebird.getBean("service")..5.17. } public void setService(IService service) { this. 24. } // gestion des versions de personne public void test3() throws ParseException. 41. 23.

103. 96. codeErreur = ex.size(). int id1 = p1. // on doit avoir une erreur de code 2 146. assertEquals(2. 104. 147. // une exception 75. 80. System.parse("01/04/2006"). 66. erreur = true. 112. "X". "Z". new SimpleDateFormat( 72. nbPersonnes3). Personne p3 = new Personne(-2. p2. "Y". par l'exemple 226/264 .getCode(). 118. 93. codeErreur). assertEquals(p1. // création de trois personnes 67. nbPersonnes2).setId(-1). // personne p1 125.out.getAll(). erreur = false.println(ex.getNom(). } 113. p1 = service. "Y". personnes = service. } 133. service.getOne(id1). erreur = true. // personne p2 137. assertTrue(erreur). p1 = service. 135. // on supprime les deux personnes valides 122. 131. erreur = false.getAll(). nbPersonnes3). int nbPersonnes1 = personnes. 91. "dd/MM/yyyy"). boolean erreur = false. 2). -1 }). p1. 98. // nouvelle liste 149. 115. codeErreur = ex. 1). try { 108.setId(-1). assertTrue(erreur). int nbPersonnes3 = service. service. erreur = true. 123. new SimpleDateFormat( 70. 117. try { 140. 111. // ajout des deux personnes valides 89. "Z". 0). assertEquals(nbPersonnes1. // nouvelle liste 116. "X". 95. // on remet leur id à -1 90. int codeErreur = 0. int id2 = p2. // aucune personne n'a du être supprimée (rollback 119.saveMany(new Personne[] { p1. id2 }).getAll(). personnes = service. p2 }). System. try { 77. // on doit avoir une erreur de code 2 134.getCode(). id2. 99. // automatique de la transaction) 120.deleteMany(new int[] { id1. 69. 126. false. 148. "X"). assertEquals(p2. try { 128. int nbPersonnes4 = personnes. } catch (Exception ex) { 79.le nombre d'éléments n'a pas du changer 85.saveMany(new Personne[] { p1. 129. "dd/MM/yyyy"). 141. 88.size(). 144. // à cause rollback automatique de la transaction 86. 100.out.println(ex. assertTrue(erreur). int nbPersonnes2 = service. 107. "dd/MM/yyyy"). 139. 81. assertEquals(nbPersonnes4. // affichage 65. } catch (DaoException ex) { 130. // on récupère leurs id 94. true. 136. 73. } 145. 109.getId(). p2. assertEquals(nbPersonnes1 + 2. new SimpleDateFormat( 68. // vérification 114. assertEquals(2. codeErreur). // vérification 83. codeErreur = 0.toString()). 121. Personne p1 = new Personne(-1. "Y"). p1 = service. // nouvelle liste . 87. 132. 76.getOne(id2). // nouvelle liste .63. p3 }). 101. // ajout des 3 personnes . // suppression de p1 et p2 et d'une personne inexistante 105.size(). 71.getNom().toString()). Personne p2 = new Personne(-1. erreur = true. // une exception doit se produire 106.getAll(). service. true.getId().getOne(id1).getOne(id2). // vérifications 124. 78. erreur = false. } catch (Exception ex) { 110. assertTrue(erreur).deleteMany(new int[] { id1. 84. 127.on doit avoir 2 éléments de + 102. } catch (DaoException ex) { 142.parse("01/03/2006").parse("01/02/2006"). p2 = service. doListe(personnes). service. 64. } 82. Les bases du développement web MVC en Java. 143. 92. 138.la personne p3 avec l'id -2 va provoquer 74. // vérifications 97.size().

155. ligne 108.} int nbPersonnes5 = personnes. L’existence de cette exception est testée ligne 83. 157. 151. On veut vérifier qu’elles s’exécutent bien dans une transaction. Lignes 116-117.xml]. ceci parce que cette méthode s’exécute dans une transaction. Celle-ci va échouer car il n’y a aucune personne avec un id égal à – 2 dans la table [PERSONNES]. Lignes 86-87. nbPersonnes1). lignes 106-114 : on supprime un groupe de personnes constitué des personnes p1 et p2 qu’on vient d’ajouter et d’une personne inexistante (id= -1). • • • • • • • • L’exécution des tests donne les résultats suivants : Les sept tests ont été réussis.on doit être revenu au point de départ assertEquals(nbPersonnes5. 153. 152. à cause de l’exception précédente. on vérifie que le nombre de personnes de la liste n’a pas bougé et que donc les insertions de p1 et p2 n’ont pas eu lieu. celui étudié dans la section précédente. 17. Cela devrait réussir. L’existence de cette exception est testée ligne 114. La personne p3 a elle un id égal à -2. La couche [dao] va donc lancer une exception qui va remonter jusqu’à la couche [service]. // vérification . Nous considèrerons notre couche [service] comme opérationnelle. par l'exemple 227/264 . La seule différence est que par configuration. ligne 122 : on supprime un groupe constitué des seules personnes p1 et p2. à cause de l’exception précédente.size(). 154. Les deux premières personnes p1 et p2 ayant un id égal à -1 vont être ajoutées à la table [PERSONNES]. Il ne s’agit donc pas d’une insertion mais d’une mise à jour. on vérifie que le nombre de personnes de la liste n’a pas bougé et que donc les suppressions de p1 et p2 n’ont pas eu lieu. } 156.7 La couche [web] Rappelons l’architecture générale de l’application web à construire : Les bases du développement web MVC en Java.150. la méthode [test7] a pour but de tester les méthodes [saveMany] et [deleteMany]. lignes 88-103 : on ajoute les seules personnes p1 et p2 et on vérifie qu’ensuite on a deux personnes de plus dans la liste. Cette méthode va échouer car il n’y a aucune personne avec un id égal à – 1 dans la table [PERSONNES]. ceci parce que cette méthode s’exécute dans une transaction. La méthode [deleteMany] est utilisée pour cela. les méthodes [saveOne] et [deleteOne] s’exécutent désormais dans une transaction. Le reste de la méthode vérifie que c’est bien le cas. les tests [test1] à [test6] sont identiques dans leur esprit à leurs homologues de même nom dans la classe de test [TestDaoFirebird] de la couche [dao]. • • • lignes 19-22 : le programme teste des couches [dao] et [service] configurées par le fichier [spring-config-test-servicefirebird. La couche [dao] va donc lancer une exception qui va remonter jusqu’à la couche [service]. la couche [service] devrait faire un [rollback] de l’ensemble des ordres SQL émis pendant l’exécution de la méthode [deleteMany]. Commentons le code de cette méthode : lignes 62-63 : on compte le nombre de personnes [nbPersonnes1] actuellement dans la liste lignes 67-72 : on crée trois personnes lignes 73-83 : ces trois personnes sont sauvegardées par la méthode [saveMany] – ligne 77. la couche [service] devrait faire un [rollback] de l’ensemble des ordres SQL émis pendant l’exécution de la méthode [saveMany]. // affichage doListe(personnes).

Dans le précédent article.jar ] : Les bases du développement web MVC en Java. service. par l'exemple 228/264 . la version 1 de l’application avait été testée avec le projet Eclipse [mvc-personnes-02B] où les couches [web.Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC Nous venons de construire les couches [dao] et [service] permettant de travailler avec une base de données Firebird.jar : 1 Le dossier [src] était vide. Nous avons écrit une version 1 de cette application où les couches [dao] et [service] travaillaient avec une liste de personnes en mémoire. La couche [web] écrite à cette occasion reste valide. Les classes des couches étaient dans les archives [personnes-*. elle s’adressait à une couche [service] implémentant l’interface [IService]. dao. En effet. La nouvelle couche [service] implémentant cette même interface. entites] avaient été mises dans des archives . la couche [web] n’a pas à être modifiée.

firebirdsql-full. Ceci fait.. Nous copions / collons également les archives [commons-dbcp-*. nous exportons [File / Export / Jar file] les couches [dao] et [service] respectivement dans les archives [personnes-dao.couche [dao] .jar] du dossier [lib] du projet [mvc-personnes-03] dans le dossier [WEB-INF/lib] du projet [mvcpersonnes-03B].jar.couche [web] Pour tester la version 2.jar] et [personnes-service. commons-pool-*.couche [service] . puis sous Eclipse nous les collons dans le dossier [WEB-INF/lib] du projet [mvc-personnes03B] où ils vont remplacer les archives de même nom de la version précédente. Ces archives sont nécessaires aux nouvelles couches [dao] et [service]. nous incluons les nouvelles archives dans le Classpath du projet : [clic droit sur projet -> Properties -> Java Build Path -> Add Jars].jar.jar. par l'exemple 229/264 . ibatis-common2.jar.jar] du dossier [dist] du projet : Nous copions ces deux fichiers.couche [entites] . sous Eclipse nous dupliquons le dossier Eclipse [mvc-personnes-02B] en [mvc-personnes-03B] (copy / paste) : Dans le projet [mvc-personnes-03]. ibatis-sqlmap-2. Les bases du développement web MVC en Java.

<bean id="sqlMapClient" 23. </property> 36. <!-.jdbc. <property name="username"> 15. <property name="password"> 18. </props> 63. </bean> 65. </property> 43. <property name="url"> 11. <bean id="transactionManager" 39. </property> 64.st. <props> 59.xml</value> 29. <value>sysdba</value> 16.service. <!DOCTYPE beans SYSTEM "http://www.FBDriver</value> 9.la source de donnéees DBCP --> 5. <!-.TransactionProxyFactoryBean"> 47. </bean> 31.gestionnaire de transactions --> 38. </bean> 21. <prop key="delete*">PROPAGATION_REQUIRED</prop> 62. class="org. </property> 20.personnes.springframework.0" encoding="ISO_8859-1"?> 2.interceptor. </property> 50.xml] configure les couches [dao] et [service] de l’application web. <ref local="dataSource"/> 42. <property name="transactionAttributes"> 58. <property name="driverClassName"> 8. class="org.orm. par l'exemple 230/264 • . <property name="dao"> 53.DataSourceTransactionManager"> 40. Dans la nouvelle version.gdb</value> 13. </property> 17.transaction.ibatis. </property> 57.attention : ne pas laisser d'espaces entre les deux balises <value> --> 12. </property> 30. <!-. </bean> 37. <property name="dataSource"> 25. <bean id="service" 46.dbcp.datasource. <ref local="transactionManager"/> 49.mvc. <value>masterkey</value> 19.BasicDataSource" 6.springframework.Le dossier [src] contient les fichiers de configuration des couches [dao] et [service] : Le fichier [spring-config.st. </property> 14.mvc. <property name="target"> 51. <bean id="dao" class="istia. il est identique au fichier [spring-config-test-service-firebird.SqlMapCllient --> 22.firebirdsql.SqlMapClientFactoryBean"> 24. <property name="configLocation"> 28. <ref local="dao"/> 54.dao. <property name="dataSource"> 41.springframework. destroy-method="close"> 7. <value>org.xml] qui a servi pour configurer le test de la couche service dans le projet [mvc-personnes-03].readOnly</prop> 60. <ref local="dataSource"/> 26. <beans> 4.dtd"> 3.la classes d'accès à la couche [service] --> 45. <!-. </property> 10. <bean id="dataSource" class="org.la classes d'accès à la couche [dao] --> 32.apache. class="org. <prop key="get*">PROPAGATION_SUPPORTS. <!-. Nous continuons à utiliser la base qui a servi aux tests des couches [dao] et [service] Les bases du développement web MVC en Java. On fait donc un copier / coller de l’un vers l’autre : 1. <property name="sqlMapClient"> 34. </bean> 44. </beans> ligne 12 : l’url de la base de données Firebird. <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes. <!-. <ref local="sqlMapClient"/> 35.org/dtd/spring-beans. </property> 27. </property> 55. <prop key="save*">PROPAGATION_REQUIRED</prop> 61. <?xml version="1.springframework.commons.personnes.DaoImplFirebird"> 33. <value>classpath:sql-map-config-firebird. </bean> 56.ServiceImpl"> 52.jdbc. <property name="transactionManager"> 48. <bean class="istia.

par l'exemple 231/264 .Nous déployons le projet web [mvc-personnes-03B] au sein de Tomcat : Nous sommes prêts pour les tests. Avec un navigateur. Le SGBD Firebird est lancé. nous demandons l’url [http://localhost:8080/mvc-personnes-03B] : Nous ajoutons une nouvelle personne avec le lien [Ajout] : Nous vérifions l’ajout dans la base de données : Les bases du développement web MVC en Java. Le contenu de la table [PERSONNES] est alors le suivant : Tomcat est lancé à son tour.

par l'exemple 232/264 . Faisons maintenant le test de conflits de version qui avait été fait dans la version 1. Celui-ci demande l’url [http://localhost:8080/mvc-personnes-03B] : [IE] sera le navigateur de l’utilisateur U2. suppression]. Celui-ci demande la même Url : L’utilisateur U1 entre en modification de la personne [Perrichon] : Les bases du développement web MVC en Java.Le lecteur est invité à faire d’autres tests [modification. [Firefox] sera le navigateur de l’utilisateur U1.

par l'exemple 233/264 .L’utilisateur U2 fait de même : L’utilisateur U1 fait des modifications et valide : réponse du serveur validation L’utilisateur U2 fait de même : Les bases du développement web MVC en Java.

17. C’était la version 1. On se propose maintenant de voir l’impact qu’a sur notre application le changement de SGBD. Elle est donc désormais persistante. les couches [service] et [dao] ont été réécrites pour que la liste de personnes soit dans une table de base de données. Dans la version 2. nous allons construire trois nouvelles versions de notre application web : Les bases du développement web MVC en Java.validation le conflit de version a été détecté L’utilisateur U2 revient à la liste des personnes avec le lien [Annuler] du formulaire : Il trouve la personne [Perrichon] telle que U1 l’a modifiée (nom passé en majuscules). Pour cela. par l'exemple 234/264 . Nous avions une application web avec l’architecture 3tier suivante : où les couches [dao] et [service] travaillaient avec une liste de données en mémoire qui était donc perdue lorsque le serveur web était arrêté.8 Conclusion Rappelons ce que nous voulions faire. Et la base de données dans tout ça ? Regardons : La personne n° 899 a bien son nom en majuscules suite à la modification faite par U1.

Dans ce qui suit.x [http://www. nous allons installer la liste des personnes dans une table de base de données Postgres 8.postgres. par l'exemple 235/264 . Elle va donc changer à chaque version. [personnes-mysql. les copies d’écran proviennent du client EMS PostgreSQL Manager Lite [http://www. le pilote JDBC du SGBD change également à chaque version En-dehors de ces points.sqlmanager.org]. un client d’administration gratuit du SGBD Postgres. 18 Application web MVC dans une architecture 3tier – Exemple 4. Elle contient une table [PERSONNES] : Les bases du développement web MVC en Java. la configuration de l’objet [DataSource] de la couche [dao] est spécifique à un SGBD.xml].xml].Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC • • • version 3 : le SGBD est Postgres version 4 : le SGBD est MySQL version 5 : le SGBD est SQL Server Express 2005 Les changements se font aux endroits suivants : • • • • la classe [DaoImplFirebird] implémente des fonctionnalités de la couche [dao] liées au SGBD Firebird.xml] et [personnes-sqlexpress. Dans la suite.xml] d’iBATIS pour le SGBD Firebird va être remplacé respectivement par les fichiers de mapping [personnes-postgres. Si ce besoin persiste. Postgres 18.net/fr/products/postgresql/manager]. elle sera remplacée respectivement par les classes [DaoImplPostgres]. nous décrivons ces nouvelles versions en ne nous attachant qu’aux seules nouveautés amenées par chacune d'elles. [DaoImplMySQL] et [DaoImplSqlExpress]. le fichier de mapping [personnes-firebird.1 La base de données Postgres Dans cette version. tout reste à l’identique. La base de données s’appelle [dbpersonnes].

"NOM" VARCHAR(30) NOT NULL. "DATENAISSANCE" DATE NOT NULL. "PRENOM" VARCHAR(30) NOT NULL. CREATE TABLE "public". 12. CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK ("NBENFANTS" >= 0). 10. Elle a été construite avec les ordres SQL suivants : 1. ces noms sont sensibles à la casse. 7. 6. la base [dbpersonnes] a. Ce générateur délivre des nombres entiers successifs que nous utiliserons pour donner sa valeur à la clé primaire [ID] de la classe [PERSONNES]. "VERSION" INTEGER NOT NULL. Il est possible que ce mode de fonctionnement de Postgres 8. "MARIE" BOOLEAN NOT NULL. CONSTRAINT "PERSONNES_pkey" PRIMARY KEY("ID"). "ID" INTEGER NOT NULL. Je n’ai pas creusé la question. ) WITH OIDS."PERSONNES" ( 2. "NBENFANTS" INTEGER NOT NULL. 11.x soit configurable. On notera cependant que les noms des colonnes et des tables sont entre guillemets. 5. La table [PERSONNES] pourrait avoir le contenu suivant : Outre la table [PERSONNES]. CONSTRAINT "PERSONNES_chk_NOM" CHECK (("NOM")::text <> ''::text). 9. par l'exemple 236/264 . Nous n’insistons pas sur cette table qui est analogue à la table [PERSONNES] de type Firebird étudiée précédemment. CONSTRAINT "PERSONNES_chk_PRENOM" CHECK (("PRENOM")::text <> ''::text) 13. un objet appelé séquence et nommé [SEQ_ID]. Prenons un exemple pour illustrer son fonctionnement : Les bases du développement web MVC en Java.La table [PERSONNES] contiendra la liste des personnes gérée par l’application web. Par ailleurs. 3. 8. 4.

2 Le projet Eclipse des couches [dao] et [service] Pour développer les couches [dao] et [service] de notre application avec la base de données Postgres 8. 18.émettons l’ordre SQL ci-dessus (F12) -> . nous utiliserons le projet Eclipse [spring-mvc-39] suivant : Le projet est un simple projet Java. Nous l’utiliserons dans le fichier [personnes-postgres.la prochaine valeur du générateur est 38 . par l'exemple 237/264 . Dossier [src] Les bases du développement web MVC en Java.x.xml] qui rassemble les ordres SQL émis sur le SGBD.la valeur obtenue est la valeur [Next value] de la séquence [SEQ_ID] On peut constater que la valeur [Next value] de la séquence [SEQ_ID] a changé (double-clic dessus + F5 pour rafraîchir) : L’ordre SQL SELECT nextval('"SEQ_ID"') permet donc d’avoir la valeur suivante de la séquence [SEQ_ID].double-cliquer sur [SEQ_ID] .. pas un projet web Tomcat.

CONSTRAINT "PERSONNES_chk_PRENOM" CHECK ((("PRENOM")::text <> ''::text)) ). 12. 16.Definition for index PERSONNES_pkey (OID = 17256) : 43. "NBENFANTS") VALUES (1. "DATENAISSANCE" date NOT NULL. '1985-01-12'. 7. 13. 22.0.Data for blobs (OID = 17254) (LIMIT 0. INSERT INTO "PERSONNES" ("ID". 1. 39. 25. 28. nous décrivons les fichiers modifiés. "PRENOM" varchar(30) NOT NULL. "NOM" varchar(30) NOT NULL. 32. "NBENFANTS") VALUES (2. false. 'Joachim'. --. '1984-11-13'.3) -INSERT INTO "PERSONNES" ("ID". 20. 2).Structure for table PERSONNES (OID = 17254) : -CREATE TABLE "PERSONNES" ( "ID" integer NOT NULL. -44. 40. 21. 27. 30.1 --------------------------------------Host : localhost Database : dbpersonnes . 11. 'Major'. "NBENFANTS" integer NOT NULL. "NOM".. "MARIE". "PRENOM". "VERSION". "NOM". "DATENAISSANCE". 31. 'Mélanie'. 17. CONSTRAINT "PERSONNES_chk_NOM" CHECK ((("NOM")::text <> ''::text)). 34.. 1). 'Charles'. "PRENOM". 15. '1986-01-01'. 19. 35. "VERSION". 36. "MARIE" boolean NOT NULL. -. 29. ALTER TABLE ONLY "PERSONNES" Les bases du développement web MVC en Java. "NBENFANTS") VALUES (3. Dossier [database] Ce dossier contient le script de création de la base de données Postgres des personnes : --.Ce dossier contient les codes source des couches [dao] et [service] : Tous les fichiers ayant [postgres] dans leur nom ont pu subir ou non une modfication vis à vis de la version Firebird. 8. 3. INSERT INTO "PERSONNES" ("ID". 10. 1. "DATENAISSANCE". par l'exemple 238/264 1. 2. false. "NOM". --. 1. 6. "MARIE". 18. 38. 37. -42. "VERSION" integer NOT NULL. "MARIE". 9. ----- EMS PostgreSQL Manager Lite 3. 0). 14. CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK (("NBENFANTS" >= 0)). "PRENOM". --. "DATENAISSANCE".Definition for function plpgsql_call_handler (OID = 17230) : -. 24. 'Lemarchand'. 4.Definition for sequence SEQ_ID (OID = 17261) : -CREATE SEQUENCE "SEQ_ID" INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1. 23. "VERSION". Dans ce qui suit. 'Humbort'. true. 41. 33. 5. 26.1.

alias classe [Personne] --> 12."SEQ_ID" (OID = 17261) -SELECT pg_catalog.mapping table [PERSONNES] .Comments -COMMENT ON SCHEMA public IS 'Standard public schema'. PUBLIC "-//iBATIS. <!-.entites.3 La couche [dao] La couche [dao] est la suivante : Nous ne présentons que ce qui change vis à vis de la version [Firebird]. <sqlMap> 11.0" encoding="UTF-8" ?> 2. --. type="istia. --. 52.x. 47. "http://www.dtd"> 6. 51.classe" 13. <!DOCTYPE sqlMap 4. <!-.xml] est le suivant : 1. Le fichier de mapping [personne-postgres. 46. <?xml version="1.objet [Personne] --> Les bases du développement web MVC en Java.attention . ADD CONSTRAINT "PERSONNES_pkey" PRIMARY KEY ("ID"). 37.ibatis.Data for sequence public. Toutes ces archives font partie du Classpath du projet Eclipse. <!-. 7. 10.com//DTD SQL Map 2.0//EN" 5. 53.personnes. et des tables ainsi que des guillemets autour de ces noms --> 9. 49. 18.st. Dossier [lib] Ce dossier contient les archives nécessaires à l’application : On notera la présence du pilote jdbc du SGBD Postgres 8.setval('"SEQ_ID"'. 3.45. true).com/dtd/sql-map-2.Personne"/> 14.mvc. par l'exemple 239/264 . 50. <typeAlias alias="Personne. 48.Postgresql 8 demande l'orthographe exacte des noms de colonnes 8.

<bean id="sqlMapClient" 22. 36. #prenom#.xml] aux détails près suivants : • • les noms des colonnes et des tables sont entre guillemets et ces noms sont sensibles à la casse l’ordre SQL " Personne. <property name="dataSource"> 24.obtenir une personne en particulier --> <select id="Personne. "VERSION". "DATENAISSANCE". "PRENOM".springframework. "VERSION". "NOM". 18.SqlMapClientFactoryBean"> 23.la source de donnéees DBCP --> 5. #nom#. <bean id="dataSource" class="org. #version#. <!-. <value>jdbc:postgresql:dbpersonnes</value> 12. "MARIE". 38. "NBENFANTS" FROM "PERSONNES"</select> <!-. "PRENOM"=#prenom#.dtd"> 3. La configuration de la couche [dao] a été adaptée au SGBD [Postgres]. Donc le SELECT est fait avant l’INSERT. 43. </property> 13. "PRENOM". 39. "PRENOM". 28. 21.liste de toutes les personnes --> <select id="Personne. ici le champ [id]. </bean> 20. 23. <property name="username"> 14. "DATENAISSANCE". Cette valeur va être affectée au champ de l’objet [Personne] désigné par l’attribut [keyProperty] (line 35). <!-. "MARIE". </property> 10. 30. 17. par l'exemple 240/264 . destroy-method="close"> 7.personnes.dbcp.0" encoding="ISO_8859-1"?> 2.orm. 33. "NOM".SqlMapCllient --> 21.classe"> update "PERSONNES" set "VERSION"=#version#+1.postgresql. 35. "NBENFANTS" FROM "PERSONNES" WHERE "ID"=#value#</select> <!-. "MARIE"=#marie#. 20. 42. [value] représente la clé obtenue. • lignes 38-40 : insertion de l’objet [Personne] La classe d’implémentation [DaoImplCommon] de la couche [dao] est celle étudiée dans la version [Firebird]. <beans> 4. 16. <value>postgres</value> 18. Ainsi. "NOM". 45.getAll" resultMap="Personne. 47. </property> Les bases du développement web MVC en Java. Au moment de l’opération d’insertion. 44. le champ [id] de l’objet [Personne] aura donc été mis à jour par l’ordre SQL SELECT. 31. 27. 37. 29. 32.map" > select "ID". • les ordres SQL de la balise <insert> sont exécutés dans l’ordre où ils sont rencontrés. "DATENAISSANCE"=#dateNaissance#. </property> 16. "NBENFANTS"=#nbEnfants# WHERE "ID"=#id# and "VERSION"=#version#</update> <!-.apache. La façon de générer la clé primaire avec Postgres est différente de celle utilisée avec Firebird : • ligne 36 : l’ordre SQL [SELECT nextval('"SEQ_ID"')] fournit la clé primaire. "NBENFANTS") VALUES(#id#. 41. 25.BasicDataSource" 6.classe"> <selectKey keyProperty="id"> SELECT nextval('"SEQ_ID"') as value </selectKey> insert into "PERSONNES"("ID". 46.springframework. <resultMap id="Personne.ibatis. 50.ajouter une personne --> <insert id="Personne.Driver</value> 9. <ref local="dataSource"/> 25. 48.insertOne " a changé lignes 34-41.st. </property> 19. 34.org/dtd/spring-beans.supprimer une personne --> <delete id="Personne.15. 26.insertOne" parameterClass="Personne.mettre à jour une personne --> <update id="Personne. 40.map" class="istia. "DATENAISSANCE". "VERSION". <property name="password"> 17.xml] est-il le suivant : 1. <!DOCTYPE beans SYSTEM "http://www.commons.deleteOne" parameterClass="int"> delete FROM "PERSONNES" WHERE "ID"=#value# </delete> </sqlMap> C’est le même contenu que [personnes-firebird. <value>org.getOne" resultMap="Personne. "NOM"=#nom#. 19.mvc. #dateNaissance#. #marie#. #nbEnfants#) </insert> <!-.updateOne" parameterClass="Personne. <value>postgres</value> 15.entites. 49. <property name="driverClassName"> 8.Personne"> <result property="id" column="ID" /> <result property="version" column="VERSION" /> <result property="nom" column="NOM"/> <result property="prenom" column="PRENOM"/> <result property="dateNaissance" column="DATENAISSANCE"/> <result property="marie" column="MARIE"/> <result property="nbEnfants" column="NBENFANTS"/> </resultMap> <!-. 22. <?xml version="1. class="org. 24. le fichier de configuration [spring-config-testdao-postgres. La syntaxe [as value] est obligatoire. "MARIE". <property name="url"> 11.map" >select "ID".

En effet. <property name="sqlMapClient"> 33. ligne 31 : la classe [DaoImplCommon] est la classe d'implémentation de la couche [dao] Ces modifications faites. 18.26.jar] et [personnes-service. Cependant.couche [dao] . </property> 29. Nous n'aurons pas à dériver cette classe comme il avait été nécessaire de le faire avec le SGBD [Firebird]. </property> 35. Lançons le SGBD Postgres puis les tests Eclipse.mvc.jar] contient la classe [DaoImplFirebird] qui est devenue inutile. Le lecteur modifiera cette configuration selon son propre environnement.4 Les tests des couches [dao] et [service] Les tests des couches [dao] et [service] sont les mêmes que pour la version [Firebird]. par l'exemple 241/264 .st. nous n'avons modifié aucune classe vis à vis du projet [mvc-personnes-03B].jar].DaoImplCommon"> 32.xml</value> 28. <ref local="sqlMapClient"/> 34. Les bases du développement web MVC en Java. on peut passer aux tests. nous n'avons pas à recréer les archives [personnes-dao.couche [service] On constate que les tests ont été passés avec succès avec l'implémentation [DaoImplCommon]. Les résultats obtenus sont les suivants : . Simplement l'archive [personnes-dao. </bean> 36.la classes d'accè à la couche [dao] --> 31.5 Tests de l’application [web] Pour tester l’application web avec le SGBD [Postgres]. nous construisons un projet Eclipse [mvc-personnes-04B] de façon analogue à celle utilisée pour construire le projet [mvc-personnes-03B] avec la base Firebird (cf page 227). <!-.personnes. <value>classpath:sql-map-config-postgres. 18. </bean> 30. </beans> • • lignes 5-19 : le bean [dataSource] désigne maintenant la base [Postgres] [dbpersonnes] dont l’administrateur est [postgres] avec le mot de passe [postgres].dao. <bean id="dao" class="istia. <property name="configLocation"> 27.

Nous déployons le projet web [mvc-personnes-04B] au sein de Tomcat : Nous sommes prêts pour les tests. nous demandons l’url [http://localhost:8080/mvc-personnes-04B] : Nous ajoutons une nouvelle personne avec le lien [Ajout] : Les bases du développement web MVC en Java. Le contenu de la table [PERSONNES] est alors le suivant : Tomcat est lancé. Avec un navigateur. par l'exemple 242/264 .

Elle contient une table [PERSONNES] : La table [PERSONNES] contiendra la liste des personnes gérée par l’application web.org]. `NOM` varchar(30) NOT NULL default ''.net/fr/products/mysql/manager]. suppression]. `DATENAISSANCE` date NOT NULL default '0000-00-00'. 8. `NBENFANTS` int(11) NOT NULL default '0'. `MARIE` tinyint(4) NOT NULL default '0'. MySQL 19. Dans ce qui suit.easyphp. 6. `ID` int(11) NOT NULL auto_increment. CREATE TABLE `personnes` ( 2. 7.x. nous allons installer la liste des personnes dans une table de base de données MySQL 4. Les bases du développement web MVC en Java. 3.1 La base de données MySQL Dans cette version. `PRENOM` varchar(30) NOT NULL default ''. par l'exemple 243/264 . Nous avons utilisé le paquetage [Apache – MySQL – PHP] disponible à l'url [http://www. 19 Application web MVC dans une architecture 3tier – Exemple 5.Nous vérifions l’ajout dans la base de données : Le lecteur est invité à faire d’autres tests [modification. 4. un client d’administration gratuit du SGBD MySQL.sqlmanager. les copies d’écran proviennent du client EMS MySQL Manager Lite [http://www. La base de données s’appelle [dbpersonnes]. `VERSION` int(11) NOT NULL default '0'. 5. Elle a été construite avec les ordres SQL suivants : 1.

cette valeur étant la clé primaire affectée à la nouvelle ligne insérée dans la table [PERSONNES]. Nous l’utiliserons dans le fichier [personnes-mysql.on exécute l'ordre SELECT ci. MySQL générera automatiquement un nombre entier pour cette colonne. ) ENGINE=InnoDB DEFAULT CHARSET=latin1 MySQL 4. La table [PERSONNES] pourrait avoir le contenu suivant : Nous savons que lors de l'insertion d'un objet [Personne] par notre couche [dao]. Si on insère une ligne sans valeur pour la colonne ID de la table.2 Le projet Eclipse des couches [dao] et [service] Pour développer les couches [dao] et [service] de notre application avec la base de données MySQL. .xml] qui rassemble les ordres SQL émis sur la base de données.on exécute l'ordre d'insertion ci-dessus (F12). • • ligne 10 : la table doit avoir le type [InnoDB] et non le type [MyISAM] qui ne supporte pas les transactions. Cela va nous éviter de générer les clés primaires nous-mêmes.vérification L’ordre SQL SELECT LAST_INSERT_ID() permet de connaître la dernière valeur insérée dans le champ ID de la table. Voyons sur un exemple comment nous allons pouvoir connaître cette valeur. ligne 2 : la clé primaire est de type auto_increment. Elle est à émettre après l'insertion. 19. PRIMARY KEY (`ID`) 10.le résultat .x semble plus pauvre que les deux SGBD précédents. On notera qu'on ne donne pas de valeur dessus pour connaître la dernière au champ ID -> valeur insérée dans le champ ID de la table -> . par l'exemple 244/264 . le champ [id] de cet objet est égal à -1 avant l'insertion et a une valeur différente de -1 ensuite.9. Je n'ai pas pu mettre de contraintes (checks) à la table. nous utiliserons le projet Eclipse [mvc-personnes-05] suivant : Les bases du développement web MVC en Java. C'est une différence avec les SGBD [Firebird] et [Postgres] où on demandait la valeur de la clé primaire de la personne ajoutée avant l'insertion.

8. `NBENFANTS` int(11) NOT NULL default '0'. `NOM` varchar(30) NOT NULL default ''. `DATENAISSANCE` date NOT NULL default '0000-00-00'. 31. 26. PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1. # # Data for the `personnes` table # (LIMIT 0. 32. Dossier [src] Ce dossier contient les codes source des couches [dao] et [service] ainsi que les fichiers de configuration de ces deux couches : Tous les fichiers ayant [mysql] dans leur nom ont pu subir ou non une modification vis à vis des versions Firebird et Postgres. 2. 33. par l'exemple 245/264 . # # Structure for the `personnes` table : # CREATE TABLE `personnes` ( `ID` int(11) NOT NULL auto_increment.0. 7. 19.Le projet est un simple projet Java. 15. Dossier [database] Ce dossier contient le script de création de la base de données MySQL des personnes : 1. `PRENOM` varchar(30) NOT NULL default ''. 18. 9. 10. 11. USE `dbpersonnes`. 20. 22. # # # # # EMS MySQL Manager Lite 3. 17. 30. Dans ce qui suit. CREATE DATABASE `dbpersonnes` CHARACTER SET 'latin1' COLLATE 'latin1_swedish_ci'.500) Les bases du développement web MVC en Java.1 --------------------------------------Host : localhost Port : 3306 Database : dbpersonnes SET FOREIGN_KEY_CHECKS=0. 24. 27.2. 34. 5. 4. 21. 13. 6. 14. 29. 16. 3. `VERSION` int(11) NOT NULL default '0'. 23. `MARIE` tinyint(4) NOT NULL default '0'. 28. pas un projet web Tomcat. 12. 25. nous décrivons ceux qui ont été modifiés.

38. 7.mapping table [PERSONNES] .objet [Personne] --> 12. (3.xml] est le suivant : 1.0" encoding="UTF-8" ?> 2.'1986-01-01'.3 La couche [dao] La couche [dao] est la suivante : Nous ne présentons que ce qui change vis à vis de la version [Firebird].mvc. <sqlMap> 8.'Charles'. COMMIT.35. <!-.com//DTD SQL Map 2. (1. type="istia.1. `DATENAISSANCE`. `VERSION`.1. <typeAlias alias="Personne. 3. par l'exemple 246/264 . Dossier [lib] Ce dossier contient les archives nécessaires à l’application : On notera la présence du pilote jdbc du SGBD MySQL.1.'Mélanie'. `NOM`.dtd"> 6. PUBLIC "-//iBATIS. `PRENOM`. <resultMap id="Personne.0//EN" 5.'Joachim'. 19.'Lemarchand'. Le fichier de mapping [personne-mysql.1).entites.'1985-01-12'. 37. INSERT INTO `personnes` (`ID`.personnes.classe" 10.ibatis.2). `MARIE`. "http://www. `NBENFANTS`) VALUES 36.'1984-01-13'.Personne"/> 11. <!DOCTYPE sqlMap 4.0.map" Les bases du développement web MVC en Java. 40.0).1.'Humbort'.alias classe [Personne] --> 9.'Major'.st. Toutes ces archives font partie du Classpath du projet Eclipse. (2.com/dtd/sql-map-2. <!-. 39. <?xml version="1.0.

PERSONNES(VERSION.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 45. VERSION. </sqlMap> C’est le même contenu que [personnes-firebird. par l'exemple 247/264 . PRENOM. Si ce n'était pas le cas. <!-. il faudrait alors résoudre le problème à la fois avec des transactions et un niveau d'étanchéité adéquat (isolation level) entre transactions. </resultMap> 22. <result property="prenom" column="PRENOM"/> 18. insertion I2 de Th2 3. <result property="nom" column="NOM"/> 17. VERSION=#version#</update> 43. NBENFANTS FROM PERSONNES</select> 25.getNextId" resultClass="int">select 48.classe"> update 40. 3. MARIE. DATENAISSANCE=#dateNaissance#. VALUES(#version#.obtenir une personne en particulier --> 26. #dateNaissance#. Si on a par exemple la séquence : 1. <selectKey keyProperty="id"> 35. <result property="id" column="ID" /> 15. Il y a au total quatre ordres SQL à émettre. Imaginons deux threads Th1 et Th2 qui font une insertion en même temps. Si ci-dessus.obtenir la valeur de la clé primaire [id] de la dernière personne insérée --> 47. insert into 31. NBENFANTS=#nbEnfants# WHERE ID=#id# and 42. select LAST_INSERT_ID() as value 36. Nous allons supposer qu'elle le gère proprement. #nbEnfants#) 34. <result property="version" column="VERSION" /> 16.xml] aux détails près suivants : • l’ordre SQL " Personne. MARIE. NBENFANTS) 32.Personne"> 14. DATENAISSANCE. #prenom#. </insert> 38.supprimer une personne --> 44. <select id="Personne.map" >select ID. <!-. On notera que ce problème n'existe pas avec les SGBD Firebird et Postgres qui eux font le SELECT avant l'INSERT. <!-. Th1 récupère la clé primaire générée lors de la dernière insertion. select S2 de Th2 En 3.liste de toutes les personnes --> 23.classe"> 30.entites. <!-. class="istia. 24. 41. Ceci ne résoudrait le problème que pour les threads de notre application. Th1 et Th2 sont des threads de deux applications différentes.mvc. PRENOM=#prenom#. <delete id="Personne. PRENOM. MARIE. donc celle de Th2 et non la sienne. <insert id="Personne. <select id="Personne.getAll" resultMap="Personne. Supposons qu'ils soient faits dans l'ordre suivant : 1. #nom#. <!-. select S1 de Th1 4. <result property="dateNaissance" column="DATENAISSANCE"/> 19. PERSONNES set VERSION=#version#+1. <update id="Personne.mettre à jour une personne --> 39. select S1 de Th1 select S2 de Th2 insertion I1 de Th1 insertion I2 de Th2 Les bases du développement web MVC en Java. NOM=#nom#. <!-. Le niveau [serializable] dans lequel les transactions sont exécutées comme si elles s'exécutaient séquentiellement serait approprié. PRENOM. 4. ID=#value# </delete> 46. DATENAISSANCE.ajouter une personne --> 29.insertOne " a changé lignes 29-37 : • l'ordre SQL d'insertion est exécuté avant l'ordre SELECT qui va permettre de récupérer la valeur de la clé primaire de la ligne insérée • l'ordre SQL d'insertion n'a pas de valeur pour la colonne ID de la table [PERSONNES] Cela reflète l'exemple d'insertion que nous avons commenté page 244. 33.updateOne" parameterClass="Personne. 2. NOM. insertion I1 de Th1 2.personnes.13.getOne" resultMap="Personne. MARIE=#marie#. NBENFANTS FROM PERSONNES WHERE ID=#value#</select> 28.map" > select ID. NOM. <result property="marie" column="MARIE"/> 20. NOM. 27. VERSION. DATENAISSANCE.st. <result property="nbEnfants" column="NBENFANTS"/> 21. <select id="Personne. il nous faudrait dériver la classe d’implémentation [DaoImplCommon] de la couche [dao] en une classe [DaoImplMySQL] où la méthode [insertPersonne] serait synchronisée. Je ne sais pas si la méthode [insert] d'iBATIS est protégée pour ce cas de figure. #marie#.insertOne" parameterClass="Personne. On notera qu'il y a peut-être là une source possible de problèmes entre threads concurrents. </selectKey> 37. LAST_INSERT_ID()</select> 49.

C'est une erreur tout à fait récupérable et Th2 peut retenter l'insertion.dbcp.SqlMapCllient --> 21. <property name="username"> 14. </property> 29. </bean> 20.personnes. <!-.commons.springframework.ibatis.SqlMapClientFactoryBean"> 23.apache. destroy-method="close"> 7. </property> 26. <!DOCTYPE beans SYSTEM "http://www. <property name="dataSource"> 24. </property> 13. et Th1 et Th2 vont récupérer deux valeurs différentes. <property name="url"> 11. <!-. <bean id="dao" class="istia. Les résultats obtenus sont les suivants : Les bases du développement web MVC en Java. par l'exemple 248/264 .la source de donnéees DBCP --> 5. <bean id="dataSource" class="org. On va laisser l'opération " Personne.org/dtd/spring-beans. <value>root</value> 15. 19. Si l'opération n'était pas atomique. La classe d’implémentation [DaoImplCommon] de la couche [dao] est celle des deux versions précédentes. <value>classpath:sql-map-config-mysql.</beans> • • lignes 5-19 : le bean [dataSource] désigne maintenant la base [MySQL] [dbpersonnes] dont l’administrateur est [root] sans mot de passe. <bean id="sqlMapClient" 22.dtd"> 3. Cette opération est normalement atomique. le fichier de configuration [spring-config-testdao-mysql. </property> 35.Aux étapes 1 et 2. La configuration de la couche [dao] a été adaptée au SGBD [MySQL]. Th1 et Th2 récupèrent des valeurs de clé primaire auprès du même générateur.Driver</value> 9. <property name="password"> 17. </property> 19. <value>jdbc:mysql://localhost/dbpersonnes</value> 12.xml] est-il le suivant : 1.xml] mais le lecteur doit avoir conscience qu'il y a là potentiellement un problème.insertOne " telle qu'elle est actuellement dans le fichier [personnes-mysql. et que Th1 et Th2 récupéraient deux valeurs identiques.xml</value> 28. <?xml version="1. class="org. <ref local="sqlMapClient"/> 34.dao. on peut passer aux tests.0" encoding="ISO_8859-1"?> 2.orm.BasicDataSource" 6. <property name="sqlMapClient"> 33.mysql. Le lecteur modifiera cette configuration selon son propre environnement. l'insertion faite en 4 par Th2 échouerait pour cause de doublon de clé primaire.springframework. <ref local="dataSource"/> 25. Ainsi. ligne 31 : la classe [DaoImplCommon] est la classe d'implémentation de la couche [dao] Ces modifications faites. <beans> 4. </property> 16. </bean> 36. <!-. <value></value> 18.DaoImplCommon"> 32. </property> 10. <property name="driverClassName"> 8.mvc. <property name="configLocation"> 27. <value>com. </bean> 30.4 Les tests des couches [dao] et [service] Les tests des couches [dao] et [service] sont les mêmes que pour la version [Firebird].la classes d'accè à la couche [dao] --> 31.jdbc.st.

jar] et [personnes-service. Cependant. 19. comme avec Postgres.couche [service] On constate que les tests ont été passés avec succès avec l'implémentation [DaoImplCommon]. nous demandons l’url [http://localhost:8080/mvc-personnes-05B] : Nous ajoutons une nouvelle personne avec le lien [Ajout] : Les bases du développement web MVC en Java. Nous n'aurons pas à dériver cette classe comme il avait été nécessaire de le faire avec le SGBD [Firebird].5 Tests de l’application [web] Pour tester l’application web avec le SGBD [MySQL]. nous construisons un projet Eclipse [mvc-personnes-05B] de façon analogue à celle utilisée pour construire le projet [mvc-personnes-03B] avec la base Firebird (cf page 227). Nous déployons le projet web [mvc-personnes-05B] au sein de Tomcat : Le SGBD MySQL est lancé.couche [dao] .jar] puisque nous n'avons modifié aucune classe. par l'exemple 249/264 . Le contenu de la table [PERSONNES] est alors le suivant : Tomcat est lancé à son tour.. nous n'avons pas à recréer les archives [personnes-dao. Avec un navigateur.

sqlmanager. SQL Server Express 20. nous allons installer la liste des personnes dans une table de base de données SQL Server Express 2005 disponible à l'url [http://msdn. par l'exemple 250/264 . un client d’administration gratuit du SGBD SQL Server Express. Dans ce qui suit.microsoft. La base de données s’appelle [dbpersonnes].1 La base de données SQL Server Express Dans cette version.Nous vérifions l’ajout dans la base de données : Le lecteur est invité à faire d’autres tests [modification.com/vstudio/express/sql/]. les copies d’écran proviennent du client EMS Manager Lite pour SQL Server Express [http://www. 20 Application web MVC dans une architecture 3tier – Exemple 6. suppression]. Elle contient une table [PERSONNES] : Les bases du développement web MVC en Java.net/fr/products/mssql/manager].

3. 10. l'incrément utilisé dans la génération des nombres. 11. CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0)) ) ON [PRIMARY] GO • ligne 2 : la clé primaire [ID] est de type entier. Elle a été construite avec les ordres SQL suivants : 1.[PERSONNES] ( [ID] int IDENTITY(1. 14. 13. L'attribut IDENTITY indique que si on insère une ligne sans valeur pour la colonne ID de la table. Voyons sur un exemple comment nous allons pouvoir connaître cette valeur. [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL. 12. 2. [NOM] varchar(30) COLLATE French_CI_AS NOT NULL. 7. CREATE TABLE [dbo]. 6. 5. 1) NOT NULL. 15. CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>''). 8. Les bases du développement web MVC en Java. cette valeur étant la clé primaire affectée à la nouvelle ligne insérée dans la table [PERSONNES]. La table [PERSONNES] pourrait avoir le contenu suivant : Nous savons que lors de l'insertion d'un objet [Personne] par notre couche [dao]. le champ [id] de cet objet est égal à -1 avant l'insertion et a une valeur différente de -1 ensuite. par l'exemple 251/264 . PRIMARY KEY CLUSTERED ([ID]). [MARIE] tinyint NOT NULL.La table [PERSONNES] contiendra la liste des personnes gérée par l’application web. 1). [VERSION] int NOT NULL. [DATENAISSANCE] datetime NOT NULL. le premier paramètre est la première valeur possible pour la clé primaire. Dans IDENTITY(1. 4. [NBENFANTS] tinyint NOT NULL. 9. CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''). SQL Express générera lui-même un nombre entier pour cette colonne. le second.

pas un projet web Tomcat. mais c'est analogue à la génération de clé primaire du SGBD MySQL.xml] qui rassemble les ordres SQL émis sur la base de données.2 Le projet Eclipse des couches [dao] et [service] Pour développer les couches [dao] et [service] de notre application avec la base de données[SQL Server Express].le résultat .on exécute l'ordre SELECT cidessus pour connaître la dernière valeur insérée dans le champ ID de la . C'est une différence avec les SGBD [Firebird] et [Postgres] où on demandait la valeur de la clé primaire de la personne ajoutée avant l'insertion. 20. Elle est à émettre après l'insertion.vérification L’ordre SQL SELECT @@IDENTITY permet de connaître la dernière valeur insérée dans le champ ID de la table.. nous utiliserons le projet Eclipse [mvc-personnes-06] suivant : Le projet est un simple projet Java.on exécute l'ordre d'insertion ci-dessus (F12). Dossier [src] Ce dossier contient les codes source des couches [dao] et [service] : Les bases du développement web MVC en Java. par l'exemple 252/264 . Nous l’utiliserons dans le fichier [personnessqlexpress. On notera qu'on ne donne pas de valeur table -> au champ ID -> .

[PERSONNES] ([ID]. SET IDENTITY_INSERT [dbo]. 8. 17. [VERSION] int NOT NULL. 0) 46. 14. [DATENAISSANCE]. [NOM].500) SET IDENTITY_INSERT [dbo]. [DATENAISSANCE] datetime NOT NULL.Structure for table PERSONNES : -CREATE TABLE [dbo]. 28. 13. 55.1) --------------------------------------Host : (local)\SQLEXPRESS Database : dbpersonnes --. 12. 2) 36. Dans ce qui suit. 32. 21. 60. 1. [NBENFANTS]) 44. 31.[PERSONNES] ([ID]. 38. IGNORE_DUP_KEY = OFF. -54. ----- SQL Manager 2005 Lite for SQL Server (2. 62. 51. [VERSION].[PERSONNES] ON GO INSERT INTO [dbo]. 'Humbort'. '19541113'. 0. 43. 2. VALUES 45. 61. [NBENFANTS] tinyint NOT NULL. 5. 24. 7. 26. 10.2. ALLOW_PAGE_LOCKS = ON) 63. [MARIE] tinyint NOT NULL. 48. [MARIE].[PERSONNES] 56. CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''). 59. [PRENOM]. [VERSION]. WITH ( 58. GO 47. 15. ALTER TABLE [dbo]. 1. 9. ALLOW_ROW_LOCKS = ON. [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL. 1) NOT NULL. ADD PRIMARY KEY CLUSTERED ([ID]) 57.0. VALUES 35. [NOM] varchar(30) COLLATE French_CI_AS NOT NULL. PAD_INDEX = OFF. 3.Definition for indices : 53. 'Charles'. 'Mélanie'. ON [PRIMARY] 64. -. 6. [PRENOM]. GO Dossier [lib] Ce dossier contient les archives nécessaires à l’application : Les bases du développement web MVC en Java. 30. Postgres et MySQL. 19. INSERT INTO [dbo]. '19850212'. 0. 11. 'Major'. [MARIE].Tous les fichiers ayant [sqlexpress] dans leur nom ont pu subir ou non une modification vis à vis des versions Firebird. Dossier [database] Ce dossier contient le script de création de la base de données SQL Express des personnes : 1. GO 50. GO 42. 1. GO 37. CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0)). [DATENAISSANCE].[PERSONNES] ( [ID] int IDENTITY(1. [NBENFANTS]) 39. [MARIE]. 23. 29. (2. 4. (1. 'Lemarchand'. CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>'') ) ON [PRIMARY] GO --. STATISTICS_NORECOMPUTE = OFF.[PERSONNES] ([ID]. 'Joachim'. 20. INSERT INTO [dbo]. 33. (3. [PRENOM]. -52. par l'exemple 253/264 . nous ne décrivons que ceux qui ont été modifiés. [NBENFANTS]) 34. [VERSION]. VALUES 40. 25. [NOM]. '19860301'. 18. [DATENAISSANCE]. 1) 41. 16. 22.Data for table PERSONNES -(LIMIT 0.[PERSONNES] OFF 49. [NOM]. 27. 1.

Les bases du développement web MVC en Java. 3.st.objet [Personne] --> 12.jar] du SGBD [Sql Server Express]. 20.alias classe [Personne] --> 9. <!-. class="istia. <!-. <result property="nom" column="NOM"/> 17.0//EN" 5. <result property="prenom" column="PRENOM"/> 18. NOM. <resultMap id="Personne. "http://www. VERSION.map" 13. <typeAlias alias="Personne.personnes.ibatis.classe" 10. Le fichier de mapping [personne-sqlexpress.map" > select ID.Personne"/> 11. <?xml version="1. </resultMap> 22. <result property="version" column="VERSION" /> 16.personnes.0" encoding="UTF-8" ?> 2.mvc. <select id="Personne. <result property="dateNaissance" column="DATENAISSANCE"/> 19. <result property="nbEnfants" column="NBENFANTS"/> 21. Toutes ces archives font partie du Classpath du projet Eclipse.com//DTD SQL Map 2.mvc.Personne"> 14.getAll" resultMap="Personne. <result property="marie" column="MARIE"/> 20.com/dtd/sql-map-2. par l'exemple 254/264 .st. <result property="id" column="ID" /> 15.mapping table [PERSONNES] .3 La couche [dao] La couche [dao] est la suivante : Nous ne présentons que ce qui change vis à vis de la version [Firebird]. type="istia. <!DOCTYPE sqlMap 4.liste de toutes les personnes --> 23. <sqlMap> 8.dtd"> 6. PUBLIC "-//iBATIS.On notera la présence du pilote jdbc [sqljdbc. 7.xml] est le suivant : 1. <!-.entites.entites.

SqlMapCllient --> <bean id="sqlMapClient" class="org. 13. 2. #nbEnfants#) 34. ID=#value# </delete> 46.mvc.org/dtd/spring-beans. Ainsi. <!-.ibatis.classe"> update 40.supprimer une personne --> 44. MARIE.24. <!-. </insert> 38. #marie#. MARIE=#marie#. 6. 34. 8. select @@IDENTITY as value 36.obtenir la valeur de la clé primaire [id] de la dernière personne insérée --> 47. <delete id="Personne. DATENAISSANCE=#dateNaissance#. <update id="Personne.dao. DATENAISSANCE. LAST_INSERT_ID()</select> 49. le fichier de configuration [spring-configtest-dao-sqlexpress. MARIE. <?xml version="1. 16. 27.apache. 30. 11.orm. 23.xml] aux détails près suivants : • l’ordre SQL " Personne. 31. NOM. 7. NOM.SQLServerDriver</value> </property> <property name="url"> <value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000.la source de donnéees DBCP --> <bean id="dataSource" class="org. 10.updateOne" parameterClass="Personne. 35. 25. PRENOM. </selectKey> 37. <!-. PRENOM=#prenom#. qu'on retrouve ici le problème d'insertions simultanées par des threads différents décrit pour MySQL au paragraphe 19. </sqlMap> C’est le même contenu que [personnes-firebird.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 45.0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www.getNextId" resultClass="int">select 48. PRENOM. 29. 14. VALUES(#version#. 33. 4. 15.st. 24.databaseName=dbpersonnes</value> </property> <property name="username"> <value>sa</value> </property> <property name="password"> <value>msde</value> </property> </bean> <!-. par l'exemple 255/264 .xml</value> </property> </bean> <!-. NOM=#nom#. 27. <select id="Personne. DATENAISSANCE. La classe d’implémentation [DaoImplCommon] de la couche [dao] est celle des trois versions précédentes.ajouter une personne --> 29. VERSION. 41.map" >select ID. 17. 33. 36.obtenir une personne en particulier --> 26. <insert id="Personne.commons. PERSONNES(VERSION.sqlserver. <!-. PERSONNES set VERSION=#version#+1. <selectKey keyProperty="id"> 35. page 247. 32.springframework. 9. 3. La configuration de la couche [dao] a été adaptée au SGBD [SQL Express]. MARIE.personnes. #prenom#. #nom#. DATENAISSANCE.microsoft.DaoImplCommon"> <property name="sqlMapClient"> <ref local="sqlMapClient"/> </property> </bean> </beans> Les bases du développement web MVC en Java.xml] est-il le suivant : 1.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>com. 21. NBENFANTS FROM PERSONNES</select> 25. 22. <!-. A noter. 26.dtd"> <beans> <!-.3. <select id="Personne. VERSION=#version#</update> 43.springframework. NBENFANTS FROM PERSONNES WHERE ID=#value#</select> 28.insertOne " a changé lignes 29-37 : • l'ordre SQL d'insertion est exécuté avant l'ordre SELECT qui va permettre de récupérer la valeur de la clé primaire de la ligne insérée • l'ordre SQL d'insertion n'a pas de valeur pour la colonne ID de la table [PERSONNES] Cela reflète l'exemple d'insertion que nous avons commenté page 251.dbcp.insertOne" parameterClass="Personne.mettre à jour une personne --> 39. 28. 19.getOne" resultMap="Personne.jdbc. 5. 12.la classes d'accè à la couche [dao] --> <bean id="dao" class="istia. PRENOM.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-sqlexpress. #dateNaissance#. 18.classe"> 30. NBENFANTS=#nbEnfants# WHERE ID=#id# and 42. insert into 31. 20. NBENFANTS) 32.

Ce nom peut être obtenu grâce [SQL Server Configuration Manager] installé normalement en même temps que SQL Express : • 4000 : port d'écoute de SQL Express. nous avons travaillé avec un port fixe. Par défaut. Le lecteur modifiera cette configuration selon son propre environnement. Ici. C'est celle qui a été créée avec le client EMS : Les bases du développement web MVC en Java. Cela est dépendant de la configuration du serveur. il travaille avec des ports dynamiques. Il semble donc logique de nommer l'instance à laquelle on s'adresse. Cela s'obtient par configuration : . donc pas connus à l'avance.on laisse les champs [TCP Dynamic Ports] vides .on met les champs [TCP Port] à 4000 • l'attribut dataBaseName fixe la base de données avec laquelle on veut travailler. ligne 31 : la classe [DaoImplCommon] est la classe d'implémentation de la couche [dao] La ligne 11 mérite des explications : <value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000. par l'exemple 256/264 . le port 4000. On ne précise alors pas de port dans l'url JDBC.[clic droit sur TCP/IP -> Propriétés] -> .databaseName=dbpersonnes</value> • • //localhost : indique que le serveur SQL Express est sur la même machine que notre application Java \\SQLEXPRESS : est le nom d'une instance de SQL Server. Il semble que plusieurs instances peuvent s'exécuter en même temps.• • lignes 5-19 : le bean [dataSource] désigne maintenant la base [SQL Express] [dbpersonnes] dont l’administrateur est [sa] avec le mot de passe [msde].

par l'exemple 257/264 . Avec un navigateur. Nous n'aurons pas à dériver cette classe comme il avait été nécessaire de le faire avec le SGBD [Firebird].couche [dao] . 20. Les résultats obtenus sont les suivants : . nous demandons l’url [http://localhost:8080/mvc-personnes-06B] : Les bases du développement web MVC en Java. nous construisons un projet Eclipse [mvc-personnes-06B] de façon analogue à celle utilisée pour construire les projets web précédents.couche [service] On constate que les tests ont été passés avec succès avec l'implémentation [DaoImplCommon]. Le contenu de la table [PERSONNES] est alors le suivant : Tomcat est lancé à son tour.4 Les tests des couches [dao] et [service] Les tests des couches [dao] et [service] sont les mêmes que pour la version [Firebird].Ces modifications faites. on peut passer aux tests.5 Tests de l’application [web] Pour tester l’application web avec le SGBD [SQL Server Express]. Nous déployons le projet web [mvc-personnes-05B] au sein de Tomcat : Le SGBD SQL server Express est lancé. 20.

Il est également prêt à aborder d'autres méthodes de développement proches de celles étudiées dans ce document. suppression]. par l'exemple 258/264 . Rappelons l'architecture des applications web développées ici : Les bases du développement web MVC en Java. 21 Conclusion Rappelons ce qui a été présenté dans ce document : • • • • • les bases de la programmation web en Java avec les servlets et les pages JSP une introduction à l'architecture MVC une introduction à l'architecture 3tier une introduction à Spring IoC des exemples pour illustrer ces points Nous pensons que le lecteur arrivé jusqu'ici est prêt pour développer lui-même ses propres applications web en Java.Nous ajoutons une nouvelle personne avec le lien [Ajout] : Nous vérifions l’ajout dans la base de données : Le lecteur est invité à faire d’autres tests [modification.

par l'exemple 259/264 . Il est utile même dans des applications situées en-dehors du web. couche [metier] Modèles couche [dao] Données Application web couche [web] Application Utilisateur LIST EDIT ERREURS Exception Modèles couche [service] couche [dao] Données Spring IoC Dans la version 1 de l'application. Spring ne s'arrête pas au seul concept MVC de la couche [web] d'une application 3tier. ne diffèrent en fait que par le contenu de ces méthodes [doAction] La tentation est alors grande de : • factoriser le traitement (1) dans une servlet générique ignorante de l'application qui l'utilise • déléguer le traitement (2) à des classes externes puisque la servlet générique ne sait pas dans quelle application elle est utilisée • faire le lien entre l'action demandée par l'utilisateur et la classe qui doit la traiter à l'aide d'un fichier de configuration Des outils. MySQL et SQL Server Express.com/java/struts/).springframework.org/) offre des facilités analogues à celles de Struts. metier. Lorsqu'on a écrit plusieurs applications de ce type. Jakarta Struts est un projet de l'Apache Software Foundation (www. Apparu plus récemment. Ainsi. Nous avons ainsi montré qu'on pouvait construire des architectures ntier avec des couches indépendantes.apache. Son utilisation a été décrite dans plusieurs articles (http://tahe. Grâce à Spring IoC. le framework Spring (http://www. C'est en réalité son module Spring MVC.org).org/).developpez. la liste des personnes est maintenue dans une table de base de données. la liste des personnes était maintenue en mémoire et disparaissait au déchargement de l’application web. Nous avons utilisé quatre SGBD différents : Firebird. sont apparus pour apporter les facilités précédentes aux développeurs. on s'aperçoit que les servlets de deux applications différentes : 1.com/java/springmvc-part1/). Ce framework est décrit dans (http://tahe. la couche [web] de la version 1 a pu être gardée intégralement dans les versions suivantes. cette architecture est suffisante.apache.developpez.Application web couche [web] Servlet Utilisateur JSP1 JSP2 JSPn Pour des applications simples. Les bases du développement web MVC en Java. dao] sur un exemple basique de gestion d’une liste de personnes. souvent appelés " frameworks ". Postgres. Le plus ancien et probablement le plus connu d'entre-eux est Struts (http://struts. ont le même mécanisme pour déterminer quelle méthode [doAction] il faut exécuter pour traiter l'action demandée par l'utilisateur 2. nous avons terminé notre cours en mettant en oeuvre une architecture MVC dans une architecture 3tier [web. Dans les autres versions.

l'url de la base de données à exploiter 3. Les dossiers [lib] des différents projets ont été eux vidés afin de diminuer la taille du zip. Les informations 2 à 4 sont dépendantes de l'environnement utilisé pour les tests. sur le site de l'article. Les bases du développement web MVC en Java. Enfin. Grâce à l'intégration de Spring avec iBATIS. . sous la forme d'un fichier zippé. nous avons montré comment Spring nous permettait de gérer les transactions de façon déclarative au niveau de la couche [service]. le lecteur devra adapter la configuration du [DataSource] trouvé dans les différents fichiers de configuration à son propre environnement. nous avons pu construire quatre versions qui ne diffèrent que par leurs fichiers de configuration. La même classe [DaoImplCommon] a été utilisée pour implémenter la couche [dao] dans les quatre versions. [service] et [web] de l'application construite par les projets associés [mvc-personnes-0X] : Pour rejouer les exemples avec Base de données. Il faudra donc aller chercher les archives nécessaires à un projet. Dans les dossiers [lib] des projets web [mvc-personnes-0XB]. par l'exemple 260/264 . nous avons été amenés à dériver cette classe mais pas à la modifier. le mot de passe de ce dernier. Cet objet renseigne : 1.contenu du zip .contenu du dossier [lib] Le dossier [lib] rassemble les archives utilisées par les différents projets étudiés. 22 Le code de l'article Le lecteur trouvera le code des exemples de ce document. le nom de classe du pilote JDBC du SGBD utilisé 2. Pour gérer un problème spécifique au SGBD Firebird. dans le dossier [lib] ci-dessus.Avec les versions utilisant une base de données. Le lecteur est encouragé à découvrir la totalité des fonctionnalités offertes par ce produit. nous avons montré l'apport de Spring pour la construction des couches [dao] et [service]. ont été laissées les archives des couches [dao]. le propriétaire des connexions qui sont ouvertes sur la base 4.

...........3DÉPLOIEMENT D'UNE APPLICATION WEB AU SEIN DU SERVEUR TOMCAT................................................................................................................................................44 3........ 9 2.......................................................................................................................................................................................................................5LA MÉTHODE [DOVALIDATIONFORMULAIRE].......................... par l'exemple 261/264 ................................................................14 2............................................................................2ARCHITECTURE DE L'APPLICATION...........................81 6...................6CRÉATION D'UNE SERVLET....................................... 2 2LES OUTILS UTILISÉS DANS LE DOCUMENT...................................................................2CLASSPATH D'UN PROJET ECLIPSE.................................................................................................................................................................................................................................5........................................49 3....1LES VUES DE L'APPLICATION.........................38 3........................................................................ 3 2.......2INITIALISATION DU CONTRÔLEUR.............................................................................1LA VUE [FORMULAIRE]..........................................................7..........76 6....................... 29 3.......................................................71 5............................. 84 6........................ 24 3LES BASES DU DÉVELOPPEMENT WEB EN JAVA...............................................................6LE CONTRÔLEUR [SERVLETPERSONNE]....................7.............................1JAVA 1.............3CONFIGURATION DE L'APPLICATION WEB [PERSONNE2].................................................3LA VUE [ERREURS].................................................................. 66 5.................................................................................................................................................................7...........................................................................5TEST DE LA SERVLET............................... 41 3.......................3LE PROJET ECLIPSE.........................................................................................................................2LE PROJET ECLIPSE.ECLIPSE...........................................................................................................................................................................3.............................................................2LA PAGE JSP [FORMULAIRE2.....................7....................73 5.................................................................. 29 3................................................5............................................................................................................................................................................ 81 6.............................................................................................................................................................36 3................................................. 10 2................6..................................3..................................6........5CRÉATION D'UNE PAGE JSP.................................... 19 2......................................................................5..............................................................................................................................2LE CONTENEUR DE SERVLETS TOMCAT 5........................................4LA MÉTHODE [DOINIT].....4.......................................................3LA MÉTHODE [DOGET]............................ 56 3........................................................61 5..................................................4...9 2..................................6... 33 3.............................................................3.....................................................................................................2LA VUE [REPONSE].............................................3LA VUE [ERREURS]...............................................................................................................................................................42 3.........................3CONFIGURATION DE L'APPLICATION...............................................................................3GESTION DES APPLICATIONS WEB DÉPLOYÉES....................................................................................................... 78 6..................34 3......5TESTS DES VUES.............................................................. 57 4DÉVELOPPEMENT MVC (MODÈLE – VUE – CONTRÔLEUR)..........4LE CODE DES VUES...........................................................................................7...................................................3TEST DE LA PAGE D'ACCUEIL..................................................................................................... 82 6....................................1CRÉATION DE LA SERVLET............3 2................................3..............................2CRÉATION D'UNE PAGE D'ACCUEIL....................................................5....................................................................................................................... 76 6............50 3..............................................Table des matières 1INTRODUCTION........................................6...................................................................................................66 5.....................................................................................................................................1CRÉATION D'UN PROJET WEB SOUS ECLIPSE...........................................................................................................47 3....................................................................................................................................... 75 6APPLICATION WEB MVC [PERSONNE] – VERSION 2.................................2ADMINISTRATION DE TOMCAT.................1INTRODUCTION............................................................................... 71 5.............JSP]............................................................................................ 83 6.......................................................................................7........................................................7..................................................................................................... 46 3....................................................................86 Les bases du développement web MVC en Java................................4APPLICATION WEB AVEC PAGE D'ACCUEIL...............................................................6RECHARGEMENT AUTOMATIQUE DU CONTEXTE DE L'APPLICATION WEB............................................................................................................................................5INTÉGRATION TOMCAT ......................................................................6....................................................................................................................................53 3..............................................2LA VUE [REPONSE].................... 17 2.................................................................................................6TESTS DES VUES.................................................................................................................................................................... 69 5................... 68 5...............................................................................................................................................................................................................................................................................................7LE CONTRÔLEUR [SERVLETPERSONNE].............. 58 5APPLICATION WEB MVC [PERSONNE] – VERSION 1...........................................................................................................................................................................................1LA VUE [FORMULAIRE].................................................................4CRÉATION D'UN FORMULAIRE HTML............................................................. 74 5.....3CONFIGURATION DE LA SERVLET...............................................................7.............................................5LE CODE DES VUES................................................54 3.........................................................................................................1LA SERVLET [SERVLETFORMULAIRE2]........................................................................................4LE CODE DE LA SERVLET [SERVLETFORMULAIRE]............................................65 5...............................................72 5.........4INSTALLATION D'ECLIPSE.........................7COOPÉRATION SERVLET ET PAGES JSP........................................4 2................................4CONFIGURATION DE L'APPLICATION WEB [PERSONNE1]..............1SQUELETTE DU CONTRÔLEUR...... 63 5........................................................................... 60 5.......................................................................................................................................................................................................1DÉPLOIEMENT....80 6................................................4..................................75 5..............................................8TESTS.. 60 5......................................................................6................................................................................................................ 68 5.

......................................................2LE PROJET ECLIPSE.............................4LE CODE DES VUES.......................................................................................................................................................................................................2LE PROJET ECLIPSE......................................1INTRODUCTION..................................................................5LA MÉTHODE [DOVALIDATIONFORMULAIRE]........................2INITIALISATION DU CONTRÔLEUR [INIT]................. 88 6..........................................................................................................................................................1INTRODUCTION..........................................................................123 11.........................1LA MÉTHODE [DOGET]............................................................108 9...................................................................................... 89 6....................................6..... 114 10APPLICATION WEB MVC [PERSONNE] – VERSION 5.....................................................................................................................................3...............................................................................................................................................................103 8.................................................123 11........................................ 100 7................................... 101 8.............................................................119 10.................................................................................5LE CONTRÔLEUR [SERVLETPERSONNE]................................. 91 7........................................................ 122 11.....5....................................................................7TESTS.....................................................................1LA VUE [FORMULAIRE]....................................4LA MÉTHODE [DOINIT]................... 96 7.......................................................6LE CONTRÔLEUR [SERVLETPERSONNE].................................................................4LA MÉTHODE [DORETOURFORMULAIRE]........................................................................2LA MÉTHODE [DOVALIDATIONFORMULAIRE]..................................................................................................................3LA MÉTHODE [DOGET].............................................................................3LA MÉTHODE [DORETOURFORMULAIRE].....119 10.....6LA MÉTHODE [DORETOURFORMULAIRE]...... 90 7................1...................121 11..........................................................112 9........................................... 120 10......................................................5...............1LE PROJET ECLIPSE...4LE CODE DES VUES..........................................................................................................................................................................................................................................................................................................................................86 6................................................................. par l'exemple 262/264 ...................................................... 123 12............................................................................................................................6............ 95 7............................................................................................................................................................1SQUELETTE DU CONTRÔLEUR........................................2CONFIGURATION DE L'APPLICATION WEB [PERSONNE4]....................................................123 11.............................................................................................................................................................. 97 7....................3LE CODE DES VUES.....................6........................................................................................................ 101 8.............................5TESTS DES VUES......6...................................................................................................................................2LA MÉTHODE [DOVALIDATIONFORMULAIRE]............................................117 10.....................................................6..............................5LE CONTRÔLEUR [SERVLETPERSONNE].......3LA VUE [ERREURS]................................................................... 113 9.........................................................................................................................6TESTS.................................................................2INSTALLER ET DÉCOUVRIR LA BIBLIOTHÈQUE JSTL................................106 9APPLICATION WEB MVC [PERSONNE] – VERSION 4...........................3CONFIGURATION DE L'APPLICATION WEB [PERSONNE6]......................................4.....................................................................................121 11............................1..................................................................................................................................... 111 9........................ 121 11APPLICATION WEB MVC [PERSONNE] – VERSION 6...................1INTRODUCTION............3UTILISER JSTL DANS UNE APPLICATION WEB.................................................................5...........................................................................................................................93 7..................................................................................................................100 7.................................................................................................. 114 10....................................................... 100 7.....................................................................................................................................................................................................................114 10........................................................................................1LA MÉTHODE [DOGET]..........................................................................................2LA MÉTHODE [DOVALIDATIONFORMULAIRE]..................2LA VUE [REPONSE]......6TESTS......................................................................................107 9............................................................... 107 9....................6..............................................................................................................................3LA MÉTHODE [DOINIT]....................................................................................6...............................................................................3CONFIGURATION DE L'APPLICATION WEB [PERSONNE3].......................................................................................... 109 9....................................................................................................................5LE CONTRÔLEUR [SERVLETPERSONNE].................................................................................................2LA VUE [REPONSE]...................................5....................................................................................98 7.....................................6.......................................... 123 12APPLICATION WEB MVC [PERSONNE] – VERSION 7........1INTRODUCTION........................119 10....................................................................87 6................................ 86 6....................................................................................................2LE PROJET ECLIPSE............................................4TESTS DES VUES............................................ 121 11..... 100 8LA BIBLIOTHÈQUE DE BALISES JSTL.......................4LE CODE DES VUES............................................................ 118 10................................................................................3LA MÉTHODE [DORETOURFORMULAIRE]........................................ 93 7...................................................................................................................................................................................................... 110 9...........................................1LA VUE [FORMULAIRE].......... 116 10................................................................................................................................................................................................................................................................1LA MÉTHODE [DOINIT]........................5.....................................................................................................123 Les bases du développement web MVC en Java............................. 99 7...............................6.............. 107 9...................................................................90 7.............7TESTS......................................................................................................................................................3CONFIGURATION DE L'APPLICATION WEB [PERSONNE5]....................................................................................1SQUELETTE DU CONTRÔLEUR...................................................................5...............................................113 9..................................................................5...3LA VUE [ERREURS]....................................1.............................4...............................................5.........................................................................................................................................................................................3............................4....................... 121 11.....92 7.................................. 90 7APPLICATION WEB MVC [PERSONNE] – VERSION 3.........................................3.88 6.......6............. 108 9.............................................................2LA MÉTHODE [DOGET]......................................................................................................................................................................................................................................................1INTRODUCTION.....................................................................................................6..... 113 9...........................6TESTS.......................................................

..........................................................2MISE EN ARCHIVES DE L’APPLICATION WEB........................1INTRODUCTION..........................................................................3TESTS DE L'IMPLÉMENTATION [DAOIMPLFIREBIRD].............................................................................................................................. 241 18...............................................128 12....................................................9LES TESTS DE L’APPLICATION WEB............................ 186 16....186 16APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 2................................................................................................................ par l'exemple 263/264 .................4............................................................................................................................................... 227 17...........8..............................................................................................140 14............................................................................................................................................................ 235 18.............................................................1INTRODUCTION................ 180 15.........................................234 18APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 4...............3LA COUCHE [DAO]..................151 14.................................. 133 13APPLICATION WEB MVC [PERSONNE] – VERSION 8................................ 241 Les bases du développement web MVC en Java.........139 14................................................................................................3LA COUCHE [DAO]................3LA CLASSE [DAOIMPLCOMMON]..............................4LES TESTS DES COUCHES [DAO] ET [SERVICE]...................3..................................... 219 17..........................................3CONFIGURATION DE L'APPLICATION WEB [PERSONNE7]................................................3LE CONTRÔLEUR DE L’APPLICATION...............................................7TESTS DE LA COUCHE [SERVICE].............................................................................................8....................5TESTS DE LA COUCHE [DAO]............................................................................................................................................................217 17............................5...................................................................................................................................2............................................................................................................................................................................................ 220 17.............................. 174 15. 130 12..............................................................................................................................................................................................3...........................190 17...........................................................................................................................1LA BASE DE DONNÉES POSTGRES......................................................................................................................................................................3LA REPRÉSENTATION D’UNE PERSONNE. 128 12.....................219 17.............................................................................................................................................................2LE PROJET ECLIPSE................................................................................................................................. 133 14APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 1...5TESTS DE L’APPLICATION [WEB]............................................................................................................................................................................................................................2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]................. 196 17.....3EXEMPLE 1.........2.........................................................................................1SPRING........................................................................ 156 14..........................................................................3CONFIGURATION D'UNE APPLICATION N TIER AVEC SPRING...........................6LA COUCHE [SERVICE]................................................................................................1TESTS DE L'IMPLÉMENTATION [DAOIMPLCOMMON].......169 14..................................................................................................................................................................4CONCLUSION.............................................1PRÉSENTATION.................................................................................................................................... 138 14.....................................................................................................................................................................................................3LA VUE [ERREURS]............................................................................................................................................... 194 17.................................................2LA VUE [RÉPONSE]................ 237 18.................208 17............4LE CODE DES VUES..................................................................................................10CONCLUSION...........................................................................................................................................................4LA COUCHE [DAO]...........................................................186 16...........................................................................................................................................................................................................172 15............................................................................................................. 129 12................................................................................................................................................................................................. 136 14............... 178 15...................6TESTS.................................................................................................................................3..........172 15SPRING IOC................ POSTGRES............................1LA VUE [FORMULAIRE].............................. 235 18.................8.................1CONFIGURATION DE L’APPLICATION WEB.......................129 12........................................................1LES COMPOSANTES DE LA COUCHE [SERVICE]....................................................4............................136 14....................................6TESTS DE LA COUCHE [SERVICE]..............................194 17...........................145 14.............................................................................2LA COUCHE D’ACCÈS AUX DONNÉES [IBATIS]..................................................................... 161 14........................................................................5LE CONTRÔLEUR [SERVLETPERSONNE].......2.................................................................................................................2.....2CONFIGURATION DE LA COUCHE [SERVICE]...............................131 12...............2PROJETS ECLIPSE DES EXEMPLES.......................12.......................................4TESTS DE LA COUCHE [DAO]......................................................................................................................................217 17.................................................................. 174 15.................2SPRING IOC PAR LA PRATIQUE........ 176 15.......................................5LA COUCHE [SERVICE].............................................1LA BASE DE DONNÉES FIREBIRD...8CONCLUSION......................4........................................ 192 17........................182 15............................. 188 17APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 3 – SGBD FIREBIRD..................................175 15................................................ 130 12..........................................................239 18...........................................................5EXEMPLE 3..... 208 17..............................155 14.........2LE PROJET ECLIPSE................................................................4................................................................................................................................152 14...................................2LES PAGES JSP / JSTL DE L’APPLICATION.................................................................................1LES COMPOSANTES DE LA COUCHE [DAO].............................8LA COUCHE [WEB]...............2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]..................................................................................................................................................................................................................... 172 15.........4EXEMPLE 2.........................................................................................................................................................................................5..................................................................................4.................................................................................. 202 17....................................................................4........................................................................... 190 17............................................................................................225 17..........................................2.........................................................................................................................7LA COUCHE [WEB].........................2LA CLASSE [DAOIMPLFIREBIRD]................................................................................ 154 14......................................................

2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]...........................................5TESTS DE L’APPLICATION [WEB]................................................................. SQL SERVER EXPRESS.............2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]....3LA COUCHE [DAO].................................254 20.................................................................................. 260 Les bases du développement web MVC en Java.................................... 250 20...........................................................................................................................................................5TESTS DE L’APPLICATION [WEB]................................................................................................................................................... 248 19............................1LA BASE DE DONNÉES SQL SERVER EXPRESS..................................... 257 21CONCLUSION.............................. 252 20......3LA COUCHE [DAO].............................................................................................................................................................................................................................................................................................................................................. par l'exemple 264/264 ............................................... MYSQL............. 243 19............... 257 20..........................................4LES TESTS DES COUCHES [DAO] ET [SERVICE]...................................................................243 19..........................................................................................1LA BASE DE DONNÉES MYSQL................................................................... 244 19.....................................................................................................................................................................................246 19................................................................................. 258 22LE CODE DE L'ARTICLE.........4LES TESTS DES COUCHES [DAO] ET [SERVICE]............... 249 20APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 6.......................................19APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER – EXEMPLE 5.................... 250 20......................