Vous êtes sur la page 1sur 264

Les bases du dveloppement web MVC en Java - par l'exemple -

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

Les bases du dveloppement web MVC en Java, par l'exemple

1/264

1 Introduction
On se propose ici de dcouvrir les bases de la programmation web MVC en Java avec les servlets et les pages JSP. Aprs 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 comprhension des exercices proposs, 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 donns au dbut de certaines sections rfrencent cet article. Ce document peut tre exploit de diverses manires : 1. 2. on installe les outils, on tlcharge les codes sur le site de ce document et on fait les tests proposs. Un dbutant ne retirera rien d'une telle mthode. Un dveloppeur expriment, lui, peut le faire, s'il est seulement intress par tester l'environnement de dveloppement Eclipse / WTP. on installe les outils, on suit le document et on fait les tests proposs en faisant du copier / coller partir de ce document. On ne lit pas le document [ref1]. En procdant de cette faon, on va commencer acqurir les bases du dveloppement web mais certains points vont rester obscurs ou magiques. C'est une mthode 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 rfrence que [ref1]. on fait la mme chose qu'en 2, mais on lit [ref1] quand c'est conseill. Cette mthode plus lente va liminer certains des points obscurs et magiques de la mthode 2 mais ne prpare pas bien un envol personnel parce que les codes obtenus par copier / coller ne seront pas forcment bien compris. on fait la mme chose qu'en 3, mais on tape soi-mme tous les codes. C'est videmment plus long et plus fastidieux mais trs efficace. Pour taper les codes, il faut les lire ce qui induit une attention au code et par-l mme un dbut de comprhension. Cette recopie manuelle se fera rarement sans erreurs de recopie. Ces erreurs, qui seront signales par les diffrents 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 " Dveloppement 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 dveloppement d'applications web, une rorganisation du document pour mettre l'accent sur l'architecture MVC 3tier qui tait peine aborde dans le document initial, un exemple d'application web MVC 3-tier utilisant les donnes d'un SGBD.

Une application web 3tier a la structure suivante :

utilisateur

Couche web [web]

Couche mtier [metier]

Couche d'accs aux donnes [dao]

Donnes

la couche [dao] s'occupe de l'accs aux donnes, le plus souvent des donnes persistantes au sein d'un SGBD. Mais cela peut tre aussi des donnes qui proviennent de capteurs, du rseau, ... la couche [metier] implmente les algorithmes " mtier " de l'application. Cette couche est indpendante 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 teste en-dehors de l'interface web et notamment avec une interface console. C'est gnralement la couche la plus stable de l'architecture. Elle ne change pas si on change l'interface utilisateur ou la faon d'accder aux donnes ncessaires 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 dveloppement web MVC en Java, par l'exemple

2/264

2 Les outils utiliss 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 dveloppement 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 faon gnrale, de nombreux outils libres peuvent tre utiliss dans le dveloppement Web :
Jbuilder Foundation Eclipse Struts Bibliothques 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 ncessite 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 dveloppement web MVC en Java, par l'exemple

3/264

tape 3 : Lancer l'installation du JDK 1.5 partir du fichier tlcharg.

2.2

Le conteneur de servlets Tomcat 5

Pour excuter des servlets, il nous faut un conteneur de servlets. Nous prsentons ici l'un d'eux, Tomcat 5.x, disponible l'url http://tomcat.apache.org/. Nous indiquons la dmarche (mai 2006) pour l'installer. Si une prcdente version de Tomcat est dj installe, il est prfrable de la supprimer auparavant.

Pour tlcharger 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 tlcharg, on lance l'installation de Tomcat : Faire [next] ->

Les bases du dveloppement 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 dveloppement web MVC en Java, par l'exemple

5/264

Fixer le login / mot de passe de l'administrateur du serveur Tomcat. Ici on a mis [admin / admin] ->

Tomcat 5.x a besoin d'un JRE 1.5. Il doit normalement trouver celui qui est install sur votre machine. Ci-dessus, le chemin dsign est celui du JRE 1.5 tlcharg au paragraphe 2.1. Si aucun JRE n'est trouv, dsignez sa racine en utilisant le bouton [1]. Ceci fait, utilisez le bouton [Install] pour installer Tomcat 5.x ->

Les bases du dveloppement web MVC en Java, par l'exemple

6/264

Le bouton [Finish] termine l'installation. La prsence de Tomcat est signale par une icne droite dans la barre des tches de windows :

Un clic droit sur cette icne donne accs aux commandes Marche Arrt du serveur :

Nous utilisons l'option [Stop service] pour arrter maintenant le serveur web :

On notera le changement d'tat de l'icne. Cette dernire peut tre enleve de la barre des tches :

L'installation de Tomcat s'est faite dans le dossier choisi par l'utilisateur, que nous appellerons dsormais <tomcat>. L'arborescence de ce dossier pour la version Tomcat 5.5.17 tlcharge est la suivante :

L'installation de Tomcat a amen un certain nombre de raccourcis dans le menu [Dmarrer]. Nous utilisons le lien [Monitor] cidessous pour lancer l'outil d'arrt/dmarrage de Tomcat :

Nous retrouvons alors l'icne prsente prcdemment :

Les bases du dveloppement web MVC en Java, par l'exemple

7/264

Le moniteur de Tomcat peut tre activ par un double-clic sur cette icne :

Les boutons [Start - Stop - Pause] - Restart nous permettent de lancer - arrter - relancer le serveur. Nous lanons le serveur par [Start] puis, avec un navigateur nous demandons l'url http://localhost:8080. Nous devons obtenir une page analogue la suivante :

On pourra suivre les liens ci-dessous pour vrifier la correcte installation de Tomcat :

Tous les liens de la page [http://localhost:8080] prsentent un intrt et le lecteur est invit les explorer. Nous aurons l'occasion de revenir sur les liens permettant de grer les applications web dployes au sein du serveur :

Les bases du dveloppement web MVC en Java, par l'exemple

8/264

2.3

Dploiement d'une application web au sein du serveur Tomcat

Lectures [ref1] : chapitre 1, chapitre2 : 2.3.1, 2.3.2, 2.3.3

2.3.1

Dploiement

Une application web doit suivre certaines rgles pour tre dploye au sein d'un conteneur de servlets. Soit <webapp> le dossier d'une application web. Une application web est compose de :
classes archives java vues, ressources (.jsp, .html, ...)

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 configure par un fichier XML : <webapp>\WEB-INF\web.xml. Construisons l'application web dont l'arborescence est la suivante :

On construira l'arborescence ci-dessus avec l'explorateur windows. Les dossiers [classes] et [lib] sont ici vides. 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 .... </body> </html>

Si on charge ce fichier dans un navigateur, on obtient la page suivante :

L'URL affiche par le navigateur montre que la page n'a pas t dlivre par un serveur web mais directement charge par le navigateur. Nous voulons maintenant qu'elle soit disponible via le serveur web Tomcat. Revenons sur l'arborescence du rpertoire <tomcat> :

La configuration des applications web dployes au sein du serveur Tomcat se fait l'aide de fichiers XML placs dans le dossier [<tomcat>\conf\Catalina\localhost] :
Les bases du dveloppement web MVC en Java, par l'exemple

9/264

Ces fichiers XML peuvent tre crs la main car leur structure est simple. Plutt que d'adopter cette dmarche, nous allons utiliser les outils web que nous offre Tomcat.

2.3.2

Administration de Tomcat

Sur sa page d'entre http://localhost:8080, 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 dployes en son sein, par exemple un pool de connexions une base de donnes. Suivons le lien :

La page obtenue nous indique que l'administration de Tomcat 5.x ncessite un paquetage particulier appel " admin ". Retournons sur le site de Tomcat :

Tlchargeons le zip libell [Administration Web Application] puis dcompressons-le. 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.x :

Les bases du dveloppement web MVC en Java, par l'exemple

10/264

Le dossier [localhost] contient un fichier [admin.xml] qui doit tre copi dans [<tomcat>\conf\Catalina\localhost] :

Arrtons puis relanons Tomcat s'il tait actif. Puis avec un navigateur, redemandons la page d'entre du serveur web :

Suivons le lien [Tomcat Administration]. Nous obtenons une page d'identification : Note : en fait, pour obtenir la page ci-dessous, j'ai du d'abord demander manuellement l'url [http://localhost:8080/admin/index.jsp]. Ce n'est qu'ensuite que le lien [Tomcat Administration] ci-dessus a fonctionn. J'ignore s'il s'agit ou non d'une erreur de procdure de ma part.

Ici, il faut redonner les informations que nous avons donnes au cours de l'installation de Tomcat. Dans notre cas, nous donnons le couple admin / admin. Le bouton [Login] nous amne la page suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

11/264

Cette page permet l'administrateur de Tomcat de dfinir des sources de donnes (Data Sources), les informations ncessaires l'envoi de courrier (Mail Sessions), des donnes d'environnement accessibles toutes les applications (Environment Entries), de grer les utilisateurs/administrateurs de Tomcat (Users), de grer des groupes d'utilisateurs (Groups), de dfinir des rles (= ce que peut faire ou non un utilisateur), de dfinir les caractristiques des applications web dployes par le serveur (Service Catalina)

Suivons le lien [Roles] ci-dessus :

Un rle permet de dfinir ce que peut faire ou ne pas faire un utilisateur ou un groupe d'utilisateurs. On associe un rle certains droits. Chaque utilisateur est associ un ou plusieurs rles et dispose des droits de ceux-ci. Le rle [manager] cidessous donne le droit de grer les applications web dployes dans Tomcat (dploiement, dmarrage, arrt, dchargement). Nous allons crer un utilisateur [manager] qu'on associera au rle [manager] afin de lui permettre de grer les applications de Tomcat. Pour cela, nous suivons le lien [Users] de la page d'administration :

Nous voyons qu'un certain nombre d'utilisateurs existent dj. Nous utilisons l'option [Create New User] pour crer un nouvel utilisateur :

Les bases du dveloppement web MVC en Java, par l'exemple

12/264

Nous donnons l'utilisateur manager le mot de passe manager et nous lui attribuons le rle manager. Nous utilisons le bouton [Save] pour valider cet ajout. Le nouvel utilisateur apparat dans la liste des utilisateurs :

Ce nouvel utilisateur va tre ajout dans le fichier [<tomcat>\conf\tomcat-users.xml] :

dont le contenu est le suivant :


1. <?xml version='1.0' encoding='utf-8'?> 2. <tomcat-users> 3. <role rolename="tomcat"/> 4. <role rolename="role1"/> 5. <role rolename="manager"/> 6. <role rolename="admin"/> 7. <user username="tomcat" password="tomcat" roles="tomcat"/> 8. <user username="role1" password="tomcat" roles="role1"/> 9. <user username="both" password="tomcat" roles="tomcat,role1"/> 10. <user username="manager" password="manager" fullName="" roles="manager"/> 11. <user username="admin" password="admin" roles="admin,manager"/> 12. </tomcat-users>

ligne 10 : l'utilisateur [manager] qui a t cr

Une autre faon d'ajouter des utilisateurs est de modifier directement ce fichier. C'est notamment ainsi qu'il faut procder si, d'aventure, on a oubli le mot de passe de l'administrateur admin ou de manager.
Les bases du dveloppement web MVC en Java, par l'exemple

13/264

2.3.3

Gestion des applications web dployes

Revenons maintenant la page d'entre [http://localhost:8080] et suivons le lien [Tomcat Manager] :

Nous obtenons alors une page d'authentification. Nous nous identifions comme manager / manager, c.a.d. l'utilisateur de rle [manager] que nous venons de crer. En effet, seul un utilisateur ayant ce rle peut utiliser ce lien. Ligne 11 de [tomcatusers.xml], nous voyons que l'utilisateur [admin] a galement le rle [manager]. Nous pourrions donc utiliser galement l'authentification [admin / admin].

Nous obtenons une page listant les applications actuellement dployes dans Tomcat :

Nous pouvons ajouter une nouvelle application grce des formulaires placs en bas de la page :

Les bases du dveloppement web MVC en Java, par l'exemple

14/264

Ici, nous voulons dployer au sein de Tomcat, l'application exemple que nous avons construite prcdemment. Nous le faisons de la faon suivante :

Context Path Directory URL

/exemple C:\data\2005-2006\eclipse\dvp-eclipse-tomcat\exemple

le nom utilis pour dsigner l'application web dployer le dossier de l'application web

Pour obtenir le fichier [C:\data\2005-2006\eclipse\dvp-eclipse-tomcat\exemple\vues\exemple.html], nous demanderons Tomcat l'URL [http://localhost:8080/exemple/vues/exemple.html]. Le contexte sert donc donner un nom la racine de l'arborescence de l'application web dploye. Nous utilisons le bouton [Deploy] pour effectuer le dploiement de l'application. Si tout se passe bien, nous obtenons la page rponse suivante :

et la nouvelle application apparat dans la liste des applications dployes :

Commentons la ligne du contexte /exemple ci-dessus :

Les bases du dveloppement web MVC en Java, par l'exemple

15/264

/exemple Dmarrer Arrter Recharger Undeploy

lien sur http://localhost:8080/exemple permet de dmarrer l'application permet d'arrter l'application permet de recharger l'application. C'est ncessaire par exemple lorsqu'on a ajout, modifi ou supprim certaines classes de l'application. suppression du contexte [/exemple]. L'application disparat de la liste des applications disponibles.

Maintenant que notre application /exemple est dploye, nous pouvons faire quelques tests. Nous demandons la page [exemple.html] via l'url [http://localhost:8080/exemple/vues/exemple.html] :

Une autre faon de dployer une application web au sein du serveur Tomcat est de donner les renseignements que nous avons donns via l'interface web, dans un fichier [contexte].xml plac dans le dossier [<tomcat>\conf\Catalina\localhost], o [contexte] est le nom de l'application web. 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. Maintenant dfinissons le fichier [exemple.xml] suivant :
1. 2. <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 dfinit le dossier contenant l'application web dployer. Plaons ce fichier dans <tomcat>\conf\Catalina\localhost :

Arrtons et relanons Tomcat si besoin est, puis visualisons la liste des applications actives avec l'administrateur de Tomcat :
Les bases du dveloppement web MVC en Java, par l'exemple

16/264

L'application [/exemple] est bien prsente. Demandons, avec un navigateur, l'url : [http://localhost:8080/exemple/vues/exemple.html] :

Une application web ainsi dploye peut tre supprime de la liste des applications dployes, de la mme faon que prcdemment, avec le lien [Undeploy] :

Dans ce cas, le fichier [exemple.xml] est automatiquement supprim du dossier [<tomcat>\conf\Catalina\localhost]. Enfin, pour dployer une application web au sein de Tomcat, on peut galement dfinir son contexte dans le fichier [<tomcat>\conf\server.xml]. Nous ne dvelopperons pas ce point ici.

2.3.4

Application web avec page d'accueil

Lorsque nous demandons l'url [http://localhost:8080/exemple/], nous obtenons la rponse suivante :

Ce rsultat dpend de la configuration de Tomcat. Avec des versions prcdentes, nous aurions obtenu le contenu du dossier physique de l'application [/exemple]. C'est une bonne chose que dsormais par dfaut, Tomcat empche cet affichage. On peut faire en sorte que, lorsque le contexte est demand, on affiche une page dite d'accueil. Pour cela, nous crons un fichier [web.xml] que nous plaons dans le dossier <exemple>\WEB-INF, o <exemple> est le dossier physique de l'application web [/exemple]. Ce fichier est le suivant :
Les bases du dveloppement web MVC en Java, par l'exemple

17/264

1. 2. 3. 4.

5. 6. 7. <display-name>Application Exemple</display-name> 8. <description>Application web minimale</description> 9. <welcome-file-list> 10. <welcome-file>/vues/exemple.html</welcome-file> 11. </welcome-file-list> 12. </web-app>

<?xml version="1.0" encoding="ISO-8859-1"?> <web-app 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" version="2.4">

lignes 2-5 : la balise racine <web-app> avec des attributs obtenus par copier / coller du fichier [web.xml] de l'application [/admin] de Tomcat (<tomcat>/server/webapps/admin/WEB-INF/web.xml). ligne 7 : le nom d'affichage de l'application web. 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, ce qui n'est pas possible avec le nom de contexte. Ce nom est affich par exemple par l'administrateur Tomcat :

ligne 8 : description de l'application web. Ce texte peut ensuite tre obtenu par programmation. lignes 9-11 : la liste des fichiers d'accueil. La balise <welcome-file-list> sert dfinir la liste des vues prsenter lorsqu'un client demande le contexte de l'application. Il peut y avoir plusieurs vues. La premire trouve est prsente au client. Ici nous n'en avons qu'une : [/vues/exemple.html]. Ainsi, lorsqu'un client demandera l'url [/exemple], ce sera en fait l'url [/exemple/vues/exemple.html] qui lui sera dlivre.

Sauvegardons ce fichier [web.xml] dans <exemple>\WEB-INF :

Si Tomcat est toujours actif, il est possible de le forcer recharger l'application web [/exemple] avec le lien [Recharger] :

Lors de cette opration de " rechargement ", Tomcat relit le fichier [web.xml] contenu dans [<exemple>\WEB-INF] s'il existe. Ce sera le cas ici. Si Tomcat tait arrt, le relancer. Avec un navigateur, demandons l'URL [http://localhost:8080/exemple/] :

Le mcanisme des fichiers d'accueil a fonctionn.

Les bases du dveloppement web MVC en Java, par l'exemple

18/264

2.4

Installation d'Eclipse

Eclipse est un environnement de dveloppement multi-langages. Il est beaucoup utilis dans le dveloppement Java. C'est un outil extensible par adjonction d'outils appels plugins. Il existe un grand nombre de plugins et c'est ce qui fait la force d'Eclipse. Eclipse est disponible l'url [http://www.eclipse.org/downloads/] :

Nous souhaitons utiliser Eclipse pour faire du dveloppement web en Java. Il existe un certain nombre de plugins pour cela. Ils aident vrifier la syntaxe des pages JSP, des fichiers XML, ... et permettent de tester une application web l'intrieur d'Eclipse. Nous utiliserons l'un de ces plugins, appel Web Tools Package (WTP). La dmarche normale d'installation d'Eclipse est normalement la suivante : 1. 2. installer Eclipse installer les plugins dont on a besoin

Le plugin WTP ncessite lui-mme d'autres plugins ce qui fait que son installation est assez complexe. Aussi le site d'Eclipse propose-t-il un paquetage comprenant la plate-forme de dveloppement Eclipse et le plugin WTP avec tous les autres plugins dont celui-ci a besoin. Ce paquetage est disponible sur le site d'Eclipse (mai 2006) l'url [http://download.eclipse.org/webtools/downloads/] :

Suivons le lien [1.0.2] ci-dessus :

Nous tlchargeons le paquetage [wtp] avec le lien ci-dessus. Le fichier zipp obtenu a le contenu suivant :

Il suffit de dcompresser ce contenu dans un dossier. Nous appellerons dsormais ce dossier <eclipse>. Son contenu est le suivant :
Les bases du dveloppement web MVC en Java, par l'exemple

19/264

[eclipse.exe] est l'excutable et [eclipse.ini] le fichier de configuration de celui-ci. Regardons le contenu de celui-ci :
1. 2. 3. -vmargs -Xms40m -Xmx256m

Ces arguments sont utiliss lors du lancement d'Eclipse de la faon suivante :


eclipse.exe -vmargs -Xms40m -Xmx256m

On arrive au mme rsultat obtenu avec le fichier .ini en crant un raccourci qui lancerait Eclipse avec ces mmes arguments. Explicitons ceux-ci :

-vmargs : indique que les arguments qui suivent sont destins la machine virtuelle Java qui va excuter Eclipse. En effet, Eclipse est une application Java. -Xms40m : ? -Xmx256m : fixe la taille mmoire en Mo alloue la machine virtuelle Java (JVM) qui excute Eclipse. Par dfaut, cette taille est de 256 Mo comme montr ici. Si la machine le permet, 512 Mo est prfrable.

Ces arguments sont passs la JVM qui va excuter Eclipse. La JVM est reprsente par un fichier [java.exe] ou [javaw.exe]. Comment celui-ci est-il trouv ? En fait, il est cherch de diffrentes faons : 1. 2. 3. dans le PATH de l'OS dans le dossier <JAVA_HOME>/jre/bin o JAVA_HOME est une variable systme dfinissant le dossier racine d'un JDK. un emplacement pass en argument Eclipse sous la forme -vm <chemin>\javaw.exe

Cette dernire solution est prfrable car les deux autres sont sujettes aux alas des installations ultrieures d'applications qui peuvent soit changer le PATH de l'OS, soit changer la variable JAVA_HOME. Nous crons donc le raccourci suivant :

cible Dmarrer dans

<eclipse>\eclipse.exe -vm "C:\Program Files\Java\jre1.5.0_06\bin\javaw.exe" -vmargs -Xms40m -Xmx512m dossier <eclipse> d'installation d'Eclipse

Ceci fait, lanons Eclipse via ce raccourci. On obtient une premire bote de dialogue :

Les bases du dveloppement web MVC en Java, par l'exemple

20/264

Un [workspace] est un espace de travail. Acceptons les valeurs par dfaut proposes. Par dfaut, les projets Eclipse crs le seront dans le dossier <workspace> spcifi dans cette bote de dialogue. Il y a moyen de contourner ce comportement. C'est ce que nous ferons systmatiquement. Aussi la rponse donne cette bote de dialogue n'est-elle pas importante. Passe cette tape, l'environnement de dveloppement Eclipse est affich :

Nous fermons la vue [Welcome] comme suggr ci-dessus :

Avant de crer un projet Java, nous allons configurer Eclipse pour indiquer le JDK utiliser pour compiler les projets Java. Pour cela, nous prenons l'option [Window / Preferences / Java / Installed JREs ] :

Normalement, le JRE (Java Runtime Environment) qui a t utilis pour lancer Eclipse lui-mme doit tre prsent dans la liste des JRE. Ce sera le seul normalement. Il est possible d'ajouter des JRE avec le bouton [Add]. Il faut alors dsigner la racine du JRE. Le bouton [Search] va lui, lancer une recherche de JREs sur le disque. C'est un bon moyen de savoir o on en est dans les JREs qu'on installe puis qu'on oublie de dsinstaller lorsqu'on passe une version plus rcente. Ci-dessus, le JRE coch est celui qui sera utilis pour compiler et excuter les projets Java. Le JRE qui sera utilis dans nos exemples est celui qui a t install au paragraphe 2.1 et qui a galement servi lancer Eclipse. Un double clic dessus donne accs ses proprits :

Les bases du dveloppement web MVC en Java, par l'exemple

21/264

Maintenant, crons un projet Java [File / New / Project] :

Choisir [Java Project], puis [Next] ->

Dans [2], nous indiquons un dossier vide dans lequel sera install le projet Java. Dans [1], nous donnons un nom au projet. Il n'a pas porter le nom de son dossier comme pourrait le laisser croire l'exemple ci-dessus. Ceci fait, on utilise le bouton [Finish] pour terminer l'assistant de cration. Cela revient accepter les valeurs par dfaut proposes par les pages suivantes de l'assistant. Nous avons alors un squelette de projet Java :

Cliquons droit sur le projet [test1] pour crer une classe Java :

Les bases du dveloppement web MVC en Java, par l'exemple

22/264

1 2 3

4
dans [1], le dossier o sera cre la classe. Eclipse propose par dfaut le dossier du projet courant. dans [2], le paquetage dans lequel sera place la classe dans [3], le nom de la classe dans [4], nous demandons ce que la mthode statique [main] soit gnre

Nous validons l'assistant par [Finish]. Le projet est alors enrichi d'une classe :

Eclipse a gnr le squelette de la classe. Celui-ci est obtenu en double-cliquant sur [Test1.java] ci-dessus :

Nous modifions le code ci-dessus de la faon suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

23/264

Nous excutons le programme [Test1.java] : [clic droit sur Test1.java -> Run As -> Java Application]

Le rsultat de l'excution est obtenu dans la fentre [Console] :

2.5

Intgration Tomcat - Eclipse

Afin de travailler avec Tomcat tout en restant au sein d'Eclipse, il nous faut dclarer ce serveur dans la configuration d'Eclipse. Pour cela, nous prenons l'option [File / New / Other]. Nous obtenons alors l'assistant suivant :

Nous choisissons de crer un nouveau serveur. Nous slectionnons l'icne [Server] ci-dessus puis faisons [Next] :

Les bases du dveloppement web MVC en Java, par l'exemple

24/264

- avec le bouton [Browse], nous dsignons le dossier d'installation de Tomcat 5.5 - avec le bouton [Installed JREs] nous dsignons le JRE utiliser - le champ [Name] est libre puis [Next] ->

[Finish] ->

Nous choisissons le serveur Tomcat 5.5 puis [Next] -> L'ajout du serveur se concrtise par celui d'un dossier dans l'explorateur de projets d'Eclipse :

Pour grer Tomcat depuis Eclipse, nous faisons apparatre la vue appele [Servers] avec l'option [Window -> Show View -> Other -> Server] :

Faire [OK]. La vue [Servers] apparat alors :

Apparassent dans cette vue, tous les serveurs dclars, ici le serveur Tomcat 5.5 que nous venons d'enregistrer. Un clic droit dessus donne accs aux commandes permettant de dmarrer arrter relancer le serveur :

Les bases du dveloppement web MVC en Java, par l'exemple

25/264

Ci-dessus, nous lanons le serveur. Lors de son dmarrage, un certain nombre de logs sont crits dans la vue [Console] :
1. 2. 3. 4. 5. 6. 11 mai 2006 15:31:16 org.apache.catalina.core.AprLifecycleListener lifecycleEvent INFO: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:\Program Files\Java\jre1.5.0_06\bin;.;C:\WINDOWS\system32;... 11 mai 2006 15:31:16 org.apache.coyote.http11.Http11BaseProtocol init INFO: Initialisation de Coyote HTTP/1.1 sur http-8080 ... INFO: Server startup in 1641 ms

La comprhension de ces logs demande une certaine habitude. Nous ne nous apesantirons pas dessus pour le moment. Il est cependant important de vrifier qu'ils ne signalent pas d'erreurs de chargement de contextes. En effet, lorsqu'il est lanc, le serveur Tomcat / Eclipse va chercher charger le contexte des applications qu'il gre. Charger le contexte d'une application implique d'exploiter son fichier [web.xml] et de charger une ou plusieurs classes l'initialisant. Plusieurs types d'erreurs peuvent alors se produire : - le fichier [web.xml] est syntaxiquement incorrect. C'est l'erreur la plus frquente. Il est conseill d'utiliser un outil capable de vrifier la validit d'un document XML lors de sa construction. - certaines classes charger n'ont pas t trouves. Elles sont cherches dans [WEB-INF/classes] et [WEBINF/lib]. Il faut en gnral vrifier la prsence des classes ncessaires et l'orthographe de celles dclares dans le fichier [web.xml]. Le serveur lanc partir d'Eclipse n'a pas la mme configuration que celui install au paragraphe 2.2, page 4. Pour nous en assurer, demandons l'url [http://localhost:8080] avec un navigateur :

Cette rponse n'indique pas que le serveur ne fonctionne pas mais que la ressource / qui lui est demande n'est pas disponible. Avec le serveur Tomcat intgr Eclipse, ces ressources vont tre des projets web. Nous le verrons ultrieurement. Pour le moment, arrtons Tomcat :

Les bases du dveloppement web MVC en Java, par l'exemple

26/264

Le mode de fonctionnement prcdent peut tre chang. Revenons dans la vue [Servers] et double-cliquons sur le serveur Tomcat pour avoir accs ses proprits :

La case cocher [1] est responsable du fonctionnement prcdent. Lorsqu'elle est coche, les applications web dveloppes sous Eclipse ne sont pas dclares dans les fichiers de configuration du serveur Tomcat associ mais dans des fichiers de configuration spars. Ce faisant, on ne dispose pas des applications dfinies par dfaut au sein du serveur Tomcat : [admin] et [manager] qui sont deux applications utiles. Aussi, allons-nous dcocher [1] et relancer Tomcat :

Ceci fait, demandons l'url [http://localhost:8080] avec un navigateur :

Les bases du dveloppement web MVC en Java, par l'exemple

27/264

Nous retrouvons le fonctionnement dcrit au paragraphe 2.3.3, page 14. Nous avons, dans nos exemples prcdents, utilis un navigateur extrieur Eclipse. On peut galement utiliser un navigateur interne Eclipse :

Nous slectionnons ci-dessus, le navigateur interne. Pour le lancer partir d'Eclipse on peut utiliser l'icne suivante :

Le navigateur rellement lanc sera celui slectionn par l'option [Window -> Web Browser]. Ici, nous obtenons le navigateur interne :

Lanons si besoin est Tomcat partir d'Eclipse et demandons dans [1] l'url [http://localhost:8080] :

Les bases du dveloppement web MVC en Java, par l'exemple

28/264

Suivons le lien [Tomcat Manager] :

Le couple [login / mot de passe] ncessaire pour accder l'application [manager] est demand. D'aprs la configuration de Tomcat que nous avons faite prcdemment, on peut taper [admin / admin] ou [manager / manager]. On obtient alors la liste des applications dployes :

Nous voyons l'application [personne] que nous avons cre. Le lien [Recharger] associ nous sera utile ultrieurement.

3 Les bases du dveloppement web en Java


Nous abordons maintenant le dveloppement d'applications web dynamiques, c.a.d. des applications dans lesquelles les pages HTML envoyes l'utilisateur sont gnres par des programmes.

3.1

Cration d'un projet web sous Eclipse

Nous allons dvelopper une premire application web avec Eclipse/Tomcat. Nous reprenons une dmarche analogue celle utilise pour crer une application web sans Eclipse. Eclipse lanc, nous crons un nouveau projet :

que nous dfinissons comme un projet web dynamique :

Les bases du dveloppement web MVC en Java, par l'exemple

29/264

Dans la premire page de l'assistant de cration, nous prcisons le nom du projet [1] et son emplacement [2] :

1 2

Dans la seconde page de l'assistant nous acceptons les valeurs par dfaut :

La dernire page de l'assistant nous demande de dfinir le contexte [3] de l'application :

Une fois l'assistant valid par [Finish], Eclipse se connecte au site [http://java.sun.com] pour rcuprer certains documents qu'il veut mettre en cache afin d'viter des accs rseau inutiles. Une demande d'agrment de licence est alors demande :

Les bases du dveloppement web MVC en Java, par l'exemple

30/264

On l'accepte. Eclipse cre le projet web. Pour l'afficher, il utilise un environnement, appel perspective, diffrent de celui utilis pour un projet Java classique :

La perspective associe un projet web est la perspective J2EE. Nous l'acceptons pour voir... Le rsultat obtenu est le suivant :

La perspective J2EE est en fait inutilement complexe pour les projets web simples. Dans ce cas, la perspective Java est suffisante. Pour l'obtenir, 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. build/classes (non reprsent) : contiendra les .class des classes compiles ainsi qu'une copie de tous les fichiers autres que .java placs dans src. Une application web utilise frquemment des fichiers dits fichiers "ressource" qui doivent tre dans le Classpath de l'application, c.a.d. l'ensemble des dossiers qui sont explors par la JVM lorsque l'application fait rfrence une classe, soit pendant la compilation soit l'excution. Eclipse fait en sorte que le dossier build/classes fasse partie du c web. On place dans le dossier src les fichiers "ressource" sachant qu'Eclipse les recopiera automatiquement dans build/classes. WebContent : contiendra les ressources de l'application web qui n'ont pas tre dans le dans le Classpath de l'application. WEB-INF/lib : contiendra les archives .jar dont a besoin l'application web. Examinons le contenu du fichier [WEB-INF/web.xml] qui configure l'application [personne] :
Les bases du dveloppement web MVC en Java, par l'exemple

31/264

1. 2.

<?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"> 3. <display-name>personne</display-name> 4. <welcome-file-list> 5. <welcome-file>index.html</welcome-file> 6. <welcome-file>index.htm</welcome-file> 7. <welcome-file>index.jsp</welcome-file> 8. <welcome-file>default.html</welcome-file> 9. <welcome-file>default.htm</welcome-file> 10. <welcome-file>default.jsp</welcome-file> 11. </welcome-file-list> 12. </web-app>

Nous avons dj rencontr de type de configuration lorsque nous avons tudi la cration de pages d'accueil au paragraphe 2.3.4, page 17. Ce fichier ne fait rien d'autre que de dfinir une srie de pages d'accueil. Nous ne gardons que la premire. Le fichier [web.xml] devient le suivant :
1. 2. 3. 4. 5. <?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"> 6. <display-name>personne</display-name> 7. <welcome-file-list> 8. <welcome-file>index.html</welcome-file> 9. </welcome-file-list> 10. </web-app>

Le contenu du fichier XML ci-dessus doit obir aux rgles de syntaxe dfinies dans le fichier dsign par l'attribut [xsi:schemaLocation] de la balise d'ouverture <web-app>. Ce fichier est ici [http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd]. C'est un fichier XML qu'on peut demander directement avec un navigateur. Si celui-ci est suffisamment rcent, il saura afficher un fichier XML :

Eclipse va chercher vrifier la validit du document XML l'aide du fichier .xsd prcis dans l'attribut [xsi:schemaLocation] de la balise d'ouverture <web-app>. Il va pour cela faire un accs rseau. Si votre ordinateur est sur un rseau priv, il faut alors indiquer Eclipse la machine utiliser pour sortir du rseau priv, appele proxy HTTP. Cela se fait avec l'option [Window -> Preferences -> Internet] :

Les bases du dveloppement web MVC en Java, par l'exemple

32/264

1 2 3 4

On coche (1) si on est sur un rseau priv. Dans (2), on indique le nom de la machine qui supporte le proxy HTTP et dans (3) le port d'coute de celui-ci. Enfin dans (4), on indique les machines pour lesquelles il ne faut pas passer par le proxy, des machines qui sont sur le mme rseau priv que la machine avec laquelle on travaille. Nous allons crer maintenant le fichier [index.html] de la page d'accueil.

3.2

Cration 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, nous slectionnons le dossier parent [WebContent] en (1) ou en (2) puis nous prcisons en (3) le nom du fichier crer. Ceci fait, nous passons la page suivante de l'assistant :

Les bases du dveloppement web MVC en Java, par l'exemple

33/264

Avec (1), nous pouvons gnrer un fichier HTML pr-rempli par (2). Si on dcoche (1), on gnre un fichier HTML vide. Nous gardons la coche (1) afin de bnficier d'un squelette de code. Nous terminons l'assistant par [Finish]. Le fichier [index.html] est alors cr :

avec le contenu suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> </body> </html>

Nous modifions ce fichier de la faon suivante :


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Application personne</title> </head> <body> Application personne active... <br> <br> Vous tes sur la page d'accueil </body> </html>

3.3

Test de la page d'accueil

Si elle n'est pas prsente, faisons apparatre la vue [Servers] avec l'option [Window - > Show View -> Other -> Servers] puis cliquons droit sur le serveur Tomcat 5.5 :

Les bases du dveloppement web MVC en Java, par l'exemple

34/264

L'option [Add and Remove Objects] ci-dessus permet d'ajouter / retirer des applications web au serveur Tomcat :

2 1 3

Les projets web connus d'Eclipse sont prsents en (1). On peut les enregistrer sur le serveur Tomcat par (2). Les applications web enregistres auprs du serveur Tomcat apparaissent en (4). On peut les dsenregistrer avec (3). Enregistrons le projet [personne] :

puis terminons l'assistant d'enregistrement par [Finish]. La vue [Servers] montre que le projet [personne] a t enregistr sur Tomcat :

Maintenant, lanons le serveur Tomcat :

Les bases du dveloppement web MVC en Java, par l'exemple

35/264

Lanons le navigateur web :

puis demandons l'url [http://localhost:8080/personne]. Cette url est celle de la racine de l'application web. Aucun document n'est demand. Dans ce cas, c'est la page d'accueil de l'application qui est affiche. Si elle n'existe pas, une erreur est signale. Ici la page d'accueil existe. C'est le fichier [index.html] que nous avons cr prcdemment. Le rsultat obtenu est le suivant :

Il est conforme ce qui tait attendu. Maintenant, prenons un navigateur externe Eclipse et demandons la mme url :

L'application web [personne] est donc connue galement l'extrieur d'Eclipse.

3.4

Cration d'un formulaire HTML

Nous crons maintenant un document HTML statique [formulaire.html] dans le dossier [personne] :

Les bases du dveloppement web MVC en Java, par l'exemple

36/264

Pour le crer, on suivra la procdure dcrite au paragraphe 3.2, page 33. Son contenu sera 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. 27. 28. 29. 30. 31. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>Personne - formulaire</title> </head> <body> <center> <h2>Personne - 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

5
rle

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 reue initialement par le navigateur pour effacer les contenu des champs de saisie [1] et [2]

Sauvegardons le document dans le dossier <personne>/WebContent. Lanons Tomcat si besoin est. Avec un navigateur demandons l'URL http://localhost:8080/personne/formulaire.html :

Les bases du dveloppement web MVC en Java, par l'exemple

37/264

L'architecture client / serveur de cette application basique est la suivante :

Application web Utilisateur formulaire.html

Le serveur web est entre l'utilisateur et l'application web et n'a pas t reprsent ici. [formulaire.html] est un document statique qui dlivre chaque requte du client le mme contenu. La programmation web vise gnrer du contenu adapt la requte du client. Ce contenu est alors gnr par programme. Une premire solution est d'utiliser une page JSP (Java Server Page) la place du fichier HTML statique. C'est ce que nous voyons maintenant.

3.5

Cration d'une page JSP

Lectures [ref1] : chapitre 1, chapitre 2 : 2.2, 2.2.1, 2.2.2, 2.2.3, 2.2.4 L'architecture client / serveur prcdente est transforme comme suit :

Application web Utilisateur formulaire.jsp

Une page JSP est une forme de page HTML paramtre. Certains lments de la page ne reoivent leur valeur qu'au moment de l'excution. Ces valeurs sont calcules par programme. On a donc une page dynamique : des requtes successives sur la page peuvent amener des rponses diffrentes. Nous appelons ici rponse, la page HTML affiche par le navigateur client. Au final, c'est toujours un document HTML que reoit le navigateur. Ce document HTML est gnr par le serveur web partir de la page JSP. Celle-ci sert de modle. Ses lments dynamiques sont remplacs par leurs valeurs effectives au moment de la gnration du document HTML. Pour crer une page JSP, nous cliquons droit sur le dossier [WebContent] puis prenons l'option [New -> Other] :

Les bases du dveloppement web MVC en Java, par l'exemple

38/264

Nous choisissons le type [JSP] et faisons [Next] ->

1 3
Ci-dessus, nous slectionnons le dossier parent [WebContent] en (1) ou en (2) puis nous prcisons en (3) le nom du fichier crer. Ceci fait, nous passons la page suivante de l'assistant :

Avec (1), nous pouvons gnrer un fichier JSP pr-rempli par (2). Si on dcoche (1), on gnre un fichier JSP vide. Nous gardons la coche (1) afin de bnficier d'un squelette de code. Nous terminons l'assistant par [Finish]. Le fichier [formulaire.jsp] est alors cr :

Les bases du dveloppement web MVC en Java, par l'exemple

39/264

avec le contenu suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Insert title here</title> </head> <body> </body> </html>

La ligne 1 indique que nous avons affaire une page JSP. Nous transformons le texte ci-dessus de la faon 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. 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"> <% // on rcupre les paramtres String nom=request.getParameter("txtNom"); if(nom==null) nom="inconnu"; String age=request.getParameter("txtAge"); if(age==null) age="xxx"; %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Personne - formulaire</title> </head> <body> <center> <h2>Personne - 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="Rtablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table> </form> </center> </body> </html>

Les bases du dveloppement web MVC en Java, par l'exemple

40/264

Le document initialement statique est maintenant devenu dynamique par introduction de code Java. Pour ce type de document, nous procderons toujours comme suit : nous mettons du code Java ds le dbut du document pour rcuprer les paramtres ncessaires au document pour s'afficher. Ceux-ci seront souvent dans l'objet request. Cet objet reprsente la requte du client. Celle-ci peut passer au travers de plusieurs servlets et pages JSP qui ont pu l'enrichir. Ici, elle nous arrivera directement du navigateur. le code HTML se trouve la suite. Il se contentera le plus souvent d'afficher des variables calcules auparavant dans le code Java au moyen de balises <%= variable %>. On notera ici que le signe = est coll au signe %. C'est une cause frquente d'erreurs. Que fait le document dynamique prcdent ? lignes 6-9 : il rcupre dans la requte, deux paramtres appels [txtNom] et [txtAge] et met leurs valeurs dans les variables [nom] (ligne 6) et [age] (ligne 8). S'il ne trouve pas les paramtres, il donne aux variables associes des valeurs par dfaut. il affiche la valeur des deux variables [nom, age] dans le code HTML qui suit (lignes 25 et 29). Faisons un premier test. Lanons Tomcat http://localhost:8080/personne/formulaire.jsp : si besoin est, puis avec un navigateur, demandons l'URL

Le document formulaire.jsp a t appel sans passage de paramtres. Les valeurs par dfaut ont donc t affiches. Maintenant demandons l'URL http://localhost:8080/personne/formulaire.jsp?txtNom=martin&txtAge=14 :

Cette fois-ci, nous avons pass au document formulaire.jsp, les paramtres txtNom et txtAge qu'il attend. Il les a donc affichs. On sait qu'il y a deux mthodes pour passer des paramtres un document web : GET et POST. Dans les deux cas, les paramtres passs se retrouvent dans l'objet prdfini request. Ici, ils ont t passs par la mthode GET.

3.6

Cration d'une servlet

Lectures [ref1] : chapitre 1, chapitre 2 : 2.1, 2.1.1, 2.1.2, 2.3.1 Dans la version prcdente, la requte du client tait traite par une page JSP. Lors du premier appel celle-ci, le serveur web, ici Tomcat, cre une classe Java partir de cette page et compile celle-ci. C'est le rsultat de cette compilation qui au final traite la requte du client. La classe gnre partir de la page JSP est une servlet parce qu'elle implmente l'interface [javax.Servlet] :

Les bases du dveloppement web MVC en Java, par l'exemple

41/264

La requte du client peut tre traite par toute classe implmentant cette interface. Nous construisons maintenant une telle classe : ServletFormulaire. L'architecture client / serveur prcdente est transforme comme suit :

Application web Utilisateur ServletFormulaire.java

Avec l'architecture base de page JSP, le document HTML envoy au client tait gnr par le serveur web partir de la page JSP qui servait de modle. Ici le document HTML envoy au client sera entirement gnr par la servlet.

3.6.1

Cration de la servlet

Sous Eclipse, cliquons droit sur le dossier [src] et prenons l'option de cration d'une classe :

puis dfinissons les caractristiques de la classe crer :

1 2 4 3

Dans (1), on met un nom de paquetage, dans (2) le nom de la classe crer. Celle-ci doit driver de la classe indique en (3). Il est inutile de taper soi-mme le nom complet de celle-ci. Le bouton (4) permet l'accs aux classes actuellement dans le Classpath de l'application web :
Les bases du dveloppement web MVC en Java, par l'exemple

42/264

1 2
En (1) on tape le nom de la classe recherche. On obtient en (2) les classes du Classpath dont le nom contient la chane tape en (1). Aprs validation de l'assistant de cration, le projet web [personne] est modifi de la faon suivante :

La classe [ServletFormulaire] a t cre avec un squelette de code :

La copie d'cran ci-dessus montre qu'Eclipse signale un [warning] sur la ligne dclarant la classe. Cliquons sur l'icne (lampe) signalant ce [warning] :

1 2 3

Aprs avoir cliqu sur (1), des solutions pour supprimer le [warning] nous sont proposes dans (2). La slection de l'une d'elles provoque l'apparition dans (3) de la modification de code que son choix va amener. Java 1.5 a amen des modifications au langage Java et ce qui tait correct dans une version antrieure peut dsormais faire l'objet de [warnings]. Ceux-ci ne signalent pas des erreurs qui pourraient empcher la compilation de la classe. Ils sont l pour attirer l'attention du dveloppeur sur des points de code qui pourraient tre amliors. Le [warning] prsent indique qu'une classe devrait avoir un numro de version. Celui-ci est utilis pour la srialisation / dsrialisation des objets, c.a.d. lorsqu'un objet Java .class en mmoire doit tre transform en une suite de bits envoys squentiellement dans un flux d'criture ou l'inverse lorsqu'un objet Java .class en mmoire doit tre cr partir d'une suite de bits lus squentiellement dans un flux de lecture. Tout cela est fort loign de nos proccupations actuelles. Aussi allons-nous demander au compilateur d'ignorer ce warning en choisissant la solution [Add @SuppressWarnings ...]. Le code devient alors le suivant :

Les bases du dveloppement web MVC en Java, par l'exemple

43/264

Il n'y a plus de [warning]. La ligne ajoute s'appelle une " annotation ", une notion apparue avec Java 1.5. Nous complterons ce code ultrieurement.

3.6.2

Classpath d'un projet Eclipse

Le Classpath d'une application Java est l'ensemble des dossiers et archives.jar explores lorsque le compilateur la compile ou lorsque la JVM l'excute. Ces deux Classpath ne sont pas forcment identiques, certaines classes n'tant utiles qu' l'excution et pas la compilation. Le compilateur Java aussi bien que la JVM ont un argument qui permet de prciser le Classpath de l'application compiler ou excuter. De faon plus ou moins transparente pour l'utilisateur, Eclipse assure la construction et le passage de cet argument la JVM. Comment peut-on connatre les lments 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 dfinir la liste des archives .jar qui font partie du Classpath de l'application. Elles sont donc explores par la JVM lorsque l'application demande une classe. Les boutons [2] et [3] permettent d'ajouter des archives au Classpath. Le bouton [2] permet de dsigner des archives prsentes dans les dossiers des projets grs par Eclipse alors que le bouton [3] permet de dsigner toute archive du systme de fichiers de l'ordinateur. Ci-dessus on voit apparatre trois bibliothques (Libraries) :

[JRE System Library] : librairie de base pour les projets Java d'Eclipse :

Les bases du dveloppement web MVC en Java, par l'exemple

44/264

[Tomcat v5.5 runtime] : librairie apporte par le serveur Tomcat. Elle comporte les classes ncessaires au dveloppement web. Cette librairie est incluse dans tout projet web d'Eclipse ayant t associ au serveur Tomcat.

C'est l'archive [servlet-api.jar] qui contient la classe [javax.servlet.http.HttpServlet], classe parent de la classe [ServletFormulaire] que nous sommes en train de crer. C'est parce que cette archive est dans le Classpath de l'application qu'elle a pu tre propose comme classe parent dans l'assistant rappel ci-dessous.

1 2
Si ce n'avait pas t le cas, elle ne serait pas apparue dans les propositions de [2]. Si donc dans cet assistant, on veut rfrencer une classe parent et que celle-ci n'est pas propose, c'est que, soit on se trompe dans le nom de cette classe, soit l'archive qui la contient n'est pas dans le Classpath de l'application.

[Web App Libraries] rassemble les archives prsentes dans le dossier [WEB-INF/lib] du projet. Ici il est vide :

Les archives du Classpath du projet Eclipse sont prsentes dans l'explorateur de projets. Par exemple, pour le projet web [personne] :

L'explorateur de projets nous donne accs au contenu de ces archives :

Les bases du dveloppement web MVC en Java, par l'exemple

45/264

Ainsi ci-dessus, on peut voir que c'est l'archive [servlet-api.jar] qui contient la classe [javax.servlet.http.HttpServlet].

3.6.3

Configuration de la servlet

Lectures [ref1] : chapitre 2 : 2.3, 2.3.1, 2.3.2, 2.3.3, 2.3.4 Le fichier [WEB-INF/web.xml] sert configurer l'application web :

Ce fichier, pour le projet [personne] est actuellement le suivant (cf page 32) :
1. 2. 3. 4. 5. <?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"> 6. <display-name>personne</display-name> 7. <welcome-file-list> 8. <welcome-file>index.html</welcome-file> 9. </welcome-file-list> 10. </web-app>

Il indique seulement l'existence d'un fichier d'accueil (ligne 8). Nous le faisons voluer pour dclarer :
Les bases du dveloppement web MVC en Java, par l'exemple

46/264

l'existence de la servlet [ServletFormulaire] les URL traites par cette servlet des paramtres d'initialisation de la servlet

Le fichier web.xml de notre application personne sera 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. 27. 28. <?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>personne</display-name> <servlet> <servlet-name>formulairepersonne</servlet-name> <servlet-class> istia.st.servlets.personne.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.html</welcome-file> </welcome-file-list> </web-app>

Les points principaux de ce fichier de configuration sont les suivants : les lignes 7-24 sont lies la prsence de la servlet [ServletFormulaire] lignes 7-20 : la configuration d'une servlet se fait entre les balises <servlet> et </servlet>. Une application peut comporter plusieurs servlets et donc autant de sections de configuration <servlet>...</servlet>. ligne 8 : la balise <servlet-name> fixe un nom la servlet - peut tre quelconque lignes 9-11 : la balise <servlet-class> donne le nom complet de la classe correspondant la servlet. Tomcat ira chercher cette classe dans le Classpath du projet web [personne]. Il la trouvera dans [build/classes] :

lignes 12-15 : la balise <init-param> sert passer des paramtres de configuration la servlet. Ceux-ci sont gnralement lus dans la mthode init de la servlet car les paramtres de configuration de celle-ci doivent tre connus ds son premier chargement. lignes 13-14 : la balise <param-name> fixe le nom du paramtre et <param-value> sa valeur. les lignes 12-15 dfinissent un paramtre [defaultNom,"inconnu"] et les lignes 16-19 un paramtre [defaultAge,"XXX"] lignes 21-24 : la balise <servlet-mapping> sert associer une servlet (servlet-name) un modle d'URL (urlpattern). Ici le modle est simple. Il dit qu' chaque fois qu'une URL aura la forme /formulaire, il faudra utiliser la servlet formulairepersonne, c.a.d. la classe [istia.st.servlets.ServletFormulaire] (lignes 8-11). Il n'y a donc qu'une url accepte par la servlet [formulairepersonne].

3.6.4
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.

Le code de la servlet [ServletFormulaire]


package istia.st.servlets.personne; import java.io.IOException; import java.io.PrintWriter; import import import import import javax.servlet.ServletConfig; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse;

La servlet [ServletFormulaire] aura le code suivant :

Les bases du dveloppement web MVC en Java, par l'exemple

47/264

12. @SuppressWarnings("serial") 13. public class ServletFormulaire extends HttpServlet { 14. 15. // paramtres d'instance 16. private String defaultNom = null; 17. private String defaultAge = null; 18. 19. // init 20. public void init() { 21. // on rcupre les paramtres d'initialisation de la servlet 22. ServletConfig config = getServletConfig(); 23. defaultNom = config.getInitParameter("defaultNom"); 24. if (defaultNom == null) 25. defaultNom = "NNNNNNNNNNNNNNN"; 26. defaultAge = config.getInitParameter("defaultAge"); 27. if (defaultAge == null) 28. defaultAge = "AAA"; 29. } 30. 31. // GET 32. public void doGet(HttpServletRequest request, HttpServletResponse response) 33. throws IOException, ServletException { 34. 35. // on rcupre les paramtres du formulaire 36. String nom = request.getParameter("txtNom"); 37. if (nom == null) { 38. nom = defaultNom; 39. } 40. String age = request.getParameter("txtAge"); 41. if (age == null) { 42. age = defaultAge; 43. } 44. // on affiche le formulaire 45. response.setContentType("text/html"); 46. PrintWriter out = response.getWriter(); 47. out.println( 48. "<html>"+ 49. "<head>"+ 50. "<title>Personne - formulaire</title>"+ 51. "</head>"+ 52. "<body>"+ 53. "<center>"+ 54. "<h2>Personne - formulaire</h2>"+ 55. "<hr>"+ 56. "<form action='' method='post'>"+ 57. "<table>"+ 58. "<tr>"+ 59. "<td>Nom</td>"+ 60. "<td><input name='txtNom' value='"+nom+"' type='text' size='20'></td>"+ 61. "</tr>"+ 62. "<tr>"+ 63. "<td>Age</td>"+ 64. "<td><input name='txtAge' value='"+ age +"' type='text' size='3'></td>"+ 65. "</tr>"+ 66. "</table>"+ 67. "<table>"+ 68. "<tr>"+ 69. "<td><input type='submit' value='Envoyer'></td>"+ 70. "<td><input type='reset' value='Rtablir'></td>"+ 71. "<td><input type='button' value='Effacer'></td>"+ 72. "</tr>"+ 73. "</table>"+ 74. "</form>"+ 75. "</center>"+ 76. "</body>"+ 77. "</html>" 78. ); 79. } 80. 81. // POST 82. public void doPost(HttpServletRequest request, HttpServletResponse response) 83. throws IOException, ServletException { 84. // on passe la main au GET 85. doGet(request, response); 86. } 87. }

A la simple lecture de la servlet, on peut constater qu'elle est beaucoup plus complexe que la page JSP correspondante. C'est une gnralit : une servlet n'est pas adapte pour gnrer du code HTML. Ce sont les pages JSP qui sont faites pour cela. Nous aurons l'occasion d'y revenir. Explicitons quelques points importants de la servlet ci-dessus : lorsqu'une servlet est appele pour la 1re fois, sa mthode init (ligne 20) est appele. C'est le seul cas o elle est appele. si la servlet a t appele par la mthode HTTP GET, la mthode doGet (ligne 32) est appele pour traiter la requte du client. si la servlet a t appele par la mthode HTTP POST, la mthode doPost (ligne 82) est appele pour traiter la requte du client.

Les bases du dveloppement web MVC en Java, par l'exemple

48/264

La mthode init sert ici rcuprer dans [web.xml] les valeurs des paramtres d'initialisation appels " defaultNom " et " defaultAge ". La mthode init excute au chargement initial de la servlet est le bon endroit pour rcuprer le contenu du fichier [web.xml]. ligne 22 : la configuration [config] du projet web est rcupre. Cet objet reflte le contenu du fichier [WEBINF/web.xml] de l'application. ligne 23 : on rcupre dans cette configuration la valeur de type String du paramtre appel " defaultNom ". Ce paramtre aura pour valeur le nom d'une personne. S'il n'existe pas, on obtiendra la valeur null. lignes 24-25 : si le paramtre appel " defaultNom " n'existe pas, on donne une valeur par dfaut la variable [defaultNom]. lignes 26-29 : on fait de mme pour le paramtre appel " defaultAge ". La mthode doPost renvoie la mthode doGet. Cela signifie que le client pourra envoyer indiffremment ses paramtres par un POST ou un GET. La mthode doGet : ligne 32 : la mthode reoit deux paramtres request et response. request est un objet reprsentant la totalit de la requte du client. Elle est de type HttpServletRequest qui est une interface. response est de type HttpServletResponse qui est galement une interface. L'objet response sert envoyer une rponse au client. request.getParameter("param") sert rcuprer dans la requte du client la valeur du paramtre de nom param. Ligne 36, on rcupre la valeur du paramtre " txtNom ", ligne 40 celle du paramtre " txtAge ". Si ces paramtres ne sont pas prsents dans la requte, on obtient la valeur null comme valeur du paramtre. lignes 37-39 : si le paramtre " txtNom " n'est pas dans la requte, on affecte la variable " nom " le nom par dfaut " defaultNom " initialis dans la mthode init. Il est fait de mme lignes 41-43 pour l'ge. ligne 45 : response.setContentType(String) sert fixer la valeur de l'entte HTTP Content-type. Cet entte indique au client la nature du document qu'il va recevoir. Le type text/html indique un document HTML. ligne 46 : response.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. La compilation de cette servlet va produire un fichier .class dans le dossier [build/classes] du projet [personne] :

Le lecteur est invit consulter l'aide Java sur les servlets. On pourra pour cela s'aider de Tomcat. Sur la page d'entre de Tomcat 5, on a un lien [Documentation] :

Ce lien mne une page que le lecteur est invit explorer. Le lien sur la documentation des servlets est le suivant :

3.6.5

Test de la servlet

Nous sommes prts pour faire un test. Lanons le serveur Tomcat si besoin est.

Les bases du dveloppement web MVC en Java, par l'exemple

49/264

Puis demandons avec un navigateur l'URL [http://localhost:8080/personne/formulaire]. Nous demandons ici l'url [/formulaire] du contexte [/personne]. Le fichier [web.xml] de ce contexte indique que l'url [/formulaire] est traite par la servlet de nom [formulairepersonne]. Dans le mme fichier, il est indiqu que cette servlet est la classe [istia.st.servlets.ServletFormulaire]. C'est donc cette classe que Tomcat va confier le traitement de la requte client. Si la classe n'tait pas dj charge, elle le sera. Elle restera alors en mmoire pour les futures requtes. On obtient le rsultat suivant avec le navigateur interne Eclipse :

Nous obtenons les valeurs par dfaut du nom et de l'ge, celles inscrites dans le fichier [web.xml]. Demandons maintenant l'URL [http://localhost:8080/personne/formulaire?txtNom=tintin&txtAge=30] :

Cette fois, nous obtenons les paramtres passs dans la requte. Le lecteur est invit relire le code de la servlet [ServletFormulaire] s'il ne comprend pas ces deux rsultats.

3.6.6

Rechargement automatique du contexte de l'application web

Lanons Tomcat :

puis modifions le code de la servlet de la faon suivante :


1. // GET 2. public void doGet(HttpServletRequest request, HttpServletResponse response) 3. throws IOException, ServletException { 4. 5. // on rcupre les paramtres du formulaire 6. String nom = request.getParameter("txtNom"); 7. if (nom == null) { 8. nom = "--"+defaultNom+"--"; 9. } 10. String age = request.getParameter("txtAge"); 11. if (age == null) { 12. age = defaultAge; 13. } 14....

la ligne 8 a t modifie 50/264

Les bases du dveloppement web MVC en Java, par l'exemple

Sauvegardons la nouvelle classe. Cette sauvegarde va entraner, de la part d'Eclipse, une recompilation automatique de la classe [ServletFormulaire] que Tomcat va dtecter. Il va alors recharger le contexte de l'application web [personne] pour prendre en compte les modifications. Ceci apparat 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. Maintenant, modifions le fichier [web.xml] de la faon suivante :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. <?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>personne</display-name> <servlet> <servlet-name>formulairepersonne</servlet-name> ... <init-param> <param-name>defaultNom</param-name> <param-value>INCONNU</param-value> </init-param> ... </servlet> ... </web-app>

la ligne 12 a t modifie

Ceci fait, sauvegardons le nouveau fichier [web.xml]. Dans la vue [console], aucun log ne signale le rechargement du contexte de l'application. Demandons l'url [http://localhost:8080/personne/formulaire] sans relancer Tomcat :

La modification faite n'a pas t prise en compte. Relanons Tomcat [clic droit sur serveur -> Restart -> Start] :

Les bases du dveloppement web MVC en Java, par l'exemple

51/264

puis redemandons l'url [http://localhost:8080/personne/formulaire] :

Cette fois la modification faite dans [web.xml] est visible. Une modification de [web.xml] ne provoque donc pas un rechargement automatique de l'application qui prendrait en compte le nouveau fichier de configuration. Pour forcer le rechargement de l'application web, on peut alors relancer Tomcat comme nous l'avons fait mais c'est une opration assez lente. Il est prfrable de passer par l'outil [manager] d'administration des applications dployes dans Tomcat. Pour que cela soit possible, il faut qu'au sein d'Eclipse, Tomcat ait t configur comme il a t montr au paragraphe 2.5, page 27. Tout d'abord, avec le navigateur interne d'Eclipse, demandons l'url [http://localhost:8080] puis suivons le lien [Tomcat Manager], comme expliqu la fin du paragraphe 2.5 :

Ouvrons un second navigateur [clic droit sur le navigateur -> New Editor] :

Dans ce second navigateur, demandons l'url [http://localhost:8080/formulaire] :


Les bases du dveloppement web MVC en Java, par l'exemple

52/264

Modifions le fichier [web.xml] de la faon suivante puis sauvegardons-le :


1.<!-- ServletFormulaire --> 2. <servlet> 3. <servlet-name>formulairepersonne</servlet-name> 4. <servlet-class> 5. istia.st.servlets.personne.ServletFormulaire 6. </servlet-class> 7. <init-param> 8. <param-name>defaultNom</param-name> 9. <param-value>YYY</param-value> 10. </init-param> 11. <init-param> 12. <param-name>defaultAge</param-name> 13. <param-value>XXX</param-value> 14. </init-param> 15. </servlet>

Puis redemandons l'url [http://localhost:8080/formulaire]. Nous pouvons constater que la modification n'a pas t prise en compte. Maintenant, 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.xml] a t prise en compte. Dans la pratique, il est utile d'avoir un navigateur ouvert sur l'application [manager] de Tomcat pour grer ce genre de cas.

3.7

Coopration servlet et pages JSP

Lectures [ref1] : chapitre 2 : 2.3.7 Revenons aux deux architectures tudies :


Les bases du dveloppement web MVC en Java, par l'exemple

53/264

Application web Utilisateur 2 Utilisateur ServletFormulaire.java formulaire.jsp 1

Aucune de ces deux architectures n'est satisfaisante. Elles prsentent toutes les deux l'inconvnient de mlanger 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 prsentation d'informations dans un navigateur.

la solution [1] base de page JSP a l'inconvnient de mlanger code HTML et code Java au sein d'une mme page. Nous ne l'avons pas vu sur l'exemple trait qui tait basique. Mais si [formulaire.jsp] avait du vrifier la validit des paramtres [txtNom, txtAge] de la requte du client, nous aurions t obligs de mettre du code Java dans la page. Cela devient trs vite ingrable. la solution [2] base de servlet prsente le mme problme. Bien qu'il n'y ait que du code Java dans la classe, celle-ci doit gnrer un document HTML. L encore, moins que le document HTML soit basique, sa gnration devient complique et quasi impossible maintenir.

Nous allons viter le mlange des technologies Java et HTML en adoptant l'architecture suivante :

Application web ServletFormulaire.java Utilisateur formulaire.jsp Modle

l'utilisateur envoie sa requte la servlet. Celle-ci la traite et construit les valeurs des paramtres dynamiques de la page JSP [formulaire.jsp] qui va servir gnrer la rponse HTML au client. Ces valeurs forment ce qu'on appelle le modle de la page JSP. une fois termin son travail, la servlet va demander la page JSP [formulaire.jsp] de gnrer la rponse HTML au client. Elle va lui fournir en mme temps les lments dont la page JSP a besoin pour gnrer cette rponse, ces lments qui forment le modle de la page.

Nous explorons maintenant cette nouvelle architecture.

3.7.1

La servlet [ServletFormulaire2]

Dans l'architecture ci-dessus, la servlet s'appellera [ServletFormulaire2]. Elle sera construite dans le mme projet [personne] que prcdemment, ainsi que toutes les servlets venir :

Les bases du dveloppement web MVC en Java, par l'exemple

54/264

[ServletFormulaire2] est tout d'abord obtenu par un copier / coller de [ServletFormulaire] au sein d'Eclipse : slection [ServletFormulaire.java] -> clic droit -> Copy slection [istia.st.servlets.personne] -> clic droit -> Paste -> changer le nom en [ServletFormulaire2.java] Puis ensuite, nous en modifions le code de [ServletFormulaire2] de la faon 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. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. package istia.st.servlets.personne; import import import import import import java.io.IOException; javax.servlet.ServletConfig; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial") public class ServletFormulaire2 extends HttpServlet { // paramtres d'instance private String defaultNom = null; private String defaultAge = null; // init public void init() { // on rcupre les paramtres d'initialisation de la servlet ServletConfig config = getServletConfig(); defaultNom = config.getInitParameter("defaultNom"); if (defaultNom == null) defaultNom = "NNNNNNNNNNNNNNN"; defaultAge = config.getInitParameter("defaultAge"); if (defaultAge == null) defaultAge = "AAA"; } // GET public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // on rcupre les paramtres du formulaire String nom = request.getParameter("txtNom"); if (nom == null) { nom = defaultNom; } String age = request.getParameter("txtAge"); if (age == null) { age = defaultAge; } // on affiche le formulaire request.setAttribute("nom", nom); request.setAttribute("age", age); getServletContext().getRequestDispatcher("/formulaire2.jsp").forward(request, response); } // POST public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // on passe la main au GET doGet(request, response); }

Les bases du dveloppement web MVC en Java, par l'exemple

55/264

Seule la partie gnration de la rponse HTTP a chang (lignes 44-46) :


ligne 46 : la gnration de la rponse est confie la page JSP formulaire2.jsp. Celle-ci pas encore tudie, sera charge d'afficher les paramtres rcuprs dans la requte du client, un nom (lignes 35-38) et un ge (lignes 39-42). ces deux valeurs sont places dans les attributs de la requte [request], associes des cls. Les attributs d'une requte sont grs comme un dictionnaire. ligne 44 : le nom est mis dans la requte associ la cl "nom" ligne 45 : l'ge est mis dans la requte associ la cl "age" ligne 46 : demande l'affichage de la page JSP [formulaire2.jsp]. On passe en paramtre celle-ci : la requte [request] du client, ce qui va permettre la page JSP d'avoir accs aux attributs de celle-ci qui viennent d'tre initialiss par la servlet la rponse [response] qui va permettre la page JSP de gnrer la rponse HTTP au client

Une fois la classe [ServletFormulaire2] crite, son code compil apparat dans [build/classes] :

3.7.2

La page JSP [formulaire2.jsp]

La page JSP formulaire2.jsp est obtenue par copier / coller de la page [formulaire.jsp]

puis transforme de la faon 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. <%@ 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 rcupre les valeurs ncessaire l'affichage String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Personne - formulaire2</title> </head> <body> <center> <h2>Personne - 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="Rtablir"></td> <td><input type="button" value="Effacer"></td> </tr> </table>

Les bases du dveloppement web MVC en Java, par l'exemple

56/264

37. </form> 38. </center> 39. </body> 40. </html>

Seules les lignes 4-8 ont chang vis vis de [formulaire.jsp] :


ligne 6 : rcupre la valeur de l'attribut nomm "nom" dans la requte [request], attribut cr par la servlet [ServletFormulaire2]. ligne 7 : fait de mme pour l'attribut "age"

3.7.3

Configuration de l'application

Le fichier de configuration [web.xml] est modifi de la faon 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. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. <?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>personne</display-name> <!-- ServletFormulaire --> <servlet> <servlet-name>formulairepersonne</servlet-name> <servlet-class> istia.st.servlets.personne.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> <!-- ServletFormulaire 2--> <servlet> <servlet-name>formulairepersonne2</servlet-name> <servlet-class> istia.st.servlets.personne.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> <!-- Mapping ServletFormulaire --> <servlet-mapping> <servlet-name>formulairepersonne</servlet-name> <url-pattern>/formulaire</url-pattern> </servlet-mapping> <!-- Mapping ServletFormulaire 2--> <servlet-mapping> <servlet-name>formulairepersonne2</servlet-name> <url-pattern>/formulaire2</url-pattern> </servlet-mapping> <!-- fichiers d'accueil --> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>

Nous avons conserv l'existant et ajout : lignes 22-36 : une section <servlet> pour dfinir 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. Nous demandons l'URL http://localhost:8080/personne/formulaire2?txtNom=milou&txtAge=10 :

Les bases du dveloppement web MVC en Java, par l'exemple

57/264

Nous obtenons le mme rsultat que prcdemment mais la structure de notre application est dsormais plus claire : une servlet qui contient de la logique applicative et dlgue une page JSP l'envoi de la rponse au client. Nous procderons dsormais toujours de cette faon.

4 Dveloppement MVC (Modle Vue Contrleur)


Une application web a souvent une architecture 3tier :

utilisateur

Couche interface utilisateur [ui]

Couche mtier [metier]

Couche d'accs aux donnes [dao]

Donnes

la couche [dao] s'occupe de l'accs aux donnes, le plus souvent des donnes persistantes au sein d'un SGBD. Mais cela peut tre aussi des donnes qui proviennent de capteurs, du rseau, ... la couche [metier] implmente les algorithmes " mtier " de l'application. Cette couche est indpendante 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 teste en-dehors de l'interface web et notamment avec une interface console. C'est gnralement la couche la plus stable de l'architecture. Elle ne change pas si on change l'interface utilisateur ou la faon d'accder aux donnes ncessaires au fonctionnement de l'application. la couche [interface utilisateur] qui est l'interface (graphique souvent) qui permet l'utilisateur de piloter l'application et d'en recevoir des informations.

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 [mtier] si pour traiter cette demande, la couche [mtier] a besoin des donnes, elle les demande la couche [dao] chaque couche interroge rend sa rponse la couche de gauche jusqu' la rponse finale l'utilisateur. Les couches [mtier] et [dao] sont normalement utilises via des interfaces Java. Ainsi la couche [mtier] ne connat de la couche [dao] que son ou ses interfaces et ne connat pas les classes les implmentant. C'est ce qui assure l'indpendance des couches entre-elles : changer l'implmentation de la couche [dao] n'a aucune incidence sur la couche [mtier] tant qu'on ne touche pas la dfinition de l'interface de la couche [dao]. Il en est de mme entre les couches [interface utilisateur] et [mtier]. L'architecture MVC (Modle Vue Contrleur) prend place dans la couche [interface utilisateur] lorsque celle-ci est une interface web : Couche Interface Utilisateur [ui] utilisateur
1

Contrleur
4 3

Modle
5

Couche mtier [metier]

Couche d'accs aux donnes [dao]

Donnes

Vue
6

Les bases du dveloppement web MVC en Java, par l'exemple

58/264

Le traitement d'une demande d'un client se droule selon les tapes suivantes : 1. le client fait une demande au contrleur. Celui-ci voit passer toutes les demandes des clients. C'est la porte d'entre de l'application. C'est le C de MVC. 2. le contrleur C traite cette demande. Pour ce faire, il peut avoir besoin de l'aide de la couche mtier. Une fois la demande du client traite, celle-ci peut appeler diverses rponses. Un exemple classique est : une page d'erreurs si la demande n'a pu tre traite correctement une page de confirmation sinon 3. le contrleur choisit la rponse (= vue) envoyer au client. Choisir la rponse envoyer au client ncessite plusieurs tapes : choisir l'objet qui va gnrer la rponse. C'est ce qu'on appelle la vue V, le V de MVC. Ce choix dpend en gnral du rsultat de l'excution de l'action demande par l'utilisateur. lui fournir les donnes dont il a besoin pour gnrer cette rponse. En effet, celle-ci contient le plus souvent des informations calcules par le contrleur. Ces informations forment ce qu'on appelle le modle M de la vue, le M de MVC. L'tape 3 consiste donc en le choix d'une vue V et en la construction du modle M ncessaire celle-ci. 4. le contrleur C demande la vue choisie de s'afficher. Il s'agit le plus souvent de faire excuter une mthode particulire de la vue V charge de gnrer la rponse au client. Dans ce document, nous appelerons vue, aussi bien l'objet qui gnre la rponse au client que cette rponse elle-mme. La littrature MVC n'est pas explicite sur ce point. Si c'est la rponse qui devait s'appeler vue, on pourrait appeler gnrateur de vue, l'objet qui gnre cette rponse. 5. le gnrateur de vue V utilise le modle M prpar par le contrleur C pour initialiser les parties dynamiques de la rponse qu'il doit envoyer au client. 6. la rponse est envoye au client. La forme exacte de celle-ci dpend du gnrateur de vue. Ce peut tre un flux HTML, PDF, Excel, ... La mthodologie de dveloppement web MVC ne ncessite pas ncessairement d'outils externes. On peut ainsi dvelopper une application web Java avec une architecture MVC avec un simple JDK et les bibliothques de base du dveloppement web. Une mthode utilisable pour des applications simples est la suivante :

le contrleur est assur par une servlet unique. C'est le C de MVC. toutes les requtes du client contiennent un attribut action, par exemple (http://.../appli?action=liste). selon la valeur de l'attribut action, la servlet fait excuter une mthode interne de type [doAction(...)]. la mthode [doAction] excute l'action demande par l'utilisateur. Pour cela, si besoin est, elle utilise la couche [mtier]. selon le rsultat de l'excution, la mthode [doAction] dcide d'une page JSP afficher. C'est la vue V du modle MVC. la page JSP a des lments dynamiques qui doivent tre fournis par la servlet. La mthode [doAction] va fournir ces lments. C'est le modle de la vue, le M de MVC. Ce modle est plac le plus souvent dans le contexte de la requte (request.setAttribute(" cl ", "valeur "), voire moins frquemment, dans le contexte de la session ou de l'application. Une page JSP a accs ces trois contextes. la mthode [doAction] fait afficher la vue en transmettant le flux d'excution la page JSP choisie. Elle utilise pour cela, une instruction du genre [getServletContext().getRequestDispatcher(" pageJSP ").forward(request, response)].

Ce modle d'architecture (Design Pattern) MVC est appele le modle " Front Controller " ou encore modle contrleur unique. Une servlet unique traite toutes les requtes de tous les utilisateurs. Revenons l'architecture de l'application web prcdente :

Application web ServletFormulaire.java Utilisateur formulaire.jsp Modle

Cette architecture correspond l'architecture ntier suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

59/264

utilisateur

Couche interface utilisateur [ui / web]

Il n'y a en fait qu'une couche, celle de l'interface web. De faon gnrale, 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, cette architecture est suffisante. Lorsqu'on a crit plusieurs applications de ce type, on s'aperoit que les servlets de deux applications diffrentes : 1. ont le mme mcanisme pour dterminer quelle mthode [doAction] il faut excuter pour traiter l'action demande par l'utilisateur 2. ne diffrent en fait que par le contenu de ces mthodes [doAction] La tentation est alors grande de : factoriser le traitement (1) dans une servlet gnrique ignorante de l'application qui l'utilise dlguer le traitement (2) des classes externes puisque la servlet gnrique ne sait pas dans quelle application elle est utilise faire le lien entre l'action demande par l'utilisateur et la classe qui doit la traiter l'aide d'un fichier de configuration Des outils, souvent appels " frameworks ", sont apparus pour apporter les facilits prcdentes aux dveloppeurs. Le plus ancien et probablement le plus connu d'entre-eux est Struts (http://struts.apache.org/). Jakarta Struts est un projet de l'Apache Software Foundation (www.apache.org). Ce framework est dcrit dans (http://tahe.developpez.com/java/struts/). Apparu plus rcemment, le framework Spring (http://www.springframework.org/) offre des facilits analogues celles de Struts. Son utilisation a t dcrite dans plusieurs articles (http://tahe.developpez.com/java/springmvc-part1/). Nous prsentons maintenant un exemple d'architecture MVC base de servlet et pages JSP.

couche [metier]
Modles

couche [dao]

Donnes

5 Application web MVC [personne] version 1


5.1 Les vues de l'application

L'application reprend le formulaire utilis dans les prcdents exemples. La premire page de l'application est la suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

60/264

Nous appellerons cette vue, la vue [formulaire]. Si on fait des saisies correctes, celles-ci sont affiches dans une vue qui sera appele [rponse] :

vue [rponse]

vue [formulaire] Si les saisies sont incorrectes, les erreurs sont signales dans une vue appele [erreurs] :

vue [erreurs]

vue [formulaire]

5.2

Architecture de l'application

L'application web [personne1] aura l'architecture suivante :

Application web
couche [web]

ServletPersonne Utilisateur [formulaire] [rponse] [erreurs]


Cette architecture est une architecture 1tier : il n'y a pas de couches [mtier] ou [dao], seulement une couche [web]. [ServletPersonne] est le contrleur de l'application qui traite toutes les requtes des clients. Pour rpondre ceux-ci, il utilise l'une des trois vues [formulaire, rponse, erreurs]. Il nous faut dterminer comment le contrleur [ServletPersonne] dtermine l'action qu'il doit faire la rception d'une requte d'un utilisateur. Une requte client est un flux HTTP qui diffre selon qu'elle est faite avec une commande GET ou POST. Requte GET
Les bases du dveloppement web MVC en Java, par l'exemple

Modles

61/264

Dans ce cas, le flux HTTP ressemble ce qui suit :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. GET /URL HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive [ligne vide]

La ligne 1 prcise l'url demande, par exemple :


GET /personne1/ HTTP/1.1

On peut utiliser cette url pour prciser l'action faire. On peut utiliser diverses mthodes : 1. 2. un paramtre de l'url prcise l'action, par exemple [/appli?action=ajouter&id=4]. Ici le paramtre [action] indique au contrleur l'action qui lui est demande. le dernier lment de l'url prcise l'action, par exemple [/appli/ajouter?id=4]. Ici, le dernier lment de l'url [/ajouter] est utilis par le contrleur pour dterminer l'action qu'il doit faire.

D'autres solutions sont possibles. Les deux prcdentes sont courantes. Requte POST Dans ce cas, le flux HTTP ressemble ce qui suit :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. POST /URL HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=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 prcise l'url demande, par exemple :


POST /personne1/main HTTP/1.1

On peut utiliser cette url pour prciser l'action faire comme pour le GET. Dans le cas du GET, le paramtre [action] tait intgre dans l'URL. Cela peut tre galement le cas ici comme dans :
POST /appli?action=ajouter&id=4 HTTP/1.1

Mais le paramtre [action] peut tre galement inclus dans les paramtres posts (ligne 15 ci-dessus) comme dans :
POST /appli HTTP/1.1 ... [ligne vide] action=ajouter&id=4

Dans ce qui suit, nous utiliserons ces diffrentes techniques pour indiquer au contrleur ce qu'il doit faire :

intgrer le paramtre action dans l'url demande :


POST /appli?action=ajouter&id=4 HTTP/1.1

poster le paramtre action :


POST /appli HTTP/1.1 ... [ligne vide] action=ajouter&id=4

utiliser le dernier lment de l'url comme nom de l'action :


POST /appli/ajouter?id=4 HTTP/1.1

Les bases du dveloppement web MVC en Java, par l'exemple

62/264

5.3

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-01] de l'application web [personne1], on suivra la dmarche dcrite au paragraphe 3.1, page 29.

On ne gardera pas le contexte [mvc-personne-01] propos par dfaut. On choisira [personne1] comme indiqu ci-dessous :

Le rsultat obtenu est le suivant :

Si d'aventure, on veut changer le contexte de l'application web, on outilisera l'option [clic droit sur projet -> Properties -> J2EE] :

On indiquera en [1] le nouveau contexte.


Les bases du dveloppement web MVC en Java, par l'exemple

63/264

Nous allons crer 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 complt, le projet sera le suivant :

le contrleur [ServletPersonne] est dans le dossier [src] les pages JSP des vues [formulaire, rponse, erreurs] sont dans le dossier [WEB-INF/vues], ce qui empche l'utilisateur de les demander directement comme le montre l'exemple ci-dessous :

Les bases du dveloppement web MVC en Java, par l'exemple

64/264

Nous dcrivons maintenant les diffrents composants de l'application web [/personne1]. Le lecteur est invit les crer au fil de sa lecture.

5.4

Configuration de l'application web [personne1]

Le fichier web.xml de l'application /personne1 sera le suivant :


1.<?xml version="1.0" encoding="UTF-8"?> 2.<web-app id="WebApp_ID" version="2.4" 3. xmlns="http://java.sun.com/xml/ns/j2ee" 4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5. xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 6. 7. <display-name>mvc-personne-01</display-name> 8. <!-- ServletPersonne --> 9. <servlet> 10. <servlet-name>personne</servlet-name> 11. <servlet-class> 12. istia.st.servlets.personne.ServletPersonne 13. </servlet-class> 14. <init-param> 15. <param-name>urlReponse</param-name> 16. <param-value> 17. /WEB-INF/vues/reponse.jsp 18. </param-value> 19. </init-param> 20. <init-param> 21. <param-name>urlErreurs</param-name> 22. <param-value> 23. /WEB-INF/vues/erreurs.jsp 24. </param-value> 25. </init-param> 26. <init-param> 27. <param-name>urlFormulaire</param-name> 28. <param-value> 29. /WEB-INF/vues/formulaire.jsp 30. </param-value> 31. </init-param> 32. </servlet> 33. <!-- Mapping ServletPersonne--> 34. <servlet-mapping> 35. <servlet-name>personne</servlet-name> 36. <url-pattern>/main</url-pattern> 37. </servlet-mapping> 38. <!-- fichiers d'accueil --> 39. <welcome-file-list> 40. <welcome-file>index.jsp</welcome-file> 41. </welcome-file-list> 42.</web-app> 43.

Que dit ce fichier de configuration ? lignes 34-37 : l'URL /main est traite par la servlet nomme personne lignes 10-13 : la servlet nomme personne est une instance de la classe [ServletPersonne] lignes 14-19 : dfinissent un paramtre de configuration nomm [urlReponse]. C'est l'url de la vue [rponse]. lignes 20-25 : dfinissent un paramtre de configuration nomm [urlErreurs]. C'est l'url de la vue [erreurs]. lignes 26-31 : dfinissent un paramtre de configuration nomm [urlFormulaire]. C'est l'url de la vue [formulaire]. ligne 40 : [index.jsp] sera la page d'accueil de l'application.

Les url des pages JSP des vues [formulaire, rponse, erreurs] font l'objet chacune d'un paramtre de configuration. Cela permet de les dplacer sans avoir recompiler l'application.
Les bases du dveloppement web MVC en Java, par l'exemple

65/264

Lorsque l'utilisateur va demander l'url [/personne1], c'est le fichier [index.jsp] qui va envoyer la rponse (fichier d'accueil, ligne 40). Ce fichier est la racine du dossier [WebContent] :

Son contenu est le suivant :


1. 2. 3. 4. 5. 6. <%@ 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("/personne1/main"); %>

La page [index.jsp] se contente de rediriger le client vers l'url [/personne1/main]. Ainsi, lorsque le navigateur demande l'url [/personne1], [index.jsp] lui envoie la rponse HTTP suivante :
1. 2. 3. 4. 5. 6. 7. HTTP/1.x 302 Dplac Temporairement Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=9F5E5BFA29643FC6B1601EEED907E1F9; Path=/personne1 Location: http://localhost:8080/personne1/main Content-Type: text/html;charset=ISO-8859-1 Content-Length: 0 Date: Thu, 18 May 2006 15:45:23 GMT

ligne 1 : rponse HTTP/1.1 pour indiquer au serveur de se rediriger vers une autre URL ligne 4 : l'URL vers laquelle doit se rediriger le navigateur

Aprs cette rponse, le navigateur va donc demander l'url [/personne1/main] comme on le lui demande (ligne 4). Le fichier [web.xml] de l'application [/personne1] indique que cette demande va tre traite par le contrleur [ServletPersonne] (lignes 35-36).

5.5

Le code des vues

Nous commenons notre criture de l'application web par celle de ses vues. Celles-ci permettent de cerner les besoins de l'utilisateur en termes d'interface graphique et peuvent tre testes sans la prsence du contrleur.

5.5.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
rle

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 reue initialement par le navigateur pour effacer les contenu des champs de saisie [1] et [2] 66/264

Les bases du dveloppement web MVC en Java, par l'exemple

Elle est gnre par la page JSP [formulaire.jsp]. Son modle est constitu des lments 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], c.a.d. l'url du contrleur [ServletPersonne]. Le code de la page JSP [formulaire.jsp] qui gnre la vue [formulaire] 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. 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"> <% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %> <html> <head> <title>Personne - formulaire</title> </head> <body> <center> <h2>Personne - 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="Rtablir"></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 rcuprer dans la requte [request] les lments [nom, age] de son modle. Dans le fonctionnement normal de l'application, ce sera le contrleur [ServletPersonne] qui construira ce modle. lignes 18-38 : la page JSP va gnrer un formulaire HTML (balise <form>) ligne 18 : la balise <form> n'a pas d'attribut action pour dsigner l'url qui devra traiter les valeurs postes par le bouton [Envoyer] de type submit (ligne 32). Les valeurs du formulaire seront alors postes l'url partir de laquelle a t obtenue le formulaire, c'est dire l'url du contrleur [ServletPersonne]. Ainsi celui-ci est-il utilis la fois pour gnrer le formulaire vide demand initialement par un GET et traiter les donnes saisies qui lui seront postes avec le bouton [Envoyer]. les valeurs postes sont celles des champs HTML [txtNom] (ligne 22) et [txtAge] (ligne 26) et [action] (ligne 37). Ce dernier paramtre va permettre au contrleur de savoir ce qu'il doit faire. l'affichage initial du formulaire, les champs de saisie [txtNom, txtAge] sont initialiss respectivement avec les variables [nom] (ligne 22) et [age] (ligne 26). Ces variables obtiennent leurs valeurs d'attributs de la requte (lignes 67), attributs qu'on sait initialiss par la servlet. C'est donc cette dernire qui fixe le contenu initial des champs de saisie du formulaire. ligne 33 : le bouton [Rtablir] de type [reset] permet de remettre le formulaire dans l'tat o il tait lorsque le navigateur l'a reu. ligne 34 : le bouton [Effacer] de type [reset] n'a pour l'instant aucune fonction.

Par la suite, nous appellerons cette vue, la vue [formulaire(nom, age)] lorsque nous voudrons prciser la fois le nom de la vue et son modle. Par ailleurs, on se rappellera que lorsque l'utilisateur clique sur le bouton [Envoyer], les paramtres [txtNom, txtAge] sont posts l'url [/personne1/main].

Les bases du dveloppement web MVC en Java, par l'exemple

67/264

5.5.2

La vue [reponse]

Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides :

vue [rponse]

vue [formulaire] Elle est gnre par la page JSP [reponse.jsp]. Son modle est constitu des lments suivants :

[nom] : un nom (String) qui sera trouv dans les attributs de session, associ la cl "nom" [age] : un ge (String) qui sera trouv dans les attributs de session, associ la cl "age"

Le code de la page JSP [reponse.jsp] 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. 27. 28. 29. <%@ 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 rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne - rponse</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 rcuprer dans la requte [request] les lments [nom, age] de son modle. Dans le fonctionnement normal de l'application, ce sera le contrleur [ServletPersonne] qui construira ce modle. les lments [nom, age] du modle sont ensuite affichs lignes 20 et 24

Par la suite, nous appelons cette vue, la vue [rponse(nom, age)].

5.5.3

La vue [erreurs]

Cette vue signale les erreurs de saisie dans le formulaire :

Les bases du dveloppement web MVC en Java, par l'exemple

68/264

vue [erreurs]

vue [formulaire] Elle est gnre par la page JSP [erreurs.jsp]. Son modle est constitu des lments suivants :

[erreurs] : une liste (ArrayList) de messages d'erreur qui sera trouve dans les attributs de requte, associe la cl "erreurs"

Le code de la page JSP [erreurs.jsp] 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. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ page import="java.util.ArrayList" %> <% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0;i<erreurs.size();i++){ out.println("<li>" + (String) erreurs.get(i) + "</li>\n"); }//for %> </ul> </body> </html>

ligne 8 : la page JSP commence par rcuprer dans la requte [request] l'lment [erreurs] de son modle. Cet lment reprsente un objet de type ArrayList d'lments de type String. Ces lments sont des messages d'erreurs. Dans le fonctionnement normal de l'application, ce sera le contrleur [ServletPersonne] qui construira ce modle. lignes 18-22 : affichent la liste des messages d'erreur. Pour cela, on est amen crire du code Java dans le corps HTML de la page. On doit toujours viser rduire celui-ci au minimum pour ne pas polluer le code HTML. Nous verrons ultrieurement qu'il existe des solution permettant de rduire la quantit de code Java dans les pages JSP. ligne 4 : noter la balise d'importation des paquetages ncessaires la page JSP

Par la suite, nous appelons cette vue, la vue [erreurs(erreurs)].

5.6

Tests des vues

Il est possible de tester la validit des pages JSP sans avoir crit le contrleur. Pour cela deux conditions : il faut pouvoir les demander directement l'application sans passer par le contrleur il faut que la page JSP initialise elle-mme le modle qui normalement sera construit par le contrleur Pour raliser ces tests, nous dupliquons les pages JSP des vues dans le dossier [/WebContent/JSP] du projet Eclipse :

Les bases du dveloppement web MVC en Java, par l'exemple

69/264

Puis dans le dossier JSP, les pages sont modifies de la faon suivante : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. ... <% // -- test : on cre le modle de la page request.setAttribute("nom","tintin"); request.setAttribute("age","30");

%>

<% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %> <html> <head> ...

Les lignes 3-7 ont t ajoutes pour crer le modle dont a besoin la page lignes 11-12. [reponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. ... <% // -- test : on cre le modle de la page request.setAttribute("nom","milou"); request.setAttribute("age","10");

%>

<% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %> <html> <head> ...

Les lignes 3-7 ont t ajoutes pour crer le modle dont a besoin la page lignes 11-12. [erreurs.jsp] :
1. ... 2. 3. <% 4. // -- test : on cre le modle de la page 5. ArrayList<String> erreurs1=new ArrayList<String>(); 6. erreurs1.add("erreur1"); 7. erreurs1.add("erreur2"); 8. request.setAttribute("erreurs",erreurs1); 9. %> 10. 11. <% 12. // on rcupre les donnes du modle 13. ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); 14. %> 15. 16. 17. <html> Les bases du dveloppement web MVC en Java, par l'exemple

70/264

18. <head> 19. ...

Les lignes 3-9 ont t ajoutes pour crer le modle dont a besoin la page ligne 13. Lanon Tomcat si ce n'est dj fait puis demandons les url suivantes :

Nous obtenons bien les vues attendues. Maintenant que nous avons une confiance raisonnable dans les pages JSP de l'application, nous pouvons passer l'criture de son contrleur [ServletPersonne].

5.7

Le contrleur [ServletPersonne]

Il reste crire le coeur de notre application web, le contrleur. Son rle consiste : - rcuprer la requte du client, - traiter l'action demande par celui-ci, - envoyer en rponse la vue approprie. Le contrleur [ServletPersonne] va traiter les actions suivantes : n 1 2 demande [GET /personne1/main] [POST /personne1/main] avec paramtres [txtNom, txtAge, action] posts origine url tape par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement - envoyer la vue [formulaire] vide - vrifier les valeurs des paramtres [txtNom, txtAge] - si elles sont incorrectes, envoyer la vue [erreurs(erreurs)] - si elles sont correctes, envoyer la vue [reponse(nom,age)]

L'application dmarre lorsque l'utilisateur demande l'url [/personne1/main]. D'aprs le fichier [web.xml] de l'application (cf paragraphe 5.4, page 65), cette demande est traite par une instance de type ServletPersonne que nous dcrivons maintenant.

5.7.1
1. 2. 3. 4. 5. 6. 7. 8.

Squelette du contrleur
package istia.st.servlets.personne; import import import import java.io.IOException; java.util.ArrayList; java.util.HashMap; java.util.Map;

Le code du contrleur [ServletPersonne] est le suivant :

import javax.servlet.ServletConfig;

Les bases du dveloppement web MVC en Java, par l'exemple

71/264

9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.

import import import import

javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { ... // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... }

// affichage vue initiale void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 32. ... 33. } 34. 35. // validation du formulaire 36. void doValidationFormulaire(HttpServletRequest request, 37. HttpServletResponse response) throws ServletException, IOException{ 38. ... 39. } 40. 41. // post 42. public void doPost(HttpServletRequest request, HttpServletResponse response) 43. throws IOException, ServletException { 44. // on passe la main au GET 45. doGet(request, response); 46. } 47. }

lignes 20-22 : la mthode [init] excute au chargement initial de la servlet lignes 25-28 : la mthode [doGet] appele par le serveur web lorsqu'une requte de type GET a t faite l'application lignes 42-46 : la mthode [doPost] appele par le serveur web lorsqu'une requte de type POST a t faite l'application. Comme il est montr, celle-ci sera traite par la mthode [doGet] galement (ligne 45). lignes 31-33 : la mthode [doInit] traite l'action n 1 [GET /personne1/main] lignes 36-39 : la mthode [doValidationFormulaire] traite l'action n 2 [POST /personne1/main] avec les paramtres posts [txtNom, txtAge, action].

Nous dcrivons maintenant les diffrentes mthodes du contrleur

5.7.2

Initialisation du contrleur

Lorsque la classe du contrleur est charge par le conteneur de servlets, sa mthode [init] est excute. Ce sera la seule fois. Une fois charge en mmoire, le contrleur y restera et traitera les requtes des diffrents clients. Chaque client fait l'objet d'un thread d'excution et les mthodes du contrleur sont ainsi excutes simultanment par diffrents threads. On rappelle que, pour cette raison, le contrleur ne doit pas avoir de champs que ses mthodes pourraient modifier. Ses champs doivent tre en lecture seule. Ils sont initialiss par la mthode [init] dont c'est le rle principal. Cette mthode a en effet la particularit d'tre excute une unique fois par un seul thread. Il n'y a donc pas de problmes d'accs concurrents aux champs du contrleur dans cette mthode. La mthode [init] a pour but d'initialiser les objets ncessaires l'application web et qui seront partags en lecture seule par tous les threads clients. Ces objets partags peuvent tre placs en deux endroits : - les champs privs du contrleur - le contexte d'excution de l'application (ServletContext) Le code de la mthode [init] du contrleur [ServletPersonne] est le suivant :
1. package istia.st.servlets.personne; 2. 3. ... 4. @SuppressWarnings("serial") 5. public class ServletPersonne extends HttpServlet { 6. // paramtres d'instance 7. private String urlErreurs = null; 8. private ArrayList erreursInitialisation = new ArrayList<String>(); 9. private String[] paramtres={"urlFormulaire","urlReponse"}; 10. private Map params=new HashMap<String,String>(); 11. 12. // init Les bases du dveloppement web MVC en Java, par l'exemple

72/264

13. @SuppressWarnings("unchecked") 14. public void init() throws ServletException { 15. // on rcupre les paramtres d'initialisation de la servlet 16. ServletConfig config = getServletConfig(); 17. // on traite les autres paramtres d'initialisation 18. String valeur=null; 19. for(int i=0;i<paramtres.length;i++){ 20. // valeur du paramtre 21. valeur=config.getInitParameter(paramtres[i]); 22. // paramtre prsent ? 23. if(valeur==null){ 24. // on note l'erreur 25. erreursInitialisation.add("Le paramtre ["+paramtres[i]+"] n'a pas t initialis"); 26. }else{ 27. // on mmorise la valeur du paramtre 28. params.put(paramtres[i],valeur); 29. } 30. // l'url de la vue [erreurs] a un traitement particulier 31. urlErreurs = config.getInitParameter("urlErreurs"); 32. if (urlErreurs == null) 33. throw new ServletException( 34. "Le paramtre [urlErreurs] n'a pas t initialis"); 35. } 36. } 37. ... 38. }

ligne 16 : on rcupre la configuration de l'application web, c.a.d. le contenu du fichier [web.xml] lignes 19-29 on rcupre les paramtres d'initialisation de la servlet dont les noms sont dfinis dans le tableau [paramtres] de la ligne 9 ligne 21 : la valeur du paramtre est rcupre ligne 25 : si le paramtre est absent, l'erreur est ajoute la liste des erreurs [erreursInitialisation] initialement vide (ligne 8). ligne 28 : si le paramtre est prsent, il est mmoris avec sa valeur dans le dictionnaire [params] initialement vide (ligne 10). lignes 31-35 : le paramtre [urlErreurs] doit tre obligatoirement prsent car il dsigne l'url de la vue [erreurs] capable d'afficher les ventuelles erreurs d'initialisation. S'il n'existe pas, on interrompt l'application en lanant une [ServletException] (ligne 33).

5.7.3

La mthode [doGet]

La mthode [doGet] traite aussi bien les demandes GET que POST la servlet, du fait que la mthode [doPost] renvoie sur la mthode [doGet]. Son code 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. package istia.st.servlets.personne; ... @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres={"urlFormulaire","urlReponse"}; private Map params=new HashMap<String,String>(); ... @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // on vrifie comment s'est passe l'initialisation de la servlet if (erreursInitialisation.size() != 0) { // on passe la main la page d'erreurs request.setAttribute("erreurs", erreursInitialisation); getServletContext().getRequestDispatcher(urlErreurs).forward( request, response); // fin return; } // on rcupre la mthode d'envoi de la requte String mthode=request.getMethod().toLowerCase(); // on rcupre l'action excuter String action=request.getParameter("action"); // action ? if(action==null){ action="init"; } // excution action

Les bases du dveloppement web MVC en Java, par l'exemple

73/264

36. if(mthode.equals("get") && action.equals("init")){ 37. // dmarrage application 38. doInit(request,response); 39. return; 40. } 41. if(mthode.equals("post") && action.equals("validationFormulaire")){ 42. // validation du formulaire de saisie 43. doValidationFormulaire(request,response); 44. return; 45. } 46. // autres cas 47. doInit(request,response); 48. } 49. 50. // post 51. public void doPost(HttpServletRequest request, HttpServletResponse response) 52. throws IOException, ServletException { 53. // on passe la main au GET 54. doGet(request, response); 55. } 56. }

lignes 18-25 : on vrifie que la liste des erreurs d'initialisation est vide. Si ce n'est pas le cas, on fait afficher la vue [erreurs(erreursInitialisation)] qui va signaler la ou les erreurs. Pour comprendre ce code, il faut se rappeler le modle de la vue [erreurs] :
<% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); %>

La vue [erreurs] attend un lment de cl " erreurs " dans la requte. Le contrleur cre cet lment ligne 20. ligne 28 : on rcupre la mthode [get] ou [post] que le client a utilise pour faire sa requte ligne 30 : on rcupre la valeur du paramtre [action] de la requte. Rappelons que dans notre application seule la requte n 2 [POST /personne1/main] a le paramtre [action]. Dans cette requte, il a la valeur [validationFormulaire]. lignes 31-34 : si le paramtre [action] n'est pas prsent, on lui affecte la valeur " init ". Ce sera le cas lors de la requte initiale n 1 [GET /personne1/main]. lignes 36-40 : traitement de la requte n 1 [GET /personne1/main]. lignes 41-45 : traitement de la requte n 2 [POST /personne1/main]. ligne 47 : si on n'est pas dans l'un des deux cas prcdents, on fait comme si on tait dans le cas n 1

5.7.4

La mthode [doInit]

Cette mthode traite la requte n 1 [GET /personne1/main]. Sur cette requte, elle doit envoyer la vue [formulaire(nom,age)] vide. Son code 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. package istia.st.servlets.personne; ... @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres={"urlFormulaire","urlReponse"}; private Map params=new HashMap<String,String>(); ... // affichage vue initiale void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on envoie le formulaire vide request.setAttribute("nom", ""); request.setAttribute("age", ""); getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; } ... }

lignes 18-19 : la vue [formulaire] est affiche. Rappelons le modle attendu par cette vue :

<% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); Les bases du dveloppement web MVC en Java, par l'exemple

74/264

%>

String age=(String)request.getAttribute("age");

lignes 16-17 : le modle [nom,age] de la vue [formulaire] est initialis avec des chanes vides.

5.7.5

La mthode [doValidationFormulaire]

Cette mthode traite la requte n 2 [POST /personne1/main] dans laquelle les paramtres posts sont [action, txtNom, txtAge]. Son code 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. package istia.st.servlets.personne; ... @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres={"urlFormulaire","urlReponse"}; private Map params=new HashMap<String,String>(); ... // validation du formulaire void doValidationFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on rcupre les paramtres String nom = request.getParameter("txtNom"); String age = request.getParameter("txtAge"); // vrification des paramtres ArrayList<String> erreursAppel = new ArrayList<String>(); // le nom doit tre non vide nom = nom.trim(); if (nom.equals("")) erreursAppel.add("Le champ [nom] n'a pas t rempli"); // l'ge doit tre un entier >=0 if (!age.matches("^\\s*\\d+\\s*$")) erreursAppel.add("Le champ [age] est erron"); // des erreurs dans les paramtres ? if (erreursAppel.size() != 0) { // on envoie la page d'erreurs request.setAttribute("erreurs", erreursAppel); getServletContext().getRequestDispatcher(urlErreurs).forward(request, response); return; } // les paramtres sont corrects - on envoie la page rponse request.setAttribute("nom", nom); request.setAttribute("age", age); getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request, response); return; } ... }

lignes 16-17 : on rcupre dans la requte du client les valeurs des paramtres "txtNom" et "txtAge". lignes 19-26 : la validit de ces deux paramtres est vrifie lignes 28-33 : si l'un des paramtres est erron, on fait afficher la vue [erreurs(erreursAppel)]. Rappelons le modle de cette vue :
<% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); %>

lignes 35-38 : si les deux paramtres " txtNom " et " txtAge " rcuprs ont des valeurs valides, on fait afficher la vue [reponse(nom,age)]. Il faut se rappeler le modle de la vue [reponse] :
<% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); %>

5.8

Tests

Incluons le projet [mvc-personne-01] dans les applications de Tomcat en suivant la procdure dcrite page 34 du paragraphe 3.3 : Les bases du dveloppement web MVC en Java, par l'exemple 75/264

Lancez Tomcat. Ceci fait, on pourra reprendre les tests montrs en exemple au paragraphe 5.1, page 60. On pourra en rajouter. On pourra par exemple supprimer la prsence d'un des paramtres de configuration urlXXX dans web.xml et voir le rsultat. Ainsi ci-dessous, l'un des paramtres est mis en commentaires dans [web.xml] :
<!-<init-param> <param-name>urlFormulaire</param-name> <param-value> /WEB-INF/vues/formulaire.jsp </param-value> </init-param> -->

On lance / relance Tomcat et on demande l'url [http://localhost:8080/personne1/main]. On obtient la rponse suivante :

6 Application web MVC [personne] version 2


Nous allons maintenant proposer des variantes de l'application [/personne1] prcdente que nous appellerons [/personne2, /personne3, ...]. Ces variantes ne modifient pas l'architecture initiale de l'application qui reste la suivante :

Application web
couche [web]

ServletPersonne Utilisateur [formulaire] [rponse] [erreurs]


Pour ces variantes, nous ferons plus court dans nos explications. Nous ne prsenterons que les modifications apportes vis vis de la version prcdente. Modles

6.1

Introduction

Nous nous proposons maintenant d'ajouter notre application une gestion de session. Rappelons les points suivants : le dialogue HTTP client-serveur est une suite de squences demande-rponse dconnectes entre elles la session sert de mmoire entre diffrentes squences demande-rponse d'un mme utilisateur. S'il y a N utilisateurs, il y a N sessions. La squence d'cran suivante montre ce qui est maintenant dsir dans le fonctionnement de l'application : Echange n 1

Les bases du dveloppement web MVC en Java, par l'exemple

76/264

demande

rponse

La nouveaut vient du lien de retour au formulaire qui a t rajout dans la vue [erreurs]. Echange n 2
demande rponse

Dans l'change n 1, l'utilisateur a donn pour le couple (nom,ge) les valeurs (xx,yy). Si au cours de l'change, le serveur a eu connaissance de ces valeurs, la fin de l'change il les "oublie". Or on peut constater que lors de l'change n 2 il est capable de rafficher leurs valeurs dans sa rponse. C'est la notion de session qui, ici, va permettre au serveur web de mmoriser des donnes au fil des changes successifs client-serveur. Il y a d'autres solutions possibles pour rsoudre ce problme. Au cours de l'change n1, le serveur va mmoriser dans la session le couple (nom,age) que le client lui a envoy afin d'tre capable de l'afficher au cours de l'change n 2. Voici un autre exemple de mise en oeuvre de la session entre deux changes : Echange n 1
demande rponse

La nouveaut vient du lien de retour au formulaire qui a t rajout dans la page de la rponse. Echange n 2
Les bases du dveloppement web MVC en Java, par l'exemple

77/264

demande

rponse

6.2

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-02] de l'application web [/personne2], nous allons dupliquer le projet Eclipse [mvcpersonne-01] afin de rcuprer l'existant. Pour cela procdons de la manire suivante : [clic droit sur projet mvc-personne-01 -> Copy] :

puis [clic droit dans Package Explorer -> Paste] :

- 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 dveloppement web MVC en Java, par l'exemple

78/264

Il est identique pour l'instant au projet [mvc-personne-01]. Il va nous falloir faire quelques modifications la main avant de pouvoir l'utiliser. Allons dans la vue [Servers] et essayons d'ajouter cette nouvelle application celles gres par Tomcat :

On voit qu'en [1], le nouveau projet [mvc-personne-02] n'est pas vu par Tomcat. Pour qu'il le voit, un fichier de configuration du projet [mvc-personne-02] doit tre modifi. Utilisons l'option [File / Open File] pour ouvrir le fichier [<mvc-personne02>/.settings/.component] :
1. 2. 3. 4. 5. 6. 7. 8. 9. <?xml version="1.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 dsigne le nom du module web dployer au sein de Tomcat. Ce nom est, ici, le mme que celui du projet [mvcpersonne-01]. Nous le changeons en [mvc-personne-02] :
<wb-module deploy-name="mvc-personne-02">

Par ailleurs, nous pouvons en profiter pour modifier, ligne 7, 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. En revanche, je n'ai pas vu comment faire la premire sans passer par le fichier de configuration.
Les bases du dveloppement web MVC en Java, par l'exemple

79/264

Ceci fait, nous sauvegardons le nouveau fichier [.content] puis nous quittons et relanons Eclipse afin qu'il soit pris en compte. Une fois Eclipse relanc, essayons de faire l'opration qui a chou prcdemment :

Cette fois-ci, le projet [mvc-personne-02] est bien vu. Nous l'ajoutons aux projets configurs pour tre excuts par Tomcat :

6.3

Configuration de l'application web [personne2]

Le fichier web.xml de l'application /personne2 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. 27. 28. 29. 30. 31. 32. 33. <?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-02</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>urlControleur</param-name> <param-value>

Les bases du dveloppement web MVC en Java, par l'exemple

80/264

34. main 35. </param-value> 36. </init-param> 37. <init-param> 38. <param-name>lienRetourFormulaire</param-name> 39. <param-value> 40. Retour au formulaire 41. </param-value> 42. </init-param> 43. </servlet> 44. <!-- Mapping ServletPersonne--> 45. <servlet-mapping> 46. <servlet-name>personne</servlet-name> 47. <url-pattern>/main</url-pattern> 48. </servlet-mapping> 49. <!-- fichiers d'accueil --> 50. <welcome-file-list> 51. <welcome-file>index.jsp</welcome-file> 52. </welcome-file-list> 53. </web-app> 54.

Ce fichier est identique celui de la version prcdente, si ce n'est qu'il dclare deux nouveaux paramtres d'initialisation : ligne 6 : le nom d'affichage de l'application web a chang en [mvc-personne-02] lignes 31-36 : dfinissent le paramtre de configuration nomm [urlControleur] qui est l'url [main] qui mne la servlet [ServletPersonne] lignes 37-42 : dfinissent un paramtre de configuration nomm [lienRetourFormulaire] qui est le texte du lien de retour vers le formulaire, des pages JSP [erreurs.jsp] et [reponse.jsp].

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("/personne2/main");

6. %>

ligne 5 : la page [index.jsp] redirige le client vers l'url du contrleur [ServletPersonne] de l'application [/personne2].

6.4
6.4.1

Le code des vues


La vue [formulaire]

Cette vue est identique celle de la version prcdente :

Elle est gnre par page JSP [formulaire.jsp] suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. <%@ 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 rcupre les donnes du modle String nom=(String)session.getAttribute("nom"); String age=(String)session.getAttribute("age"); String urlAction=(String)request.getAttribute("urlAction"); %> <html>

Les bases du dveloppement web MVC en Java, par l'exemple

81/264

12. <head> 13. <title>Personne - formulaire</title> 14. </head> 15. <body> 16. <center> 17. <h2>Personne - formulaire</h2> 18. <hr> 19. <form action="<%=urlAction%>" method="post"> 20. <table> 21. <tr> 22. <td>Nom</td> 23. <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> 24. </tr> 25. <tr> 26. <td>Age</td> 27. <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> 28. </tr> 29. <tr> 30. </table> 31. <table> 32. <tr> 33. <td><input type="submit" value="Envoyer"></td> 34. <td><input type="reset" value="Rtablir"></td> 35. <td><input type="button" value="Effacer"></td> 36. </tr> 37. </table> 38. <input type="hidden" name="action" value="validationFormulaire"> 39. </form> 40. </center> 41. </body> 42. </html>

Les nouveauts :

ligne 19, le formulaire a dsormais 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 variable [urlAction] aura la valeur action="main". La vue [formulaire] est affiche aprs 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 prcise pas d'Url absolue (commenant par /) mais une Url relative (ne commenant pas par /), la navigateur va utiliser la premire partie de l'Url de la page actuellement affiche [/personne2] et y ajouter l'Url relative. L'Url du POST sera donc [/personne2/main], celle du contrleur. Cette requte POST sera accompagne des paramtres [txtNom, txtAge, action] des lignes 23, 27 et 38. ligne 8 : on rcupre la valeur de l'lment [urlAction] du modle. Elle est cherche dans les attributs de la requte courante. Elle sera utilise ligne 19. lignes 6-7 : on rcupre les valeurs des lments [nom, age] du modle. Ils sont cherchs dans les attributs de la session et non plus dans ceux de la requte comme dans la version prcdente. Cela pour rpondre aux besoins de la requte [GET /personne2/main?action=retourFormulaire] du lien des vues [rponse] et [erreurs]. Avant d'afficher ces deux vues, le contrleur place dans la session les donnes saisies dans le formulaire, ce qui lui permet de les retrouver lorsque l'utilisateur utilise le lien [Retour au formulaire] des vues [rponse] et [erreurs].

6.4.2

La vue [reponse]

Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides :

Les bases du dveloppement web MVC en Java, par l'exemple

82/264

Vis vis de la version prcdente, la nouveaut vient du lien [Retour au formulaire]. La vue est gnre par la page JSP [reponse.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. <%@ 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 rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %> <html> <head> <title>Personne</title> </head> <body> <h2>Personne - rponse</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. Ce lien a deux composantes :

la cible [href="?action=retourFormulaire"]. La vue [rponse] est affiche aprs le POST du formulaire [formulaire.jsp] l'url [/personne2/main]. C'est donc cette dernire Url qui est affiche dans le navigateur lorsque la vue [rponse] est affiche. Un clic sur le lien [Retour au formulaire] va alors provoquer un Get du navigateur vers l'Url prcise par l'attribut [href] du lien, ici "?action=retourFormulaire". En l'absence d'Url dans [href], le navigateur va utiliser celle de la vue actuellement affiche, c.a.d. [/personne2/main]. Au final, le clic sur le lien [Retour au formulaire] va provoquer un Get du navigateur vers l'url [/personne2/main?action=retourFormulaire], c.a.d l'url du contrleur de l'application accompagne du paramtre [action] pour lui indiquer ce qu'il doit faire. le texte du lien. Celui-ci fera partie du modle transmis la page par le contrleur et rcupr ligne 10.

6.4.3

La vue [erreurs]

Cette vue signale les erreurs de saisie dans le formulaire :

Vis vis de la version prcdente, la nouveaut vient du lien [Retour au formulaire]. La vue est gnre par la page JSP [erreurs.jsp] suivante :
1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1"

Les bases du dveloppement web MVC en Java, par l'exemple

83/264

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.

pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ page import="java.util.ArrayList" %> <% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0;i<erreurs.size();i++){ out.println("<li>" + (String) erreurs.get(i) + "</li>\n"); }//for %> </ul> <br> <a href="?action=retourFormulaire"><%= lienRetourFormulaire %></a> </body> </html>

ligne 26 : le lien de retour au formulaire. Ce lien est identique celui de la vue [rponse]. Le lecteur est invit relire ventuellement les explications donnes pour cette vue.

6.5

Tests des vues

Pour raliser les tests des vues prcdentes, nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse :

Puis dans le dossier JSP, les pages sont modifies de la faon suivante : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. ... <% // -- test : on cre le modle de la page session.setAttribute("nom","tintin"); session.setAttribute("age","30"); request.setAttribute("urlAction","main"); %> <% // on rcupre les donnes du modle String nom=(String)session.getAttribute("nom"); String age=(String)session.getAttribute("age"); String urlAction=(String)request.getAttribute("urlAction"); %>

Les lignes 4-5 ont t ajoutes pour crer le modle dont a besoin la page lignes 11-13. [reponse.jsp] :
1. Les bases du dveloppement web MVC en Java, par l'exemple

84/264

2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

<%

%>

// -- test : on cre le modle de la page request.setAttribute("nom","milou"); request.setAttribute("age","10"); request.setAttribute("lienRetourFormulaire","Retour au formulaire");

<% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

Les lignes 4-6 ont t ajoutes pour crer le modle dont a besoin la page lignes 11-13. [erreurs.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. <%

%>

// -- test : on cre le modle de la page ArrayList<String> erreurs1=new ArrayList<String>(); erreurs1.add("erreur1"); erreurs1.add("erreur2"); request.setAttribute("erreurs",erreurs1); request.setAttribute("lienRetourFormulaire","Retour au formulaire");

<% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

Les lignes 4-8 ont t ajoutes pour crer le modle dont a besoin la page lignes 13-14. Lanons Tomcat si ce n'est dj fait puis demandons les url suivantes :

Nous obtenons bien les vues attendues.

Les bases du dveloppement web MVC en Java, par l'exemple

85/264

6.6
n 1 2

Le contrleur [ServletPersonne]
demande [GET /personne2/main] [POST /personne2/main] avec paramtres [txtNom, txtAge, action] posts origine url tape par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement - envoyer la vue [formulaire] vide - vrifier les valeurs des paramtres [txtNom, txtAge] - si elles sont incorrectes, envoyer la vue [erreurs(erreurs)] - si elles sont correctes, envoyer la vue [reponse(nom,age)]

Le contrleur [ServletPersonne] de l'application web [/personne2] va traiter les actions suivantes :

[GET clic sur le lien [Retour au - envoyer la vue [formulaire] pr-remplie avec les dernires valeurs saisies /personne2/main?action=retour formulaire] des vues Formulaire] [rponse] et [erreurs].

Nous avons donc une nouvelle action traiter : [GET /personne2/main?action=retourFormulaire].

6.6.1
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.

Squelette du contrleur
package istia.st.servlets.personne; ... import javax.servlet.http.HttpSession; @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { ... // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... }

Le squelette du contrleur [ServletPersonne] est quasi identique celui de la version prcdente :

// affichage formulaire vide void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 24. ... 25. } 26. 27. // affichage formulaire pr-rempli 28. void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 29. ... 30. } 31. 32. // validation du formulaire 33. void doValidationFormulaire(HttpServletRequest request, 34. ... 35. } 36. 37. // post 38. public void doPost(HttpServletRequest request, HttpServletResponse response) 39. ... 40. } 41. }

Les nouveauts :

ligne 4 : l'usage d'une session impose d'importer le paquetage [HttpSession] lignes 28-30 : la nouvelle mthode [doRetourFormulaire] traite la nouvelle action : [GET /personne2/main?action=retourFormulaire].

6.6.2

Initialisation du contrleur [init]


86/264

Les bases du dveloppement web MVC en Java, par l'exemple

La mthode [init] est identique celle de la version prcdente. Elle vrifie la prsence dans le fichier [web.xml] des lments dclars dans le tableau [paramtres] :
1.public class ServletPersonne extends HttpServlet { 2. // paramtres d'instance 3. private String urlErreurs = null; 4. private ArrayList erreursInitialisation = new ArrayList<String>(); 5. private String[] paramtres={"urlFormulaire","urlReponse","urlControleur","lienRetourFormulaire"}; 6. private Map params=new HashMap<String,String>();

ligne 5 : les paramtres [urlControleur] (Url du contrleur) et [lienRetourFormulaire] (Texte du lien des vues [rponse] et [erreurs] ont t rajouts.

6.6.3

La mthode [doGet]

La mthode [doGet] doit traiter l'action [GET /personne2/main?action=retourFormulaire] qui n'existait pas auparavant :
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. @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // on vrifie comment s'est passe l'initialisation de la servlet if (erreursInitialisation.size() != 0) { // on passe la main la page d'erreurs request.setAttribute("erreurs", erreursInitialisation); request.setAttribute("lienRetourFormulaire", ""); getServletContext().getRequestDispatcher(urlErreurs).forward( request, response); // fin return; } // on rcupre la mthode d'envoi de la requte String mthode=request.getMethod().toLowerCase(); // on rcupre l'action excuter String action=request.getParameter("action"); // action ? if(action==null){ action="init"; } // excution action if(mthode.equals("get") && action.equals("init")){ // dmarrage application doInit(request,response); return; } if(mthode.equals("post") && action.equals("validationFormulaire")){ // validation du formulaire de saisie doValidationFormulaire(request,response); return; } if(mthode.equals("get") && action.equals("retourFormulaire")){ // retour au formulaire de saisie doRetourFormulaire(request,response); return; } // autres cas doInit(request,response);

lignes 6-14 : on vrifie que la liste des erreurs d'initialisation est vide. Si ce n'est pas le cas, on fait afficher la vue [erreurs(erreursInitialisation)] qui va signaler la ou les erreurs. Pour comprendre ce code, il faut se rappeler le modle de la vue [erreurs] :
1. 2. 3. 4. 5. <% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

La vue [erreurs] attend un lment de cl " erreurs " dans la requte. Le contrleur cre cet lment ligne 8. Elle attend galement un lment de cl "lienRetourFormulaire". Le contrleur cre cet lment ligne 9. Ici le texte du lien sera vide. Il n'y aura donc pas de lien dans la vue [erreurs] envoye. En effet, s'il y a eu erreurs d'initialisation de l'application, elle doit tre reconfigure. Il n'y a pas lieu de proposer l'utilisateur de continuer l'application via un lien.

lignes 34-37 : traitement de la nouvelle action [GET /personne2/main?action=retourFormulaire] 87/264

Les bases du dveloppement web MVC en Java, par l'exemple

6.6.4

La mthode [doInit]

Cette mthode traite la requte n 1 [GET /personne2/main]. Sur cette requte, elle doit envoyer la vue [formulaire(nom,age)] vide. Son code est le suivant :
1. 2. // affichage formulaire vide void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 3. // on rcupre la session de l'utilisateur 4. HttpSession session = request.getSession(true); 5. // on envoie le formulaire vide 6. session.setAttribute("nom", ""); 7. session.setAttribute("age", ""); 8. request.setAttribute("urlAction", (String)params.get("urlControleur")); 9. getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( 10. request, response); 11. return; 12. }

ligne 4 : la session courante est rcupre si elle existe, cre sinon (paramtre true de getSession). lignes 9-10 : la vue [formulaire] est affiche. Rappelons le modle attendu par cette vue :
1. 2. 3. 4. 5. 6. <% // on rcupre les donnes du modle String nom=(String)session.getAttribute("nom"); String age=(String)session.getAttribute("age"); String urlAction=(String)request.getAttribute("urlAction"); %>

lignes 6-7 : les lments [nom,age] du modle de la vue [formulaire] sont initialiss avec des chanes vides et placs dans la session car c'est l que les attend la vue. ligne 8 : l'lment [urlAction] du modle est initialis avec la valeur du paramtre [urlControleur] du fichier [web.xml] et plac dans la requte.

6.6.5

La mthode [doValidationFormulaire]

Cette mthode traite la requte n 2 [POST /personne2/main] dans laquelle les paramtres posts sont [action, txtNom, txtAge]. Son code 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. // validation du formulaire void doValidationFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on rcupre les paramtres String nom = request.getParameter("txtNom"); String age = request.getParameter("txtAge"); // qu'on mmorise dans la session HttpSession session = request.getSession(true); session.setAttribute("nom", nom); session.setAttribute("age", age); // vrification des paramtres ArrayList<String> erreursAppel = new ArrayList<String>(); // le nom doit tre non vide nom = nom.trim(); if (nom.equals("")) erreursAppel.add("Le champ [nom] n'a pas t rempli"); // l'ge doit tre un entier >=0 if (!age.matches("^\\s*\\d+\\s*$")) erreursAppel.add("Le champ [age] est erron"); // des erreurs dans les paramtres ? if (erreursAppel.size() != 0) { // on envoie la page d'erreurs request.setAttribute("erreurs", erreursAppel); request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire")); getServletContext().getRequestDispatcher(urlErreurs).forward( request, response); return; } // les paramtres sont corrects - on envoie la page rponse request.setAttribute("nom", nom); request.setAttribute("age", age); request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire")); getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request, response); return; }

Les bases du dveloppement web MVC en Java, par l'exemple

88/264

lignes 5-6 : on rcupre dans la requte du client les valeurs des paramtres "txtNom" et "txtAge". lignes 8-10 : on mmorise ces valeurs dans la session pour pouvoir les retrouver lorsque l'utilisateur va cliquer sur le lien [Retour au formulaire] des vues [rponse] et [erreurs]. lignes 12-19 : la validit des valeurs des deux paramtres est vrifie lignes 21-28 : si l'un des paramtres est erron, on fait afficher la vue [erreurs(erreurs,lienRetourFormulaire)]. Rappelons le modle de cette vue :
1. 2. 3. 4. 5. <% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

lignes 30-34 : si les deux paramtres " txtNom " et " txtAge " rcuprs ont des valeurs valides, on fait afficher la vue [reponse(nom,age,lienRetourFormulaire)]. Il faut se rappeler le modle de la vue [reponse] :
1. 2. 3. 4. 5. 6. <% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

6.6.6

La mthode [doRetourFormulaire]

Cette mthode traite la requte 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 rcupre la session de l'utilisateur 4. HttpSession session = request.getSession(true); 5. // on prpare le modle du formulaire 6. // nom prsent dans la session ? 7. String nom = (String) session.getAttribute("nom"); 8. if (nom == null) 9. session.setAttribute("nom", ""); 10. // ge prsent 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 mthode, on doit afficher la vue [formulaire] pr-remplie avec les dernires saisies faites par l'utilisateur. Rappelons le modle de la vue [formulaire] :
1. 2. 3. 4. 5. 6. <% // on rcupre les donnes du modle String nom=(String)session.getAttribute("nom"); String age=(String)session.getAttribute("age"); String urlAction=(String)request.getAttribute("urlAction"); %>

La mthode [doRetourFormulaire] doit donc construire le modle prcdent.


ligne 4 : on rcupre la session dans laquelle le contrleur a mmoris les valeurs (nom, age) saisies. ligne 7 : on rcupre 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 aprs l'action [validationFormulaire] dont aprs la mise en session des donnes saisies. Mais une session peut expirer car elle a une dure de vie limite, 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 mme pour l'ge si on ignore le problme de la session expire, alors les lignes 3-13 sont inutiles. Les lments [nom,age] du modle sont dj dans la session. Il n'y a donc pas les y remettre. ligne 15 : on fixe la valeur de l'lment [urlAction] du modle 89/264

Les bases du dveloppement 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 montrs en exemple au paragraphe 6.1, page 76.

7 Application web MVC [personne] version 3


Lectures conseilles dans [ref1] : chapitre 9

7.1

Introduction

Nous nous proposons d'ajouter du code Javascript dans les pages HTML envoyes au navigateur. C'est ce dernier qui excute le code Javascript embarqu dans la page qu'il affiche. Cette technologie est indpendante de celle utilise par le serveur web pour gnrer 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] Rtablir [Effacer]

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

Fonction joue le rle du bouton [Envoyer] des versions prcdentes : poste au contrleur les valeurs saisies nouveau bouton vrifie localement la validit des donnes saisies avant de les poster au contrleur rtablit le formulaire dans l'tat o il a t reu 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 dveloppement web MVC en Java, par l'exemple

90/264

Nous utiliserons galement du code Javascript pour grer le lien [Retour au formulaire] des vues [erreurs] et [rponse]. Prenons l'exemple de la vue [rponse] :

La diffrence est dans l'url affiche en [1]. Dans la version prcdente, celle-ci tait :
[http://localhost:8080/personne2/main?action=retourFormulaire]

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

7.2

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-03] de l'application web [/personne3], on dupliquera le projet [mvc-personne-02] en suivant la procdure dcrite au paragraphe 6.2, page 78.

Les bases du dveloppement 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 prcdente hormis quelques dtails : ligne 6 : le nom d'affichage de l'application web a chang en [mvc-personne-03] 92/264

Les bases du dveloppement web MVC en Java, par l'exemple

Le paramtre [urlControleur] a disparu. Dans la version prcdente, il servait fixer la cible du POST de la vue [formulaire]. Dans cette nouvelle version, la cible sera la chane vide, c.a.d. l'Url affiche 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 contrleur [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 gnre 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 rcupre les donnes du modle 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(){ // vrification des paramtres 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 dveloppement web MVC en Java, par l'exemple

93/264

38. champs=/^\s*\d+\s*$/.exec(txtAge.value); 39. if(champs==null){ 40. // l'ge est incorrect 41. alert("Age incorrect"); 42. txtAge.focus(); 43. // retour l'interface visuelle 44. return; 45. }//if 46. // les paramtres sont corrects - on les envoie au serveur 47. submit(); 48. }//with 49. }//envoyer 50. </script> 51. </head> 52. <body> 53. <center> 54. <h2>Personne - formulaire</h2> 55. <hr> 56. <form name="frmPersonne" method="post"> 57. <table> 58. <tr> 59. <td>Nom</td> 60. <td><input name="txtNom" value="<%= nom %>" type="text" size="20"></td> 61. </tr> 62. <tr> 63. <td>Age</td> 64. <td><input name="txtAge" value="<%= age %>" type="text" size="3"></td> 65. </tr> 66. <tr> 67. </table> 68. <table> 69. <tr> 70. <td><input type="submit" value="Submit"></td> 71. <td><input type="button" value="[Envoyer]" onclick="envoyer()"></td> 72. <td><input type="reset" value="Rtablir"></td> 73. <td><input type="button" value="[Effacer]" onclick="effacer()"></td> 74. </tr> 75. </table> 76. <input type="hidden" name="action" value="validationFormulaire"> 77. </form> 78. </center> 79. </body> 80. </html> 81. 82.

Les nouveauts :

ligne 56 : le formulaire a un nom [frmPersonne]. Ce nom va tre utilis dans le code Javascript. On notera l'absence de l'attribut [action] qui fait que le formulaire [frmPersonne] sera post l'Url affiche par le navigateur. ligne 70 : le bouton [Submit] joue le rle du bouton [Envoyer] des versions prcdentes ligne 71 : un clic sur le bouton [Envoyer] de type [Button] fait excuter la fonction Javascript [envoyer] dfinie ligne 24 ligne 72 : le bouton [Rtablir] n'a pas chang de fonction ligne 73 : un clic sur le bouton [Effacer] de type [Button] fait excuter la fonction Javascript [effacer] dfinie ligne 16

Avant de commenter le code Javascript, rappelons quelques notations : Javascript gre la page affiche et son contenu comme un arbre d'objets dont la racine est l'objet [document]. Dans ce document, il peut y avoir un ou plusieurs formulaires. [document.frmPersonne] dsigne le formulaire de nom [frmPersonne] dfini ligne 56. Ce formulaire contient lui aussi des objets. Les champs de saisie en font partie. Ainsi [document.frmPersonne.txtNom] dsigne l'objet image du champ de saisie HTML dfini ligne 60. L'objet [txtNom] a diverses proprits dont la proprit [value] qui dsigne le contenu du champ de saisie. Ainsi [document.frmPersonne.txtNom.value] dsigne le contenu du champ de saisie [txtNom].

lignes 16-22 : la fonction Javascript [effacer] met une chane vide dans les champs de saisie [txtNom, txtAge]. lignes 24-49 : la fonction Javascript [envoyer] vrifie la validit des valeurs des champs de saisie [txtNom, txtAge] avant de les poster. Elle utilise pour cela des expressions rgulires. ligne 28 : vrifie si la valeur du champ de saisie [txtNom] correspond au modle /s* qui signifie 0 ou davantage d'espaces. Si la rponse est oui, alors cela signifie que l'utilisateur n'a pas indiqu de nom. S'il y a correspondance avec le modle, la variable champs aura une valeur diffrente du pointeur null, sinon elle aura la valeur null. ligne 29 : s'il y a correspondance avec le modle ligne 30 : on affiche un message l'utilisateur ligne 32 : on met la chane 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 dveloppement web MVC en Java, par l'exemple

ligne 34 : on interrompt la fonction. Il n'y a alors pas de POST au serveur des valeurs saisies. lignes 38-45 : on a une dmarche analogue avec le champ [txtAge] ligne 47 : si on arrive l, c'est que les valeurs saisies sont correctes. On poste (submit) alors le formulaire [frmPersonne] au serveur web. Tout se passe alors comme si on avait appuy sur le bouton libell [Submit].

7.4.2

La vue [reponse]

Cette vue affiche les valeurs saisies dans le formulaire lorsque celles-ci sont valides :

Vis vis de la version prcdente, la nouveaut n'apparat que lorsqu'on utilise le lien [Retour au formulaire] de la vue [rponse] :

La diffrence est dans l'url affiche en [1]. Dans la version prcdente, celle-ci tait :
[http://localhost:8080/personne2/main?action=retourFormulaire]

Ici, l'action ne fait plus partie de l'Url car elle va tre envoye par un POST au lieu d'un GET. La nouvelle page JSP [reponse.jsp] est lqqqaaaaaaaaaqqAAAaaa 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"> <% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom"); String age=(String)request.getAttribute("age"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %> <html> <head> <title>Personne</title> </head>

Les bases du dveloppement web MVC en Java, par l'exemple

95/264

18. <body> 19. <h2>Personne - rponse</h2> 20. <hr> 21. <table> 22. <tr> 23. <td>Nom</td> 24. <td><%= nom %> 25. </tr> 26. <tr> 27. <td>Age</td> 28. <td><%= age %> 29. </tr> 30. </table> 31. <br> 32. <form name="frmPersonne" method="post"> 33. <input type="hidden" name="action" value="retourFormulaire"> 34. </form> 35. <a href="javascript:document.frmPersonne.submit();"> 36. <%= lienRetourFormulaire %> 37. </a> 38. </body> 39. </html> 40.

Les nouveauts :

lignes 35-37 : le lien [Retour au formulaire] embarque du Javascript. Un clic sur ce lien, provoque l'excution du code Javascript de l'attribut [href]. Comme nous l'avons vu dans l'tude de [formulaire.jsp], nous savons que ce code poste les valeurs des champs de type <input>, <select>, ... du formulaire [frmPersonne]. lignes 32-34 : dfinissent le formulaire [frmPersonne]. Celui-ci n'a qu'un champ de type <input type= "hidden " ...>, donc un champ cach. Ce champ [action] sert transmettre au contrleur le nom de l'action excuter, ici [retourFormulaire].

7.4.3

La vue [erreurs]

Cette vue signale les erreurs de saisie dans le formulaire. La modification apporte est la mme que pour la vue [rponse] :

La diffrence est dans l'url affiche en [1]. Dans la version prcdente, celle-ci tait :
[http://localhost:8080/personne2/main?action=retourFormulaire]

Ici, l'action ne fait plus partie de l'Url car elle va tre envoye par un POST au lieu d'un GET. La nouvelle page JSP [reponse.jsp] suivante :
1. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" 2. pageEncoding="ISO-8859-1"%> 3. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4. <%@ page import="java.util.ArrayList" %> 5. 6. <% 7. // on rcupre les donnes du modle 8. ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); 9. String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); 10. %> 11. 12. 13. <html> 14. <head> 15. <title>Personne</title> Les bases du dveloppement web MVC en Java, par l'exemple

96/264

16. </head> 17. <body> 18. <h2>Les erreurs suivantes se sont produites</h2> 19. <ul> 20. <% 21. for(int i=0;i<erreurs.size();i++){ 22. out.println("<li>" + (String) erreurs.get(i) + "</li>\n"); 23. }//for 24. %> 25. </ul> 26. <br> 27. <form name="frmPersonne" method="post"> 28. <input type="hidden" name="action" value="retourFormulaire"> 29. </form> 30. <a href="javascript:document.frmPersonne.submit();"> 31. <%= lienRetourFormulaire %> 32. </a> 33. </body> 34. </html>

Les nouveauts :

lignes 30-32 : la nouvelle gestion du clic sur le lien. Les explications donnes ce sujet dans l'tude de [rponse.jsp] sont valables galement ici.

7.5

Tests des vues

Pour raliser les tests des vues prcdentes, nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse :

Puis dans le dossier JSP, les pages sont modifies de la faon suivante : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. ... <% // -- test : on cre le modle de la page session.setAttribute("nom","tintin"); session.setAttribute("age","30"); %> <%// on rcupre les donnes du modle String nom = (String) session.getAttribute("nom"); String age = (String) session.getAttribute("age"); %>

Les lignes 4-5 ont t ajoutes pour crer le modle dont a besoin la page lignes 9-10. [reponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. ... <% // -- test : on cre le modle de la page request.setAttribute("nom","milou"); request.setAttribute("age","10"); request.setAttribute("lienRetourFormulaire","Retour au formulaire"); %> <% // on rcupre les donnes du modle String nom=(String)request.getAttribute("nom");

Les bases du dveloppement web MVC en Java, par l'exemple

97/264

12. String age=(String)request.getAttribute("age"); 13. String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); 14. %> 15. ...

Les lignes 4-6 ont t ajoutes pour crer le modle dont a besoin la page lignes 11-13. [erreurs.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. <%

%>

// -- test : on cre le modle de la page ArrayList<String> erreurs1=new ArrayList<String>(); erreurs1.add("erreur1"); erreurs1.add("erreur2"); request.setAttribute("erreurs",erreurs1); request.setAttribute("lienRetourFormulaire","Retour au formulaire");

<% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %>

Les lignes 4-8 ont t ajoutes pour crer le modle dont a besoin la page lignes 13-14. Lanon Tomcat si ce n'est dj fait puis demandons les url suivantes :

Nous obtenons bien les vues attendues.

7.6

Le contrleur [ServletPersonne]

Le contrleur [ServletPersonne] de l'application web [/personne3] va traiter les actions suivantes :

Les bases du dveloppement web MVC en Java, par l'exemple

98/264

n 1 2

demande [GET /personne3/main] [POST /personne3/main] avec paramtres [txtNom, txtAge, action=validationFormulaire] posts [POST /personne3/main] avec paramtres [action=retourFormulaire] posts

origine url tape par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire]

traitement - envoyer la vue [formulaire] vide - vrifier les valeurs des paramtres [txtNom, txtAge] - si elles sont incorrectes, envoyer la vue [erreurs(erreurs)] - si elles sont correctes, envoyer la vue [reponse(nom,age)]

clic sur le lien [Retour au - envoyer la vue [formulaire] pr-remplie avec les dernires valeurs saisies formulaire] des vues [rponse] et [erreurs].

Nous avons donc une nouvelle action traiter : [POST /personne3/main] avec paramtre post [action=retourFormulaire]. la place de l'ancienne action [GET /personne3/main?action=retourFormulaire].

7.6.1

Squelette du contrleur

Le squelette du contrleur [ServletPersonne] est identique celui de la version prcdente.


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. package istia.st.servlets.personne; ... import javax.servlet.http.HttpSession; @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres={"urlFormulaire","urlReponse","lienRetourFormulaire"}; private Map params=new HashMap<String,String>(); ... // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... } @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... }

// affichage formulaire vide void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 29. ... 30. } 31. 32. // affichage formulaire pr-rempli 33. void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 34. ... 35. } 36. 37. // validation du formulaire 38. void doValidationFormulaire(HttpServletRequest request, 39. ... 40. } 41. 42. // post 43. public void doPost(HttpServletRequest request, HttpServletResponse response) 44. ... 45. } 46. }

Les nouveauts :

ligne 11, le tableau [paramtres] ne contient plus le paramtre [urlControleur] qui a t supprim du fichier [web.xml].

Les mthodes [init, doValidationFormulaire] restent ce qu'elles taient. Les mthodes [doGet, doInit, doRetourFormulaire] changent lgrement. Les bases du dveloppement web MVC en Java, par l'exemple 99/264

7.6.2

La mthode [doGet]

La mthode [doGet] doit traiter l'action [POST /personne3/main] avec le paramtre post [action=retourFormulaire] :
1. @SuppressWarnings("unchecked") 2. public void doGet(HttpServletRequest request, HttpServletResponse response) 3. throws IOException, ServletException { 4. 5. ... 6. if(mthode.equals("post") && action.equals("retourFormulaire")){ 7. // retour au formulaire de saisie 8. doRetourFormulaire(request,response); 9. return; 10. } 11. // autres cas 12. doInit(request,response); 13. }

lignes 6-9 : traitement de l'action [POST /personne3/main] avec le paramtre post [action=retourFormulaire]

7.6.3

La mthode [doInit]

Cette mthode traite la requte n 1 [GET /personne3/main]. Son code est le suivant :
1. 2. // affichage formulaire vide void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 3. // on rcupre la session de l'utilisateur 4. HttpSession session = request.getSession(true); 5. // on envoie le formulaire vide 6. session.setAttribute("nom", ""); 7. session.setAttribute("age", ""); 8. getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( 9. request, response); 10. return; 11. }

La nouveaut est que la vue [formulaire] affiche ligne 8 n'a plus l'lment [action] dans son modle.

7.6.4

La mthode [doRetourFormulaire]

Cette mthode traite la requte n 1 [POST /personne3/main] avec [action=retourFormulaire] dans les lments posts. Son code est le suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. // affichage formulaire pr-rempli void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on rcupre la session de l'utilisateur HttpSession session = request.getSession(true); // nom prsent dans la session ? String nom = (String) session.getAttribute("nom"); if (nom == null) session.setAttribute("nom", ""); // ge prsent dans la session ? String age = (String) session.getAttribute("age"); if (age == null) session.setAttribute("age", ""); // on affiche le formulaire getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; }

La nouveaut est que la vue [formulaire] affiche ligne 14 n'a plus l'lment [action] dans son modle.

7.7

Tests
Demander l'url

Lancer ou relancer Tomcat aprs y avoir intgr le projet Eclipse [personne-mvc-03]. [http://localhost:8080/personne3] puis reprendre les tests montrs en exemple au paragraphe 7.1, page 90.
Les bases du dveloppement web MVC en Java, par l'exemple

100/264

8 La bibliothque de balises JSTL


8.1.1 Introduction
Considrons la vue [erreurs.jsp] qui affiche une liste d'erreurs :

Il y a plusieurs faons d'crire une telle page. Nous ne nous intressons ici qu' la partie affichage des erreurs. Une premire solution est d'utiliser du code Java comme il a t fait :
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. <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <%@ page import="java.util.ArrayList" %> <% // on rcupre les donnes du modle ArrayList erreurs=(ArrayList)request.getAttribute("erreurs"); String lienRetourFormulaire=(String)request.getAttribute("lienRetourFormulaire"); %> <html> <head> <title>Personne</title> </head> <body> <h2>Les erreurs suivantes se sont produites</h2> <ul> <% for(int i=0;i<erreurs.size();i++){ out.println("<li>" + (String) erreurs.get(i) + "</li>\n"); }//for %> </ul> <br> <form name="frmPersonne" method="post"> <input type="hidden" name="action" value="retourFormulaire"> </form> <a href="javascript:document.frmPersonne.submit();"> <%= lienRetourFormulaire %> </a> </body> </html>

La page JSP rcupre la liste des erreurs dans la requte (ligne 8) et l'affiche l'aide d'une boucle Java (lignes 19-23). La page mlange code HTML et code Java, ce qui peut tre problmatique si la page doit tre maintenue par un infographiste qui en gnral ne comprendra pas le code Java. Pour viter ce mlange, on utilise des bibliothques de balises qui vont apporter des possibilits nouvelles aux pages JSP. Avec la bibliothque de balises JSTL (Java Standard Tag Library), la vue prcdente devient la suivante :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. <%@ 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>

Les bases du dveloppement web MVC en Java, par l'exemple

101/264

17. <br> 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>

La balise (ligne 4)
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>

signale l'utilisation d'une bibliothque de balises dont la dfinition se trouve dans le fichier [/WEB-INF/c.tld]. Ces balises seront utilises dans le code de la page, prfixes du mot c (prefix="c"). On peut utiliser tout prfixe de son choix. Ici c signifie [core]. Les prfixes permettent d'utiliser des bibliothques de balises qui pourraient avoir les mmes noms pour certaines balises. L'utilisation du prfixe lve l'ambigut. La nouvelle page n'a plus de code Java aux deux endroits o elle en avait prcdemment :

la rcupration du modle de la page [erreurs, lienRetourFormulaire] (partie disparue) l'affichage de la liste des erreurs (lignes 13-15)

La boucle d'affichage des erreurs a t remplace par code suivant :


<c:forEach var="erreur" items="${erreurs}"> <li>${erreur}</li> </c:forEach>

la balise <forEach> sert dlimiter une boucle la notation ${variable} sert crire la valeur d'une variable

La balise <forEach> a ici deux attributs : - items="${erreurs}" indique la collection d'objets sur laquelle il faut itrer. Ici, la collection est l'objet 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 reprsente la requte transmise par le contrleur : request.getAttribute("erreurs") o l'objet [session] qui reprsente la session du client : session.getAttribute("erreurs") o l'objet [application] qui reprsente le contexte de l'application web : application.getAttribute("erreurs") La collection dsigne par l'attribut items peut avoir diverses formes : tableau, ArrayList, objet implmentant l'interface List, ... var="erreur" sert donner un nom l'lment courant de la collection en cours de traitement. La boucle <forEach> va tre excute successivement pour chaque lment de la collection items. A l'intrieur de la boucle, l'lment de la collection en cours de traitement sera donc dsign ici par erreur.

La notation ${erreur} insre la valeur de la variable erreur dans le texte. Cette variable n'est pas ncessairement une chane de caractres. JSTL utilise la mthode erreur.toString() pour insrer la valeur de la variable erreur. A la place de la notation ${erreur}, on peut galement utiliser la balise <c:out value="${erreur}"/>. Pour en revenir notre exemple d'affichage des erreurs : - le contrleur mettra dans la requte transmise la page JSP un ArrayList de messages d'erreurs, donc un ArrayList d'objets String : request.setAttribute("erreurs",erreurs) o erreurs est le ArrayList ; - cause de l'attribut items="${erreurs}", la page JSP va chercher un attribut appel erreurs, successivement dans la requte, la session, l'application. Elle va le trouver dans la requte : request.getAttribute("erreurs") va rendre le ArrayList plac dans la requte par le contrleur ; - la variable erreur de l'attribut var="erreur" va donc dsigner l'lment courant du ArrayList, donc un objet String. La mthode erreur.toString() va insrer la valeur de ce String, ici un message d'erreur, dans le flux HTML de la page. Les objets de la collection traite par la balise <forEach> peuvent tre plus complexes que de simples chanes de caractres. Prenons l'exemple d'une page JSP qui affiche une liste d'articles :
1. 2. 3. 4. 5. 6. <c:forEach var="article" items="${listarticles}"> <tr> <td><c:out value="${article.nom}"/></td> <td><c:out value="${article.prix}"/></td> <td><a href="<c:url value="?action=infos&id=${article.id}"/>">Infos</a></td> </tr>

Les bases du dveloppement web MVC en Java, par l'exemple

102/264

7.

</c:forEach>

o [listarticles] est un ArrayList d'objets de type [Article] qu'on suppose tre un Javabean avec les champs [id, nom, prix, stockActuel, stockMinimum], chacun de ces champs tant accompagn de ses mthodes get et set. L'objet [listarticles] a t plac dans la requte par le contrleur. La page JSP prcdente va le rcuprer dans l'attribut items de la balise forEach. L'objet courant article (var="article") dsigne donc un objet de type [Article]. Considrons la balise de la ligne 3 :
<c:out value="${article.nom}"/>

Que signifie ${article.nom} ? En fait diverses choses selon la nature de l'objet article. Pour obtenir la valeur de article.nom, la page JSP va essayer deux choses : 1. article.getNom() - on notera l'orthographe getNom pour rcuprer le champ nom (norme Javabean) 2. article.get("nom") L'objet [article] peut donc tre un bean avec un champ nom, ou un dictionnaire avec une cl nom. Il n'y a pas de limites la hirarchie de l'objet trait. Ainsi la balise
<c:out value="${individu.enfants[1].nom}"/>

permet de traiter un objet [individu] de type suivant :


class Individu{ private String nom; private String prnom; private Individu[] enfants; // mthodes de la norme Javabean public String getNom(){ return nom;} public String getPrnom(){ return prnom;} public Individu getEnfants(int i){ return enfants[i];} }

Pour obtenir la valeur de ${individu.enfants[1].nom}, la page JSP va essayer diverses mthodes dont celle-ci qui russira : individu.getEnfants(1).getNom() o individu dsigne un objet de type Individu.

8.1.2

Installer et dcouvrir la bibliothque JSTL

Les explications donnes prcdemment suffiront pour l'application qui nous intresse mais la bibliothque de balises JSTL offre d'autres balises que celles prsentes. Pour les dcouvrir, on peut installer un tutoriel fourni dans le paquetage de la bibliothque. Nous utiliserons l'implmentation JSTL 1.1 du projet [Jakarta Taglibs] disponible l'Url [http://jakarta.apache.org/taglibs/] (mai 2006) :

Les bases du dveloppement web MVC en Java, par l'exemple

103/264

Le fichier zip tlcharg a un contenu analogue au suivant :

Les deux fichiers de suffixe .war sont des archives d'applications web : standard-doc : documentation sur les balises JSTL standard-examples : exemples d'utilisation des balises Nous allons dployer cette dernire application au sein de Tomcat. Nous lanons ce dernier via l'option adquate du menu [Dmarrer] puis nous demandons l'Url [http://localhost:8080] et suivons le lien [Tomcat Manager] :

Nous obtenons alors une page d'authentification. Nous nous identifions comme manager / manager ou admin / admin, comme il a t montr au paragraphe 2.3.3, page 14.

Les bases du dveloppement web MVC en Java, par l'exemple

104/264

Nous obtenons une page listant les applications actuellement dployes dans Tomcat :

Nous pouvons ajouter une nouvelle application grce des formulaires placs en bas de la page :

Nous utilisons le bouton [Parcourir] pour dsigner un fichier .war dployer.

La copie d'cran ne le montre pas, mais nous avons slectionn le fichier [standard-examples.war] de la distribution JSTL tlcharge. Le bouton [Deploy] enregistre et dploie cette application au sein de Tomcat.

L'application [/standard-examples] a bien t dploye. Nous la lanons :

Les bases du dveloppement web MVC en Java, par l'exemple

105/264

Le lecteur est invit suivre les diffrents liens offerts par cette page lorsqu'il cherche des exemples d'utilisation des balises JSTL. L'application [standard-doc] pourra tre dploye de la mme faon partir du fichier [standard-doc.war]. Elle donne accs des informations assez techniques sur la bibliothque JSTL. Elle prsente moins d'intrt pour le dbutant.

8.1.3

Utiliser JSTL dans une application web

Dans les exemples livrs avec la bibliothque JSTL 1.2, les pages JSP ont en dbut de fichier la balise suivante :
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Nous avons dj rencontr cette balise au paragraphe 8.1.1, page 102 et nous en avons donn une courte explication :

[uri] : URI (Uniform Resource Identifier) o l'on trouve la dfinition des balises utilises dans la page. Cette URI va tre utilise par le serveur web lorsque la page JSP va tre traduite en code Java pour devenir une servlet. Elle est galement utilise par les outils de dveloppement de pages web pour vrifier la bonne syntaxe des balises utilises dans la page ou pour proposer une aide la frappe. Lorsqu'on commence l'criture d'une balise, un outil connaissant la bibliothque peut alors proposer l'utilisateur les attributs possibles pour cette balise. [prefix] : prfixe qui identifie ces balises dans la page

L'uri [http://java.sun.com/jsp/jstl/core] n'est pas utilisable si on n'est pas raccord l'internet public. Dans ce cas, on peut placer localement, le fichier de dfinition des balises. Plusieurs tels fichiers sont fournis avec la distribution JSTL 1.2 dans le dossier [tld] (Tag Language Definition) :

JSTL est en fait un ensemble de bibliothque de balises. Nous n'utiliserons que la bibliothque [c.tld] dite bibliothque " core ". Nous placerons le fichier [c.tld] ci-dessus dans le dossier [WEB-INF] de nos applications :
Les bases du dveloppement web MVC en Java, par l'exemple

106/264

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

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

Ces deux archives sont places 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 bibliothque de balises JSTL prsente prcdemment.

9.1

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-04] de l'application web [/personne4], on dupliquera le projet [mvc-personne-03] en suivant la procdure dcrite 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 dveloppement 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 prcdente 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 mne au contrleur [ServletPersonne] de l'application [/personne4]. La balise <c:redirect> appartient la bibliothque JSTL / Core. Elle prsente la particularit de complter l'Url de son attribut [url] en y ajoutant : le prfixe [/contexte], o [contexte] est le contexte de l'application, ici [personne4]. le suffixe [?jsessionid=id_session] si le navigateur qui fait la requte n'a pas envoy de cookie de session. L'identifiant [jsessionid] dsigne l'identifiant du jeton de session envoy par le serveur web ses clients. Il dpend du serveur web. Ici, c'est celui du serveur Tomcat. [id_session] est le jeton de session lui-mme. Ainsi, la vritable 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 aprs 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 prmature 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 gnre par page JSP [formulaire.jsp] suivante :


Les bases du dveloppement 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="Rtablir"></td> <td><input type="button" value="[Effacer]" onclick="effacer()"></td> </tr> </table> <input type="hidden" name="action" value="validationFormulaire"> </form> </center> </body> </html>

Les nouveauts :

ligne 4 : dclaration de la bibliothque de balises JSTL / Core lignes 21, 25 : rcupration d'attributs [nom, age] dans le modle de la page. Rappelons que ces attributs seront cherchs successivement dans la requte [request], la session [session] et l'application [application] de la page JSP. Dans notre cas, le contrleur les mettra dans la session. il n'y a plus de code Java en dbut de page JSP pour rcuprer le modle de la page du fait du fonctionnement prcdent.

9.3.2

La vue [reponse]

Cette vue n'a pas chang :

La nouvelle page JSP [reponse.jsp] est la suivante :


Les bases du dveloppement 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 - rponse</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 nouveauts :

ligne 4 : dclaration de la bibliothque de balises JSTL / Core lignes 16, 20, 28 : rcupration d'attributs [nom, age, lienRetourFormulaire] dans le modle de la page. Il n'y a plus de code Java en dbut de page JSP pour rcuprer 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 dveloppement 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 nouveauts :

ligne 4 : dclaration de la bibliothque 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 dbut de page JSP pour rcuprer le modle de celle-ci.

9.4

Tests des vues

Pour raliser les tests des vues prcdentes, nous dupliquons leurs pages JSP dans le dossier /WebContent/JSP du projet Eclipse :

Puis dans le dossier JSP, les pages sont modifies de la faon suivante : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. ... <% // -- test : on cre le modle de la page session.setAttribute("nom","tintin"); session.setAttribute("age","30"); %> <html> <head>

Les lignes 4-5 ont t ajoutes pour crer le modle dont a besoin la page. [reponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. ... <% // -- test : on cre le modle 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 ajoutes pour crer le modle dont a besoin la page. [erreurs.jsp] :
Les bases du dveloppement web MVC en Java, par l'exemple

111/264

1. 2. <% 3. // -- test : on cre le modle de la page 4. ArrayList<String> erreurs1=new ArrayList<String>(); 5. erreurs1.add("erreur1"); 6. erreurs1.add("erreur2"); 7. request.setAttribute("erreurs",erreurs1); 8. request.setAttribute("lienRetourFormulaire","Retour au formulaire"); 9. %> 10. 11. <html> 12. <head> 13.

Les lignes 4-8 ont t ajoutes pour crer le modle dont a besoin la page. Lanon Tomcat si ce n'est dj fait puis demandons les url suivantes :

Nous obtenons bien les vues attendues.

9.5
n 1 2

Le contrleur [ServletPersonne]
demande [GET /personne4/main] [POST /personne4/main] avec paramtres [txtNom, txtAge, action=validationFormulaire] posts [POST /personne4/main] avec paramtres [action=retourFormulaire] posts origine url tape par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement - envoyer la vue [formulaire] vide - vrifier les valeurs des paramtres [txtNom, txtAge] - si elles sont incorrectes, envoyer la vue [erreurs(erreurs)] - si elles sont correctes, envoyer la vue [reponse(nom,age)]

Le contrleur [ServletPersonne] de l'application web [/personne3] va traiter les actions suivantes :

clic sur le lien [Retour au - envoyer la vue [formulaire] pr-remplie avec les dernires valeurs saisies formulaire] des vues [rponse] et [erreurs].

Le squelette du contrleur [ServletPersonne] est identique celui de la version prcdente. Nous passons en revue les modifications amenes aux mthodes [doInit, doValidationFormulaire, doRetourFormulaire], les mthodes [init, doGet, doPost] ne changeant pas. Les bases du dveloppement web MVC en Java, par l'exemple 112/264

9.5.1

La mthode [doInit]

Cette mthode traite la requte n 1 [GET /personne4/main]. Son code est le suivant :
1. // affichage formulaire vide 2. void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 3. getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( 4. request, response); 5. return; 6.}

Les nouveauts :

ligne 3 : on affiche la vue [formulaire]. Celle-ci attend dans son modle des attributs " nom " et " age ". Elle ne va pas les y trouver puisqu'ici le contrleur ne les y met pas. Dans ce cas, la bibliothque JSTL va rcuprer des pointeurs null pour ces attributs. Cela ne provoque pas d'erreurs et des valeurs vides seront affiches pour les lments ${nom} et ${age} de la vue [formulaire]. Cela nous convient. Nous vitons ainsi d'avoir initialiser le modle de la vue [formulaire].

9.5.2

La mthode [doValidationFormulaire]

Cette mthode traite la requte n 2 [POST /personne4/main] avec [action, txtNom, txtAge] dans les lments posts. Son code est le suivant :
1. // validation du formulaire 2. void doValidationFormulaire(HttpServletRequest request, 3. HttpServletResponse response) throws ServletException, IOException{ 4. // on rcupre les paramtres 5. String nom = request.getParameter("txtNom"); 6. String age = request.getParameter("txtAge"); 7. // qu'on mmorise dans la session 8. HttpSession session = request.getSession(true); 9. session.setAttribute("nom", nom); 10. session.setAttribute("age", age); 11. // vrification des paramtres 12.... 13. // les paramtres sont corrects - on envoie la page rponse 14. request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire")); 15. getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request, 16. response); 17. return; 18. }

Les nouveauts :

ligne 15 : la mthode [doValidationFormulaire] envoie en rponse la vue [rponse]. Celle-ci a dans son modle les lments [nom, age, lienRetourFormulaire]. [lienRetourFormulaire] est mis dans le modle ligne 14, via la requte. Les lments [nom,age] sont eux mis dans le modle lignes 8-10, via la session. Dans la version prcdente, on avait plac les lments [nom, age] galement dans la requte lorsqu'on envoyait la vue [rponse] cat cette vue les attendait l. Ici, avec la bibliothque JSTL, on sait que les diffrents contextes (request, session, application) vont tre explors pour trouver les lments du modle. Ils seront donc trouvs dans la session puisque le contrleur les y a mis (lignes 810).

9.5.3

La mthode [doRetourFormulaire]

Cette mthode traite la requte n 3 [POST /personne4/main] avec [action=retourFormulaire] dans les lments posts. Son code est le suivant :
1. 2. 3. 4. 5. 6. 7. // affichage formulaire pr-rempli void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on affiche le formulaire getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; }

Les bases du dveloppement web MVC en Java, par l'exemple

113/264

Les nouveauts :

ligne 4 : on affiche la vue [formulaire]. Celle-ci attend dans son modle des attributs " nom " et " age ". Elle les trouvera dans la session puisque la mthode [doValidationFormulaire] les y a mis et que cette mthode est forcment excute avant la mthode [doRetourFormulaire]. Il n'y a donc pas initialiser le modle de [formulaire] avant son affichage ligne 4. Du coup, les mthodes [doInit] et [doRetourFormulaire] sont identiques et on pourrait supprimer l'action [retourFormulaire] pour la remplacer par l'action [init]. La mthode [doRetourFormulaire] disparatrait alors.

9.6

Tests

Dans cette nouvelle version, seules les vues changent. Le contrleur [ServletPersonne] lui ne change pas. L'utilisation de JSTL nous a simplement permis d'exploiter plus simplement dans les pages JSP, le modle construit par le contrleur. Lancer ou relancer Tomcat aprs [http://localhost:8080/personne4]. y avoir intgr le projet Eclipse [personne-mvc-04]. Demander l'url

10 Application web MVC [personne] version 5


10.1 Introduction

Dans cette version, nous apportons deux modifications : La premire porte sur la faon utilise par le client pour indiquer au serveur l'action qu'il dsire faire. Jusqu' maintenant celleci tait prcise l'aide d'un paramtre appel [action] dans la requte du GET ou du POST du client. Ici, l'action sera prcise par le dernier lment de l'Url demande par le client comme le montre la squence suivante :

En [1], l'Url laquelle a t poste le formulaire est [/personne5/do/validationFormulaire]. C'est le dernier lment [validationFormulaire] de l'Url qui a permis au contrleur de reconnatre l'action faire. En [2], le POST provoqu par le lien [Retour au formulaire] a t fait l'Url [/personne5/do/retourFormulaire]. L encore, le dernier lment [retourFormulaire] de l'Url indique au contrleur l'action faire. Nous introduisons cette modification parce que c'est la mthode utilise par les frameworks de dveloppement web les plus rpandus tels Struts ou Spring MVC. Toutes les Url de l'application auront la forme [/personne5/do/action]. Le fichier [web.xml] de l'application [/personne5] indiquera que celle-ci accepte des Url de la forme [/do/*] :
1. <servlet-mapping> 2. <servlet-name>personne</servlet-name> 3. <url-pattern>/do/*</url-pattern> 4.</servlet-mapping>

Le contrleur rcuprera le nom de l'action faire de la faon suivante :


1. // on rcupre l'action excuter 2. String action=request.getPathInfo();

La mthode [getPathInfo] de l'objet [request] donne le dernier lment de l'Url de la requte. La seconde modification apporte porte sur la faon de mmoriser les saisies faites par l'utilisateur entre deux cycles demande / rponse. Pour l'instant, ces informations sont mmorises dans une session. Cette mthode peut prsenter des inconvnients s'il Les bases du dveloppement web MVC en Java, par l'exemple 114/264

y a beaucoup d'utilisateurs et beaucoup de donnes mmoriser pour chacun d'eux. En effet, chaque utilisateur a sa session personnelle. De plus celle-ci reste active un certain temps aprs le dpart d'un utilisateur sauf si on a pris soin de lui offrir une option de dconnexion. Ainsi 1000 sessions de 1000 octets vont occuper 1Mo de mmoire. Cela reste une exigence modre et il y a peu d'applications qui ont 1000 sessions actives simultanment. Nanmoins, il existe des alternatives la session, moins coteuses en mmoire et il est bon de les connatre. Nous utiliserons ici la mthode des cookies. Illustrons-l sur un exemple. Etape 1 : l'utilisateur valide un formulaire :

Ce cycle demande / rponse donne lieu aux changes HTTP suivants entre le client et le serveur : [1] : [demande du client]
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. POST /personne5/do/validationFormulaire HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.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. Il n'y a rien de particulier signaler ici, si ce n'est que bien qu'on ne va pas utiliser de session, le serveur web en cre une quand mme. On le voit au jeton de session que le navigateur renvoie au serveur ligne 11 et qu'il avait reu prcdemment du serveur. [2] : [rponse du serveur]
1. 2. 3. 4. 5. 6. 7. HTTP/1.x 200 OK Server: Apache-Coyote/1.1 Set-Cookie: nom=pauline Set-Cookie: age=18 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 547 Date: Mon, 22 May 2006 08:03:51 GMT

On voit que lignes 3 et 4, des enttes HTTP [Set-Cookie] ont t envoys au navigateur client, un pour le nom (ligne 3) et un pour l'ge (ligne 4). Les valeurs de ces cookies sont les valeurs postes ligne 14 du POST [1] ci-dessus. Etape 2 : Retour au formulaire

Les bases du dveloppement web MVC en Java, par l'exemple

115/264

2 1 1

Ce cycle demande / rponse donne lieu aux changes HTTP suivants entre le client et le serveur : [1] : [demande du client]
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. POST /personne5/do/retourFormulaire HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/personne5/do/validationFormulaire Cookie: nom=pauline; age=18; 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]. Ligne 11, nous voyons que le navigateur renvoie au serveur, les cookies qu'il a reus [nom, age, JSESSIONID] au moyen de l'entte Http [Cookie]. C'est le principe des cookies. Le client renvoie au navigateur les cookies que celui-ci lui a envoys. Dans cet exemple, le contrleur va recevoir les valeurs [pauline, 18] qu'il doit placer dans les champs [txtNom, txtAge] de la vue [formulaire] affiche en [2]. [2] : [rponse du serveur]
1. 2. 3. 4. 5. HTTP/1.x 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 2341 Date: Mon, 22 May 2006 08:16:47 GMT

Il n'y a l rien signaler de particulier autre que le fait que dans cette rponse, le serveur n'a pas envoy de cookies. Cela n'empchera pas le navigateur de renvoyer au prochain change tous les cookies qu'il a reus du serveur mme si cela ne sert rien. On diminue donc la pression sur la mmoire disponible du serveur au prix d'une augmentation du flux de caractres dans les changes client / serveur.

10.2

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-05] de l'application web [/personne5], on dupliquera le projet [mvc-personne-04] en suivant la procdure dcrite au paragraphe 6.2, page 78.

Les bases du dveloppement web MVC en Java, par l'exemple

116/264

10.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.

Configuration de l'application web [personne5]


<?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-05</display-name> <!-- ServletPersonne --> <servlet> <servlet-name>personne</servlet-name> <servlet-class> istia.st.servlets.personne.ServletPersonne </servlet-class> ... </servlet> <!-- Mapping ServletPersonne--> <servlet-mapping> <servlet-name>personne</servlet-name> <url-pattern>/do/*</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 /personne5 est le suivant :

Ce fichier est identique celui de la version prcdente hormis quelques dtails : ligne 6 : le nom d'affichage de l'application web a chang en [mvc-personne-05] ligne 18 : les Url traites par l'application sont de la forme [/do/*]. Auparavant, seule l'Url [/main] tait traite. Maintenant, on a autant d'Url que d'actions traiter.

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="/do/formulaire"/>

ligne 5 : la page [index.jsp] redirige le client vers l'url [/personne5/do/formulaire], ce qui revient demander au contrleur d'excuter l'action [formulaire].

Les bases du dveloppement web MVC en Java, par l'exemple

117/264

10.4

Le code des vues

Les vues [formulaire, rponse, erreurs] changent peu. L'unique changement vient du fait que l'action accomplir n'est plus prcise de la mme faon qu'auparavant o elle tait dfinie dans un champ cach nomm [action] des formulaires posts. Maintenant elle est dfinie dans l'Url cible des formulaires posts, c.a.d. dans l'attribut [action] de la balise <form> : [formulaire.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. ... <html> <head> <title>Personne - formulaire</title> <script language="javascript"> ... </script> </head> <body> <center> <h2>Personne - formulaire</h2> <hr> <form name="frmPersonne" action="validationFormulaire" method="post"> ... </form> </center> </body> </html>

ligne [13]: le paramtre [action] du formulaire rapparat aprs avoir disparu un certain temps dans les versions prcdentes. Pour comprendre la valeur de cet attribut ici, il faut se rappeler que toutes les Url traites par l'application sont de la forme [/do/action]. Ligne [13], l'attribut [action] a pour valeur une Url relative (ne commenant pas par /). Aussi le navigateur va-t-il la complter avec l'Url de la page actuellement affiche donc ncessairement une Url de la forme [/do/action]. Le dernier lment 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. le champ cach [action] a disparu

[rponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. ... <html> ... <body> ... <form name="frmPersonne" action="retourFormulaire" method="post"> </form> <a href="javascript:document.frmPersonne.submit();"> ${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.

[erreurs.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. ... <html> ... <body> ... <form name="frmPersonne" action="retourFormulaire" method="post"> </form> <a href="javascript:document.frmPersonne.submit();"> ${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.

Le lecteur est invit tester ces nouvelles vues selon le principe vu dans les versions prcdentes.
Les bases du dveloppement web MVC en Java, par l'exemple

118/264

10.5
n 1 2

Le contrleur [ServletPersonne]
demande origine url tape par l'utilisateur clic sur le bouton [Envoyer] de la vue [formulaire] traitement - envoyer la vue [formulaire] vide - vrifier les valeurs des paramtres [txtNom, txtAge] - si elles sont incorrectes, envoyer la vue [erreurs(erreurs)] - si elles sont correctes, envoyer la vue [reponse(nom,age)] - envoyer la vue [formulaire] pr-remplie avec les dernires valeurs saisies

Le contrleur [ServletPersonne] de l'application web [/personne5] va traiter les actions suivantes :

[GET /personne5/do/formulaire] [POST /personne5/do/validationFormulaire] avec paramtres [txtNom, txtAge] posts [POST /personne5/do/retourFormulaire] sans paramtres posts

clic sur le lien [Retour au formulaire] des vues [rponse] et [erreurs].

Le squelette du contrleur [ServletPersonne] est identique celui de la version prcdente. Nous passons en revue les modifications amenes aux mthodes [doValidationFormulaire, doRetourFormulaire, doGet], les mthodes [init, doInit, doPost] ne changeant pas.

10.5.1

La mthode [doGet]

La mthode [doGet] ne rcupre pas l'action excuter de la mme faon que dans les versions prcdentes :
1. @SuppressWarnings("unchecked") 2. public void doGet(HttpServletRequest request, HttpServletResponse response) 3. throws IOException, ServletException { 4. 5. // on vrifie comment s'est passe l'initialisation de la servlet 6. if (erreursInitialisation.size() != 0) { 7. ... 8. } 9. // on rcupre la mthode d'envoi de la requte 10. String mthode=request.getMethod().toLowerCase(); 11. // on rcupre l'action excuter 12. String action=request.getPathInfo(); 13. // action ? 14. if(action==null){ 15. action="/formulaire"; 16. } 17. // excution action 18. if(mthode.equals("get") && action.equals("/formulaire")){ 19. // dmarrage application 20. doInit(request,response); 21. return; 22. } 23. if(mthode.equals("post") && action.equals("/validationFormulaire")){ 24. // validation du formulaire de saisie 25. doValidationFormulaire(request,response); 26. return; 27. } 28. if(mthode.equals("post") && action.equals("/retourFormulaire")){ 29. // retour au formulaire de saisie 30. doRetourFormulaire(request,response); 31. return; 32. } 33. // autres cas 34. doInit(request,response); 35. }

ligne 12 : on rcupre l'action excuter. Elle est de la forme [/action]. lignes 18-22 : traitement de l'action [/formulaire] demande par une requte GET lignes 23-27 : traitement de l'action [/validationFormulaire] demande par une requte POST lignes 28-32 : traitement de l'action [/retourFormulaire] demande par une requte POST

10.5.2

La mthode [doValidationFormulaire]

Cette mthode traite la requte n 2 [POST /personne5/do/validationFormulaire] avec [txtNom, txtAge] dans les lments posts. Son code est le suivant :
Les bases du dveloppement web MVC en Java, par l'exemple

119/264

1.// validation du formulaire 2. void doValidationFormulaire(HttpServletRequest request, 3. HttpServletResponse response) throws ServletException, IOException{ 4. // on rcupre les paramtres 5. String nom = request.getParameter("txtNom"); 6. String age = request.getParameter("txtAge"); 7. // qu'on mmorise dans un cookie 8. response.addCookie(new Cookie("nom",nom)); 9. response.addCookie(new Cookie("age",age)); 10. // vrification des paramtres 11. ... 12. }

Les nouveauts :

la mthode [doValidationFormulaire] envoie en rponse l'une des vues [rponse, erreurs]. Quelle que soit cette rponse, le contrleur met dedans deux cookies, lignes 8-9. Un cookie est reprsent par un objet [Cookie] dont le constructeur admet deux paramtres, la cl du cookie et la valeur associe celle-ci. 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 rponse HTTP faite au client avec la mthode [response.addCookie]. Cette rponse n'est ici que prpare. Elle ne sera envoye vritablement que par excution de la page JSP de la vue envoye au client.

10.5.3

La mthode [doRetourFormulaire]

Cette mthode traite la requte n 2 [POST /personne5/do/retourFormulaire] sans lments posts. Son code 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. // affichage formulaire pr-rempli void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on rcupre les cookies de l'utilisateur Cookie[] cookies=request.getCookies(); String nom=null; String age=null; int nbCookies=0; for(int i=0;i<cookies.length && nbCookies<2;i++){ if(cookies[i].getName().equals("nom")){ nom=cookies[i].getValue(); nbCookies++; }else{ if(cookies[i].getName().equals("age")){ age=cookies[i].getValue(); nbCookies++; } } } // on prpare le modle du formulaire request.setAttribute("nom",nom); request.setAttribute("age",age); // on affiche le formulaire getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; }

Les nouveauts : La mthode [doRetourFormulaire] doit faire afficher un formulaire pr-rempli avec les dernires saisies faites. Dans la version prcdente, celles-ci taient dans la session. Dans celle-ci, on n'utilise plus la session mais des cookies pour mmoriser des lments entre deux changes client-serveur. Lorsque le client a demand la validation du formulaire, il a reu en rponse la vue [rponse] ou [erreurs] selon les cas, accompagne de deux cookies libells " nom " et " age ". Lors du clic sur le lien [Retour au formulaire] de ces deux vues qui provoque un POST sur l'Url [/do/retourFormulaire], le navigateur va renvoyer au serveur les deux cookies qu'il a reus.

lignes 4-18 : on rcupre les valeurs des cookies libells " nom " et " age ". Assez bizarrement, il n'existe pas de mthode permettant d'avoir la valeur d'un cookie partir de sa cl. Aussi est-on oblig de passer en revue chacun des cookies reu. ceci fait, les deux valeurs obtenues sont places dans le modle de la vue [formulaire] (lignes 20-21) afin que celle-ci les affiche.

Les bases du dveloppement web MVC en Java, par l'exemple

120/264

10.6

Tests

Lancer ou relancer Tomcat aprs y avoir intgr le projet Eclipse [personne-mvc-05] puis demander l'url [http://localhost:8080/personne5].

11 Application web MVC [personne] version 6


11.1 Introduction

Dans cette version, nous apportons la modification suivante : La version prcdente n'a pas utilis la session pour garder en mmoire des lments entre deux changes client / serveur. Elle a utilis la technique des cookies, qui fait que les lments mmoriser sont envoys au client par le serveur afin que celui-ci les lui renvoie lors du prochain change. Dans cette nouvelle version, nous utilisons une technique proche, celles des champs cachs dans les formulaires. Il existe des diffrences entre ces deux techniques : Champs cachs - le serveur place les lments mmoriser dans le document - le serveur place les lments mmoriser dans le flux HTTP HTML. qui prcde le document HTML. - la technique ncessite que le navigateur accepte les cookies - la technique ncessite 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 cachs soient envoys au serveur. GET ou POST. - l'utilisateur a accs aux lments mmoriss en faisant - l'utilisateur a accs aux lments mmoriss en faisant afficher afficher les cookies reus par son navigateur. La plupart des le code source du document HTML reu. navigateurs proposent cette option. La technique des champs cachs est utilisable ici car tous les appels du client sont de type POST. Cookies

11.2

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-06] de l'application web [/personne6], on dupliquera le projet [mvc-personne-05] en suivant la procdure dcrite au paragraphe 6.2, page 78.

11.3

Configuration de l'application web [personne6]


121/264

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


Les bases du dveloppement web MVC en Java, par l'exemple

1. 2. 3. 4. 5. 6. 7.

<?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-06</display-name> ...

Ce fichier est identique celui de la version prcdente hormis quelques dtails : ligne 6 : le nom d'affichage de l'application web a chang en [mvc-personne-06]

La page d'accueil [index.jsp] est identique celle de l'application [/personne5] :


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="/do/formulaire"/>

11.4

Le code des vues

Seules les vues [rponse, erreurs] changent. Elles ont maintenant des champs cachs dans leurs formulaires respectifs alors que dans la version prcdente, ces formulaires ne postaient auncun paramtre. [rponse.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. ... <html> ... <body> ... <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.frmPersonne.submit();"> ${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 cachs [nom] et [age] seront posts. Leurs valeurs ${nom} et ${age} seront fixes par le contrleur qui affichera [rponse.jsp]. Ce seront les valeurs saisies dans le formulaire.

[erreurs.jsp] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. ... <html> ... <body> ... <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.frmPersonne.submit();"> ${lienRetourFormulaire} </a> </body> </html>

Les modifications et les explications sont les mmes que pour la vue [rponse.jsp]. On notera que le modle de la vue [erreurs] s'enrichit de deux nouveaux lments [nom, age] qui viennent s'ajouter aux deux autres qui existaient dj : [erreurs, lienRetourFormulaire]. Le lecteur est invit tester ces nouvelles vues selon le principe vu dans les versions prcdentes.

Les bases du dveloppement web MVC en Java, par l'exemple

122/264

11.5

Le contrleur [ServletPersonne]

Le contrleur [ServletPersonne] de l'application web [/personne6] est trs proche celui de la version prcdente. Les changements viennent du fait que le modle de la vue [erreurs] a chang. Il y faut mettre deux lments supplmentaires : [nom, age]. Seules les mthodes faisant afficher cette vue sont concernes. Il s'agit des mthodes [doGet] et [doValidationFormulaire].

11.5.1

La mthode [doGet]

Le code de [doGet] est le suivant :


1. // GET 2. @SuppressWarnings("unchecked") 3. public void doGet(HttpServletRequest request, HttpServletResponse response) 4. throws IOException, ServletException { 5. 6. // on vrifie comment s'est passe l'initialisation de la servlet 7. if (erreursInitialisation.size() != 0) { 8. // on passe la main la page d'erreurs 9. request.setAttribute("erreurs", erreursInitialisation); 10. getServletContext().getRequestDispatcher(urlErreurs).forward( 11. request, response); 12. // fin 13. return; 14. } 15. ... 16. }

En fait, ce code reste identique ce qu'il tait. Dans la version prcdente, les lments [nom, age] n'taient pas mis dans le modle de la vue [erreurs]. Si on continue ne pas les y mettre, les variables ${nom} et ${age} de [erreurs.jsp] seront remplaces par la chane vide. Cela nous convient, car dans ce cas prcis, le lien [Retour au formulaire] n'est pas prsent l'utilisateur. En effet, nous ne mettons pas non plus l'lment [lienRetourFormulaire] dans le modle. La variable ${lienRetourFormulaire} de [erreurs.jsp] sera remplace par la chane vide. Il n'y aura donc pas de lien, donc pas possibilit de poster les champs cachs [nom, age] du formulaire de [erreurs.jsp]. La valeur de ces champs peut donc tre la chane vide.

11.5.2

La mthode [doValidationFormulaire]

Son code est le suivant :


1. // validation du formulaire 2. void doValidationFormulaire(HttpServletRequest request, 3. HttpServletResponse response) throws ServletException, IOException{ 4. // on rcupre les paramtres 5. String nom = request.getParameter("txtNom"); 6. String age = request.getParameter("txtAge"); 7. // on prpare le modle des vues [rponse, erreurs] 8. request.setAttribute("nom",nom); 9. request.setAttribute("age",age); 10. request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire")); 11. // vrification des paramtres 12....

Lignes 8-10, on met dans le modle les lments [nom, age, lienRetourFormulaire]. On sait que la mthode affiche l'une des vues [rponse] ou [erreurs]. Cette dernire aura donc les lments [nom,age] dans son modle.

11.6

Tests

Lancer ou relancer Tomcat aprs y avoir intgr le projet Eclipse [personne-mvc-06] puis demander l'url [http://localhost:8080/personne6].

12 Application web MVC [personne] version 7


12.1 Introduction

Dans cette version, nous supposons qu'il peut y avoir des navigateurs clients qui ont inhib : 1. le renvoi des cookies qu'envoie le serveur 2. l'excution de code Javascript embarqu dans les pages HTML affiches

Les bases du dveloppement web MVC en Java, par l'exemple

123/264

On veut nanmoins que ce type de navigateurs puisse utiliser notre application. Le point 2 nous ramne la version 2 de notre application, le Javascript ayant t utilis partir de la version 3. La version 2 faisait fonctionner l'application sans Javascript donc le point 2 est rsolu. Le point 1 peut tre dlicat ou non grer. La version 6 de notre application fonctionnait sans cookies. En fusionnant les versions 2 et 6, nous obtenons le rsultat demand. Nous allons ajouter une contrainte supplmentaire : l'application doit grer une session. Ce n'est pas une contrainte dnue de sens. Dans une application o les utilisateurs doivent s'authentifier, le serveur doit mmoriser le couple (identifiant / mot de passe) de l'utilisateur pour lui viter de s'authentifier chaque page qu'il demande. Nous avons utilis jusqu' maintenant trois solutions pour mmoriser des informations au fil des changes client / serveur : 1. la session 2. les cookies 3. les champs cachs. La solution 2 peut tre limine puisque le navigateur client peut avoir inhib l'utilisation des cookies. La solution 3 est celle de la version 6 prcdemment tudie. Elle ne peut tre utilise pour des raisons de scurit. Si le couple (login / mot de passe) est encapsul dans chaque page envoye au navigateur, cela veut dire qu'il transite sur le rseau chaque change client / serveur. Cela n'est pas bon pour la scurit de l'application. On peut alors envisager d'utiliser le protocole HTTPS qui crypte les changes client / serveur. Mais l'utiliser pour chaque page de l'application va alourdir la charge du serveur. On pourrait vouloir liminer la solution 1 parce qu'elle est galement base de cookies. Lors du premier change client / serveur, le serveur envoie au client un jeton de session que celui-ci va renvoyer au serveur chaque nouvelle demande. Grce ce jeton, le serveur va pouvoir reconnatre son client et lui affecter des informations qu'il avait mmorises lors d'un change prcdent. Le jeton de session est envoy par le serveur dans un cookie. Le navigateur qui n'a pas inhib ses cookies peut lui renvoyer ce cookie lors de ses demandes suivantes. S'il a inhib ses cookies, il dispose d'une autre solution : il peut inclure le jeton de session dans l'Url qu'il demande. C'est ce que nous voyons maintenant en reprenant l'tude du fichier [index.jsp] de la version 4 :
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"/>

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 aprs avoir demand l'Url [http://localhost:8080/personne4] :

Examinons de plus prs le fonctionnement de la balise <c:redirect> pour ce qui est du jeton de session. Prenons un navigateur o les cookies sont accepts. Ci-dessous, on configure le navigateur Firefox :

Les bases du dveloppement web MVC en Java, par l'exemple

124/264

En [1], nous autorisons les cookies et en [2] nous supprimons ceux qui existent dj afin de partir d'une situation connue. Puis nous demandons l'Url [http://localhost:8080/personne4]. Nous obtenons la rponse suivante :

La demande HTTP initiale du client a t la suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. GET /personne4/ HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive

On notera simplement que le client n'envoie pas de cookie de session. La rponse HTTP envoye par le serveur est la suivante :
1. 2. 3. 4. 5. 6. 7. HTTP/1.x 302 Dplac Temporairement Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC; Path=/personne4 Location: http://localhost:8080/personne4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC Content-Type: text/html;charset=ISO-8859-1 Content-Length: 0 Date: Tue, 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. La balise <c:redirect> l'y a plac parce que le client n'avait pas envoy de cookie de session.

Le navigateur, qui on a demand de se rediriger, a ensuite fait la demande suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. GET /personne4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

Les bases du dveloppement web MVC en Java, par l'exemple

125/264

ligne 1 : il demande l'Url de redirection, jeton de session inclus. C'est pourquoi sur la copie d'cran, le navigateur affiche-t-il cette Url. ligne 10 : le navigateur renvoie le jeton de session que lui a envoy dans l'change prcdent le serveur. C'est le fonctionnement normal des cookies lorsque ceux-ci sont autoriss sur le navigateur client. Si ce n'est pas le cas, les cookies reus ne sont pas renvoys.

Le serveur a rpondu la chose suivante cette seconde demande :


1. 2. 3. 4. 5. HTTP/1.x 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 2376 Date: Tue, 23 May 2006 09:10:05 GMT

Il a trouv la page qu'on lui demandait et il l'envoie. On notera qu'il n'envoie plus le jeton de session. 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 reconnatre. Maintenant, avec le mme navigateur, demandons de nouveau l'Url [http://localhost:8080/personne4] en la tapant la main. On obtient alors la page suivante :

On constate que l'Url affiche par le navigateur ne contient plus le jeton de session. Regardons le premier change client / serveur : Le navigateur a fait la demande suivante :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. GET /personne4 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

C'est exactement la mme demande que la fois prcdente avec cependant une diffrence : ligne 10, le navigateur renvoie le jeton de session qu'il avait reu lors du tout premier change. Encore une fois, c'est le fonctionnement normal si les cookies du navigateur sont actifs. Le serveur a envoy la rponse suivante :
1. 2. 3. 4. 5. HTTP/1.x 302 Dplac Temporairement Server: Apache-Coyote/1.1 Location: http://localhost:8080/personne4/ Transfer-Encoding: chunked Date: Tue, 23 May 2006 09:24:39 GMT

Il demande au client de se rediriger. Comme il a reu un jeton de session du client, il poursuit celle-ci et n'envoie pas de nouveau jeton de session. Pour la mme raison, la balise <c:redirect> n'inclut pas ce jeton de session dans l'Url de redirection. C'est pourquoi l'Url affiche par la copie d'cran ci-dessus n'a-t-elle pas de jeton de session. On retiendra de tout a la rgle 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'entte HTTP :
Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

Cette rgle est galement vraie pour la balise <c:url> que nous aurons l'occasion de rencontrer ultrieurement.
Les bases du dveloppement web MVC en Java, par l'exemple

126/264

Que se passe-t-il avec un navigateur sur lequel on a inhib les cookies ? Essayons. Tout d'abord nous rinitialisons le navigateur :

1 2

En [1], nous inhibons les cookies et en [2] nous supprimons ceux qui existent dj afin de partir d'une situation connue. Puis nous demandons l'Url [http://localhost:8080/personne4]. Nous obtenons la rponse suivante :

Nous obtenons le mme rsultat que prcdemment. Les changes HTTP ne sont pourtant pas exactement les mmes :
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. GET /personne4/ HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive HTTP/1.x 302 Dplac Temporairement Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=911B8156E0A9D32C2D256020C898E05C; Path=/personne4 Location: http://localhost:8080/personne4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C Content-Type: text/html;charset=ISO-8859-1 Content-Length: 0 Date: Tue, 23 May 2006 09:39:55 GMT GET /personne4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive HTTP/1.x 200 OK Server: Apache-Coyote/1.1 Content-Type: text/html;charset=ISO-8859-1 Content-Length: 2376 Date: Tue, 23 May 2006 09:39:55 GMT

lignes 1-9 : la demande n 1 du navigateur. Il n'envoie pas de cookie de session. lignes 11-17 : la rponse du serveur qui lui demande de se rediriger vers une autre Url. Il envoie un cookie de session ligne 13 : la balise <c:redirect> a inclus le jeton dans l'Url de redirection ligne 14. lignes 19-27 : la demande n 2 du navigateur. Il ne renvoie pas le cookie de session que vient de lui envoyer le serveur parce que ses cookies sont inhibs. lignes 29-33 : la rponse du serveur. On peut constater que bien que le navigateur ne lui ait pas envoy de cookie de session, il ne redmarre cependant pas une nouvelle session comme on aurait pu s'y attendre. On voit cela au fait qu'il n'envoie l'entte HTTP [Set-Cookie] comme il l'avait fait ligne 13. Cela signifie qu'il continue la session prcdente. Il a pu retrouver celle-ci grce au jeton de session prsent dans l'Url demande par le navigateur ligne 19. 127/264

Les bases du dveloppement web MVC en Java, par l'exemple

On retiendra que le serveur suit une session en rcuprant le jeton de session envoy par le client, de deux faons possibles : dans l'entte HTTP [Set-Cookie] envoy par le client dans l'Url demande par le client Maintenant, avec le mme navigateur, demandons de nouveau l'Url [http://localhost:8080/personne4] en la tapant la main, comme il avait t fait lorsque les cookies taient autoriss. On obtient alors la page suivante :

On a un rsultat diffrent de celui obtenu lorsque les cookies taient autoriss : le jeton de session est dans l'Url affiche par le navigateur. Exliquons ce rsultat sans tudier les changes HTTP qui ont eu lieu : [cookies autoriss] lors de la seconde demande de l'Url [http://localhost:8080/personne4], le navigateur client avait renvoy le cookie de session qu'il avait reu du serveur lors de la premire demande de cette mme Url. La balise <c:redirect> n'avait donc pas inclus le jeton de session dans l'adresse de redirection. [cookies inhibs] lors de la seconde demande de l'Url [http://localhost:8080/personne4], le navigateur client n'envoie pas le cookie de session qu'il a reu du serveur lors de la premire demande de cette mme Url, puisque ses cookies sont inhibs. La balise <c:redirect> inclut donc le jeton de session dans l'adresse de redirection. C'est pourquoi on le trouve sur la copie d'cran ci-dessus. Les balises <c:redirect> et <c:url> permettent d'inclure le jeton de session dans les Url. C'est la solution qui est propose ici.

12.2

Le projet Eclipse

Pour crer le projet Eclipse [mvc-personne-07] de l'application web [/personne7], on dupliquera le projet [mvc-personne-06] en suivant la procdure dcrite au paragraphe 6.2, page 78.

12.3

Configuration de l'application web [personne7]


128/264

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


Les bases du dveloppement web MVC en Java, par l'exemple

1. 2. 3. 4.

<?xml version="1.0" encoding="UTF-8"?> ... <display-name>mvc-personne-07</display-name> ...

Ce fichier est identique celui de la version prcdente hormis la ligne 3 o le nom d'affichage de l'application web a chang en [mvc-personne-07]. La page d'accueil [index.jsp] ne change pas.
1. 2. ... <c:redirect url="/do/formulaire"/>

12.4

Le code des vues

Les vues [formulaire, rponse, erreurs] redeviennent ce qu'elles taient dans la version 2, c.a.d. sans Javascript. Cependant elles conservent les balises JSTL des dernires versions.

12.4.1

La vue [formulaire]

On a enlev les boutons associs du code Javascript. [formulaire.jsp] :


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. <%@ 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> </head> <body> <center> <h2>Personne - 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="Rtablir"></td> <td><input type="submit" name="bouton" value="Effacer"></td> </tr> </table> </form> </center> </body> </html>

Les bases du dveloppement web MVC en Java, par l'exemple

129/264

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'entte HTTP [Cookie]. le formulaire a deux boutons de type [submit] : [Envoyer] (ligne 28) et [Effacer] (ligne 30). Les deux boutons portent le mme nom : bouton. Au moment du POST, le navigateur enverra le paramtre : 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 paramtre qui nous aidera dfinir l'action exacte faire, l'Url [/do/validationFormulaire] correspondant maintenant deux actions distinctes.

12.4.2

La vue [rponse]

[rponse.jsp] :
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. <%@ 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 - rponse</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'entte HTTP [Cookie].

12.4.3

La vue [erreurs]

[erreurs.jsp] :
Les bases du dveloppement web MVC en Java, par l'exemple

130/264

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.

<%@ 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> <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'entte HTTP [Cookie].

Le lecteur est invit tester ces nouvelles vues selon le principe vu dans les versions prcdentes.

12.5
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.

Le contrleur [ServletPersonne]
package istia.st.servlets.personne; ... @SuppressWarnings("serial") public class ServletPersonne extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres={"urlFormulaire","urlReponse","lienRetourFormulaire"}; private Map params=new HashMap<String,String>(); // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... } // GET @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { ... // on rcupre la mthode d'envoi de la requte String mthode=request.getMethod().toLowerCase(); // on rcupre l'action excuter String action=request.getPathInfo(); if(mthode.equals("post") && action.equals("/validationFormulaire")){ // validation du formulaire de saisie doValidationFormulaire(request,response); return; } if(mthode.equals("get") && action.equals("/retourFormulaire")){ // retour au formulaire de saisie doRetourFormulaire(request,response); return; } // autres cas doInit(request,response);

Le contrleur [ServletPersonne] de l'application web [/personne7] est le suivant :

...

// affichage formulaire vide void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ 46. ... 47. } 48. 49. // affichage formulaire pr-rempli Les bases du dveloppement web MVC en Java, par l'exemple

131/264

50. 51. 52. 53. 54. 55. 56. 57. 58.

void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on affiche le formulaire getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; } // affichage formulaire vide void doEffacer(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ // on prpare le modle du formulaire HttpSession session = request.getSession(true); session.setAttribute("nom", ""); session.setAttribute("age", ""); // on affiche le formulaire getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward( request, response); return; }

59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. // validation du formulaire 70. void doValidationFormulaire(HttpServletRequest request, 71. HttpServletResponse response) throws ServletException, IOException{ 72. // on rcupre le bouton qui a provoqu le POST 73. String bouton = request.getParameter("bouton").toLowerCase(); 74. // traitement selon le bouton qui a provoqu le POST 75. if(bouton==null){ 76. doInit(request,response); 77. return; 78. } 79. if("envoyer".equals(bouton)){ 80. doEnvoyer(request,response); 81. return; 82. } 83. if("effacer".equals(bouton)){ 84. doEffacer(request,response); 85. return; 86. } 87. } 88. 89. // validation du formulaire 90. void doEnvoyer(HttpServletRequest request, 91. HttpServletResponse response) throws ServletException, IOException{ 92. // on rcupre les paramtres 93. String nom = request.getParameter("txtNom"); 94. String age = request.getParameter("txtAge"); 95. // qu'on mmorise dans la session 96. HttpSession session = request.getSession(true); 97. session.setAttribute("nom", nom); 98. session.setAttribute("age", age); 99. // le lien du retour au formulaire est mis dans le modle des vues [rponse, erreurs] 100. request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire")); 101. // vrification des paramtres 102. ArrayList<String> erreursAppel = new ArrayList<String>(); 103. ... 104. // des erreurs dans les paramtres ? 105. if (erreursAppel.size() != 0) { 106. // on envoie la page d'erreurs 107. request.setAttribute("erreurs", erreursAppel); 108. getServletContext().getRequestDispatcher(urlErreurs).forward( 109. request, response); 110. return; 111. } 112. // les paramtres sont corrects - on envoie la page rponse 113. getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request, 114. response); 115. return; 116. } 117. 118. // post 119. public void doPost(HttpServletRequest request, HttpServletResponse response) 120. throws IOException, ServletException { 121.... 122. } 123.}

ligne 35 : l'action [/retourFormulaire] est faite par un GET et non plus par un POST comme dans la version prcdente. 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]. La mthode [doValidationFormulaire] fait traiter ces deux cas par deux mthodes diffrentes.

Les bases du dveloppement web MVC en Java, par l'exemple

132/264

lignes 90-103 : la mthode [doEnvoyer] correspond la mthode [doValidationFormulaire] de la version prcdente. Les donnes saisies sont mises dans la session (lignes 96-98) alors dans la version prcdente elles taient places dans la requte. lignes 58-67 : la nouvelle mthode [doEffacer] doit afficher un formulaire vide. On pourrait faire appel la mthode [doInit] qui fait dj ce travail. Ici, on en profite pour galement effacer les lments [nom, age] de la session afin que celle-ci continue reflter le dernier tat du formulaire. lignes 50-55 : demandent l'affichage de la vue [formulaire] sans initialisation apparente du modle de cette vue. Ce modle est en fait constitu des lments [nom, age] dj dans la session. Il n'y a pas lieu de faire davantage.

12.6

Tests

Lancer ou relancer Tomcat aprs y avoir intgr 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 dj. On obtient la rponse suivante :

Le code source reu par le navigateur est le suivant :


1. 2. 3. <form name="frmPersonne" action="validationFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3" method="post"> ... </form>

Ligne 1, le jeton de session est dans l'Url cible du POST. Remplissons le formulaire et validons-le :

Le code source reu par le navigateur est le suivant :


1. 2. 3. 4. ... <br> <a href="retourFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3">Retour au formulaire</a> </body>

Ligne 3, le jeton de session est dans l'Url cible du lien.

13 Application web MVC [personne] version 8


La version 8 sera identique la version 7 mais dploye dans une archive war (Web ARchive). Dans Eclipse, nous cliquons droit sur le projet [mvc-personne-07] et prenons l'option [export] :

Les bases du dveloppement web MVC en Java, par l'exemple

133/264

1 2

Choisissons dans la liste droulante [1] le nom du module exporter, ici [mvc-personne-07] et avec le bouton [Browse] indiquons le fichier .war produire, ici [personne8.war]. Terminons le processus avec le bouton [Finish] et avec l'explorateur windows allons voir le fichier qui a t produit :

Un fichier .war est analogue un fichier .zip et peut tre dcompress avec les mmes outils. Dcompressons-le et passons en revue tous les lments de son arborescence :

Nous pouvons constater que tous les lments du projet [mvc-personne-07] sont l, les codes source ayant t remplacs par leurs quivalents compils placs dans [WEB-INF/classes] comme l'exige la norme de dploiement des servlets. Nous allons dployer l'application web [personne8.war] au sein de Tomcat en suivant la procdure dcrite au paragraphe 8.1.2, page 104 pour le dploiement de la documentation de la bibliothque JSTL. Nous lanons Tomcat via l'option adquate du menu [Dmarrer] puis nous demandons l'Url [http://localhost:8080] et suivons le lien [Tomcat Manager] :

Les bases du dveloppement web MVC en Java, par l'exemple

134/264

Nous obtenons alors une page d'authentification. Nous nous identifions comme manager / manager ou admin / admin, comme il a t montr au paragraphe 2.3.3, page 14.

Nous obtenons une page listant les applications actuellement dployes dans Tomcat :

Nous pouvons ajouter une nouvelle application grce des formulaires placs en bas de la page :

Nous utilisons le bouton [Parcourir] pour dsigner un fichier .war dployer.

La copie d'cran ne le montre pas, mais nous avons slectionn le fichier [personne8.war] cr prcdemment. Le bouton [Deploy] enregistre et dploie cette application au sein de Tomcat.

Les bases du dveloppement web MVC en Java, par l'exemple

135/264

Si on dploie un fichier [XX.war], le contexte de l'application (ou le nom de l'application) sera XX. C'est ce que montre [1]. La colonne [2] montre le nom d'affichage de l'application. Ce nom est fix dans le fichier [web.xml] par la balise <diplay-name>. Dans l'application [mvc-personne-07] archive dans [personne8.jar] on avait :
<display-name>mvc-personne-07</display-name>

Le nom d'affichage de l'application est donc [mvc-personne-07], ce que montre [2]. Ouvrons un navigateur et demandons l'url [http://localhost:8080/personne8] :

Le lecteur est invit poursuivre les tests. L'archivage d'une application web dans un fichier .war est le mode normal de distribution et de dploiement d'une application web.

14 Application web MVC dans une architecture 3tier Exemple 1


14.1 Prsentation
Jusqu maintenant, nous nous sommes contents dexemples vise pdagogique. Pour cela, ils se devaient dtre simples. Nous prsentons maintenant, une application basique mais nanmoins plus riche que toutes celles prsentes jusqu maintenant. Elle aura la particularit dutiliser les trois couches dune 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 oublis, au paragraphe 4, page 58. Lapplication web que nous allons crire va permettre de grer un groupe de personnes avec quatre oprations :

couche [metier]
Modles

couche [dao]

Donnes

liste des personnes du groupe ajout dune personne au groupe modification dune personne du groupe suppression dune personne du groupe

On reconnatra les quatre oprations de base sur une table de base de donnes. Nous crirons deux versions de cette application :
Les bases du dveloppement web MVC en Java, par l'exemple

136/264

dans la version 1, la couche [dao] nutilisera pas de base de donnes. Les personnes du groupe seront stockes dans un simple objet [ArrayList] gr en interne par la couche [dao]. Cela permettra au lecteur de tester lapplication sans contrainte de base de donnes. dans la version 2, nous placerons le groupe de personnes dans une table de base de donnes. Nous montrerons que cela se fera sans impact sur la couche web de la version 1 qui restera inchange.

Les copies dcran qui suivent montrent les pages que lapplication change avec lutilisateur.

Une liste initiale de personnes est tout dabord prsente lutilisateur. Il peut ajouter une personne -> Lutilisateur a cr une nouvelle personne quil valide avec le bouton [Valider] ->

La nouvelle personne a t ajoute. On la modifie maintenant -> On modifie la date de naissance, ltat marital, le nombre denfants et on valide ->

Elle nest plus l. On ajoute une personne -> On retouve la personne telle quelle a t modifie. On la supprime maintenant ->

Les bases du dveloppement web MVC en Java, par l'exemple

137/264

Les erreurs de saisie sont signales ->

On notera que le formulaire a t renvoy tel quil a t saisi (Nombre denfants). Le lien [Annuler] permet de revenir la liste des personnes ->

14.2

Le projet Eclipse

Le projet de lapplication sappelle [personnes-01] :

Ce projet recouvre les trois couches de larchitecture 3tier de lapplication :

Les bases du dveloppement web MVC en Java, par l'exemple

138/264

Application web
couche [web]

Servlet Utilisateur JSP1 JSP2 JSPn


couche [metier]
Modles

couche [dao]

Donnes

la couche [dao] est contenue dans le paquetage [istia.st.mvc.personnes.dao] la couche [metier] ou [service] est contenue dans le paquetage [istia.st.mvc.personnes.service] la couche [web] ou [ui] est contenue dans le paquetage [istia.st.mvc.personnes.web] le paquetage [istia.st.mvc.personnes.entites] contient les objets partags entre diffrentes couches le paquetage [istia.st.mvc.personnes.tests] contient les tests Junit des couches [dao] et [service]

Nous allons explorer successivement les trois couches [dao], [service] et [web]. Parce que ce serait trop long crire et peuttre trop ennuyeux lire, nous serons peut-tre parfois un peu rapides sur les explications sauf lorsque ce qui est prsent est nouveau.

14.3

La reprsentation dune personne

Lapplication gre un groupe de personnes. Les copies dcran page 137 ont montr certaines des caractristiques dune personne. Formellement, celles-ci sont reprsentes par une classe [Personne] :

La classe [Personne] est 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. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. package istia.st.springmvc.personnes.entites; import java.text.SimpleDateFormat; import java.util.Date; public class Personne { // identifiant unique de la personne private int id; // la version actuelle private long version; // le nom private String nom; // le prnom private String prenom; // la date de naissance private Date dateNaissance; // l'tat marital private boolean marie = false; // le nombre d'enfants private int nbEnfants; // getters - setters ... // constructeur par dfaut public Personne() { } // constructeur avec initialisation des champs de la personne public Personne(int id, String prenom, String nom, Date dateNaissance, boolean marie, int nbEnfants) { setId(id); setNom(nom); setPrenom(prenom); setDateNaissance(dateNaissance); setMarie(marie); setNbEnfants(nbEnfants); } // constructeur d'une personne par recopie d'une autre personne

Les bases du dveloppement web MVC en Java, par l'exemple

139/264

43. public Personne(Personne p) { 44. setId(p.getId()); 45. setVersion(p.getVersion()); 46. setNom(p.getNom()); 47. setPrenom(p.getPrenom()); 48. setDateNaissance(p.getDateNaissance()); 49. setMarie(p.getMarie()); 50. setNbEnfants(p.getNbEnfants()); 51. } 52. 53. 54. // toString 55. public String toString() { 56. return "[" + id + "," + version + "," + prenom + "," + nom + "," 57. + new SimpleDateFormat("dd/MM/yyyy").format(dateNaissance) 58. + "," + marie + "," + nbEnfants + "]"; 59. } 60. }

une personne est identifie par les informations suivantes : id : un n identifiant de faon 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 denfants lattribut [version] est un attribut artificiellement ajout pour les besoins de lapplication. Dun point de vue objet, il aurait t sans doute prfrable dajouter cet attribut dans une classe drive de [Personne]. Son besoin apparat lorsquon fait des cas dusage de lapplication web. Lun dentre-eux est le suivant : Au temps T1, un utilisateur U1 entre en modification dune personne P. A ce moment, le nombre denfants est 0. Il passe ce nombre 1 mais avant quil ne valide sa modification, un utilisateur U2 entre en modification de la mme personne P. Puisque U1 na pas encore valid sa modification, U2 voit le nombre denfants 0. U2 passe le nom de la personne P en majuscules. Puis U1 et U2 valident leurs modifications dans cet ordre. Cest la modification de U2 qui va gagner : le nom va passer en majuscules et le nombre denfants va rester zro alors mme que U1 croit lavoir chang en 1. La notion de version de personne nous aide rsoudre ce problme. On reprend le mme cas dusage : Au temps T1, un utilisateur U1 entre en modification dune personne P. A ce moment, le nombre denfants est 0 et la version V1. Il passe le nombre denfants 1 mais avant quil ne valide sa modification, un utilisateur U2 entre en modification de la mme personne P. Puisque U1 na pas encore valid sa modification, U2 voit le nombre denfants 0 et la version V1. U2 passe le nom de la personne P en majuscules. Puis U1 et U2 valident leurs modifications dans cet ordre. Avant de valider une modification, on vrifie que celui qui modifie une personne P dtient la mme version que la personne P actuellement enregistre. Ce sera le cas de lutilisateur U1. Sa modification est donc accepte et on change alors la version de la personne modifie de V1 V2 pour noter le fait que la personne a subi un changement. Lors de la validation de la modification de U2, on va sapercevoir quil dtient une version V1 de la personne P, alors quactuellement la version de celle-ci est V2. On va alors pouvoir dire lutilisateur U2 que quelquun est pass avant lui et quil doit repartir de la nouvelle version de la personne P. Il le fera, rcuprera une personne P de version V2 qui a maintenant un enfant, passera le nom en majuscules, validera. Sa modification sera accepte si la personne P enregistre a toujours la version V2. Au final, les modifications faites par U1 et U2 seront prises en compte alors que dans le cas dusage sans version, lune des modifications tait perdue.

lignes 32-40 : un constructeur capable dinitialiser les champs dune personne. On omet le champ [version]. lignes 43-51 : un constructeur qui cre une copie de la personne quon lui passe en paramtre. On a alors deux objets de contenu identique mais rfrencs par deux pointeurs diffrents. ligne 55 : la mthode [toString] est redfinie pour rendre une chane de caractres reprsentant ltat de la personne

14.4

La couche [dao]

La couche [dao] est constitue des classes et interfaces suivantes :

[IDao] est linterface prsente par la couche [dao] 140/264

Les bases du dveloppement web MVC en Java, par l'exemple

[DaoImpl] est une implmentation de celle-ci o le groupe de personnes est encapsul dans un objet [ArrayList] [DaoException] est un type dexceptions non contrles (unchecked), lances par la couche [dao]

Linterface [IDao] est la suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. package istia.st.springmvc.personnes.dao; import istia.st.springmvc.personnes.entites.Personne; import java.util.Collection; public interface IDao { // liste de toutes les personnes Collection getAll(); // obtenir une personne particulire Personne getOne(int id); // ajouter/modifier une personne void saveOne(Personne personne); // supprimer une personne void deleteOne(int id); }

linterface a quatre mthodes pour les quatre oprations que lon souhaite faire sur le groupe de personnes : getAll : pour obtenir une collection de personnes getOne : pour obtenir une personne ayant un id prcis saveOne : pour ajouter une personne (id=-1) ou modifier une personne existante (id <> -1) deleteOne : pour supprimer une personne ayant un id prcis

La couche [dao] est susceptible de lancer des exceptions. Celles-ci seront de type [DaoException] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. package istia.st.springmvc.personnes.dao; public class DaoException extends RuntimeException { // code erreur private int code; public int getCode() { return code; } // constructeur public DaoException(String message,int code) { super(message); this.code=code; } }

ligne 3 : la classe [DaoException] drivant de [RuntimeException] est un type dexception non contrle : le compilateur ne nous oblige pas : grer ce type dexceptions avec un try / catch lorsquon appelle une mthode pouvant la lancer mettre le marqueur " throws DaoException " dans la signature dune mthode susceptible de lancer lexception Cette technique nous vite davoir signer les mthodes de linterface [IDao] avec des exceptions dun type particulier. Toute implmentation lanant des exceptions non contrles sera alors acceptable amenant ainsi de la souplesse dans larchitecture.

ligne 6 : un code derreur. La couche [dao] lancera diverses exceptions qui seront identifies par des codes derreur diffrents. Cela permettra la couche qui dcidera de grer lexception de connatre lorigine exacte de lerreur et de prendre ainsi les mesures appropries. Il y a dautres faons darriver au mme rsultat. Lune delles est de crer un type dexception pour chaque type derreur possible, par exemple NomManquantException, PrenomManquantException, AgeIncorrectException, ... lignes 13-16 : le constructeur qui permettra de crer une exception identifie par un code derreur ainsi quun message derreur. lignes 8-10 : la mthode qui permettra au code de gestion dune exception den rcuprer le code derreur.

La classe [DaoImpl] implmente linterface [IDao] :


1. 2. 3. 4. package istia.st.springmvc.personnes.dao; import istia.st.springmvc.personnes.entites.Personne;

Les bases du dveloppement web MVC en Java, par l'exemple

141/264

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, "Mlanie", "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 paramtre 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 dveloppement 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. // gnrateur 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. // rsultat ? 125. return trouv ? i : -1; 126. } 127. 128. // vrification 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. // prnom 152. if (p.getPrenom() == null || p.getPrenom().trim().length() == 0) { 153. throw new DaoException("Prnom 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 nallons donner que les grandes lignes de ce code. Nous passerons cependant un peu de temps sur les parties les plus dlicates.
Les bases du dveloppement web MVC en Java, par l'exemple

143/264

ligne 13 : lobjet [ArrayList] qui va contenir le groupe de personnes ligne 16 : lidentifiant de la dernire personne ajoute. A chaque nouvel ajout, cet identifiant va tre incrment de 1.

La classe [DaoImpl] va tre instancie en un unique exemplaire. Cest ce quon appelle un singleton. Une application web sert ses utilisateurs de faon simultane. Il y a un moment donn plusieurs threads excuts par le serveur web. Ceux-ci se partagent les singletons : celui de la couche [dao] celui de la couche [service] ceux des diffrents contrleurs, validateurs de donnes, ... de la couche web Si un singleton a des champs privs, il faut tout de suite se demander pourquoi il en a. Sont-ils justifis ? En effet, ils vont tre partags entre diffrents threads. Sils sont en lecture seule, cela ne pose pas de problme sils peuvent tre initialiss un moment o on est sr quil ny a quun thread actif. On sait en gnral trouver ce moment. Cest celui du dmarrage de lapplication web alors quelle na pas commenc servir des clients. Sils sont en lecture / criture alors il faut mettre en place une synchronisation daccs aux champs sinon on court la catastrophe. Nous illustrerons ce problme lorsque nous testerons la couche [dao].

la classe [DaoImpl] na pas de constructeur. Cest donc son constructeur par dfaut qui sera utilis. lignes 19-38 : la mthode [init] sera appele au moment de linstanciation du singleton de la couche [dao]. Elle cre une liste de trois personnes. lignes 41-43 : implmente la mthode [getAll] de linterface [IDao]. Elle rend une rfrence sur la liste des personnes. lignes 46-55 : implmente la mthode [getOne] de linterface [IDao]. Son paramtre est lid de la personne cherche. Pour la rcuprer, on fait appel une mthode prive [getPosition] des lignes 113-126. Cette mthode rend la position dans la liste, de la personne cherche ou -1 si la personne na pas t trouve. Si la personne a t trouve, la mthode [getOne] rend une rfrence (ligne 51) sur une copie de cette personne et non sur la personne elle-mme. En effet, lorsquun utilisateur va vouloir modifier une personne, les informations sur celle-ci vont-tre demandes la couche [dao] et remontes jusqu la couche [web] pour modification, sous la forme dune rfrence sur un objet [Personne]. Cette rfrence va servir de conteneur de saisies dans le formulaire de modification. Lorsque dans la couche web, lutilisateur va poster ses modifications, le contenu du conteneur de saisies va tre modifi. Si le conteneur est une rfrence sur la personne relle du [ArrayList] de la couche [dao], alors celle-ci est modifie alors mme que les modifications nont pas t prsentes aux couches [service] et [dao]. Cette dernire est la seule habilite grer la liste des personnes. Aussi faut-il que la couche web travaille sur une copie de la personne modifier. Ici la couche [dao] dlivre cette copie. Si la personne cherche nest pas trouve, une exception de type [DaoException] est lance avec le code derreur 2 (ligne 53).

lignes 94-104 : implmente la mthode [deleteOne] de linterface [IDao]. Son paramtre est lid de la personne supprimer. Si la personne supprimer nexiste pas, une exception de type [DaoException] est lance avec le code derreur 2. lignes 58-91 : implmente la mthode [saveOne] de linterface [IDao]. Son paramtre est un objet [Personne]. Si cet objet un id=-1, alors il sagit dun ajout de personne. Sinon, il sagit de modifier la personne de la liste ayant cet id avec les valeurs du paramtre.

ligne 60 : la validit du paramtre [Personne] est vrifie par une mthode prive [check] dfinie aux lignes 129-155. Cette mthode fait des vrifications basiques sur la valeur des diffrents champs de [Personne]. A chaque fois quune anomalie est dtecte, une [DaoException] avec un code derreur spcifique est lanc. Comme la mthode [saveOne] ne gre pas cette exception, elle remontera la mthode appelante. lignes 62 : si le paramtre [Personne] a son id gal -1, alors il sagit dun ajout. Lobjet [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 paramtre [Personne] a un [id] diffrent de -1, il sagit de modifier la personne de la liste interne ayant cet [id]. Tout dabord, on vrifie (lignes 70-75) que la personne modifier existe. Si ce nest pas le cas, on lance une exception de type [DaoException] avec le code derreur 2. si la personne est bien prsente, on vrifie que sa version actuelle est la mme que celle du paramtre [Personne] qui contient les modifications apporter loriginal. Si ce nest pas le cas, cela signifie que celui qui veut faire la modification de la personne nen dtient pas la dernire version. On le lui dit en lanant une exception de type [DaoException] avec le code derreur 3 (lignes 79-80). si tout va bien, les modifications sont faites sur loriginal de la personne (lignes 85-90) 144/264

Les bases du dveloppement web MVC en Java, par l'exemple

On sent bien que cette mthode doit tre synchronise. Par exemple, entre le moment o on vrifie que la personne modifier est bien l et celui o la modification va tre faite, la personne a pu tre supprime de la liste par quelquun dautre. La mthode devrait tre donc dclare [synchronized] afin de sassurer quun seul thread la fois lexcute. Il en est de mme pour les autres mthodes de linterface [IDao]. Nous ne le faisons pas, prfrant dplacer cette synchronisation dans la couche [service]. Pour mettre en lumire les problmes de synchronisation, lors des tests de la couche [dao] nous arrterons lexcution de [saveOne] pendant 10 ms (ligne 83) entre le moment o on sait quon peut faire la modification et le moment o on la fait rellement. Le thread qui excute [saveOne] perdra alors le processeur au profit dun autre. Nous augmentons ainsi nos chances de voir apparatre des conflits daccs 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 problmes daccs concurrents la liste des personnes, des threads de type [ThreadDaoMajEnfants] sont crs. Ils sont chargs daugmenter de 1 le nombre denfants dune personne donne. [TestDao] a cinq tests [test1] [test5]. Nous ne prsentons que deux dentre-eux, le lecteur tant invit dcouvrir 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 lment inexistant public void test2() throws ParseException { ... } // gestion des versions de personne public void test3() throws ParseException, InterruptedException { ... } // optimistic locking - accs multi-threads public void test4() throws Exception { ... } // tests de validit de saveOne

Les bases du dveloppement web MVC en Java, par l'exemple

145/264

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

ligne 9 : rfrence sur l'implmentation de la couche [dao] teste lignes 12-15 : le constructeur du test JUnit. Il cre une instance de type [DaoImpl] de la couche [dao] tester et l'initialise.

La mthode [test1] teste les quatre mthodes de linterface [IDao] de la faon 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(); // vrification - on aura un plantage si la personne n'est pas trouve p1 = dao.getOne(id1); assertEquals("X", p1.getNom()); // modification p1.setNom("Y"); dao.saveOne(p1); // vrification - on aura un plantage si la personne n'est pas trouve p1 = dao.getOne(id1); assertEquals("Y", p1.getNom()); // suppression dao.deleteOne(id1); // vrification 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,Mlanie,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 mthodes de linterface [IDao] sontelles utilises.

lignes 8-10 : on ajoute une nouvelle personne (id=-1). ligne 11 : on rcupre lid de la personne ajoute car lajout lui en a donn un. Avant elle nen avait pas. ligne 13-14 : on demande la couche [dao] une copie de la personne qui vient dtre ajoute. Il faut se rappeler que si la personne demande nest pas trouve, la couche [dao] lance une exception. On aura alors un plantage ligne 13. On aurait pu grer ce cas plus proprement. Ligne 14, on vrifie le nom de la personne retrouve. lignes 16-17 : on modifie ce nom et on demande la couche [dao] denregistrer les modifications. lignes 19-20 : on demande la couche [dao] une copie de la personne qui vient dtre ajoute et on vrifie son nouveau nom. ligne 22 : on supprime la personne ajoute au dbut du test. lignes 23-34 : on demande la couche [dao] une copie de la personne qui vient dtre supprime. On doit obtenir une [DaoException] de code 2. lignes 36-37 : la liste des personnes est redemande. On doit obtenir la mme quau dbut du test.

La mthode [test4] cherche mettre en lumire les problmes daccs concurrents aux mthodes de la couche [dao]. Rappelons que celles-ci nont pas t synchronises. Le code du test est le suivant :
1. public void test4() throws Exception { Les bases du dveloppement web MVC en Java, par l'exemple

146/264

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. }

// ajout d'une personne Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat( "dd/MM/yyyy").parse("01/02/2006"), true, 0); dao.saveOne(p1); int id1 = p1.getId(); // cration de N threads de mise jour du nombre d'enfants final int N = 10; Thread[] taches = new Thread[N]; for (int i = 0; i < taches.length; i++) { taches[i] = new ThreadDaoMajEnfants("thread n " + i, dao, id1); taches[i].start(); } // on attend la fin des threads for (int i = 0; i < taches.length; i++) { taches[i].join(); } // on rcupre la personne p1 = dao.getOne(id1); // elle doit avoir N enfants assertEquals(N, p1.getNbEnfants()); // suppression personne p1 dao.deleteOne(p1.getId()); // vrification boolean erreur = false; int codeErreur = 0; try { p1 = dao.getOne(p1.getId()); } catch (DaoException ex) { erreur = true; codeErreur = ex.getCode(); } // on doit avoir une erreur de code 2 assertTrue(erreur); assertEquals(2, codeErreur);

lignes 3-6 : on ajoute dans la liste une personne P avec aucun enfant. On note son [id] (ligne 6). lignes 7-13 : on lance N threads. Chacun deux va incrmenter le nombre denfants de la personne P de 1 unit. Au final, la personne P devra avoir N enfants. lignes 15-17 : la mthode [test4] qui a lanc les N threads attend quils aient termin leur travail avant de regarder le nouveau nombre denfants de la personne P. lignes 18-21 : on rcupre la personne P et on vrifie que son nombre denfants est N. lignes 22-35 : la personne P est supprime puis on vrifie quelle nexiste plus dans la liste.

Ligne 11, on voit que les threads sont de type [ThreadDaoMajEnfants]. Le constructeur de ce type a trois paramtres : 1. le nom donn au thread, ceci pour le suivre au moyen de logs 2. une rfrence sur la couche [dao] afin que le thread y ait accs 3. lid de la personne sur laquelle le thread doit travailler Le type [ThreadDaoMajEnfants] 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. 27. 28. 29. 30. package istia.st.mvc.personnes.tests; import java.util.Date; import istia.st.mvc.personnes.dao.DaoException; import istia.st.mvc.personnes.dao.IDao; import istia.st.mvc.personnes.entites.Personne; public class ThreadDaoMajEnfants extends Thread { // nom du thread private String name; // rfrence sur la couche [dao] private IDao dao; // l'id de la personne sur qui on va travailler private int idPersonne; // constructeur public ThreadDaoMajEnfants(String name, IDao dao, int idPersonne) { this.name = name; this.dao = dao; this.idPersonne = idPersonne; } // coeur du thread public void run() { // suivi suivi("lanc"); // on boucle tant qu'on n'a pas russi incrmenter de 1 // le nbre d'enfants de la personne idPersonne boolean fini = false;

Les bases du dveloppement web MVC en Java, par l'exemple

147/264

31. int nbEnfants = 0; 32. while (!fini) { 33. // on rcupre une copie de la personne d'idPersonne 34. Personne personne = dao.getOne(idPersonne); 35. nbEnfants = personne.getNbEnfants(); 36. // suivi 37. suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version "+personne.getVersion()); 38. // attente de 10 ms pour abandonner le processeur 39. try { 40. // suivi 41. suivi("dbut attente"); 42. // on s'interrompt pour laisser le processeur 43. Thread.sleep(10); 44. // suivi 45. suivi("fin attente"); 46. } catch (Exception ex) { 47. throw new RuntimeException(ex.toString()); 48. } 49. // attente termine - on essaie de valider la copie 50. // entre-temps d'autres threads ont pu modifier l'original 51. int codeErreur = 0; 52. try { 53. // incrmente de 1 le nbre d'enfants de cette copie 54. personne.setNbEnfants(nbEnfants + 1); 55. // on essaie de modifier l'original 56. dao.saveOne(personne); 57. // on est pass - l'original a t modifi 58. fini = true; 59. } catch (DaoException ex) { 60. // on rcupre le code erreur 61. codeErreur = ex.getCode(); 62. // doit tre une erreur de version 3 - sinon on relance 63. // l'exception 64. if (codeErreur != 3) { 65. throw ex; 66. } else { 67. // suivi 68. suivi(ex.getMessage()); 69. } 70. // l'original a chang - on recommence tout 71. } 72. } 73. // suivi 74. suivi("a termin et pass le nombre d'enfants " + (nbEnfants + 1)); 75. } 76. 77. // suivi 78. private void suivi(String message) { 79. System.out 80. .println(name + " [" + new Date().getTime()+ "] : " + message); 81. } 82. }

ligne 9 : [ThreadDaoMajEnfants] est bien un thread lignes 18-22 : le constructeur qui initialise le thread avec trois informations 1. le nom [name] donn au thread 2. une rfrence [dao] sur la couche [dao]. On notera quune nouvelle fois, nous travaillons avec le type de linterface [IDao] et non celui de limplmentation [DaoImpl]. 3. lidentifiant [id] de la personne sur laquelle le thread doit travailler

Lorsque [test4] lance un thread [ThreadDaoMajEnfants] (ligne 12 de test4), la mthode [run] (ligne 25) de celui-ci est excute :

lignes 78-81 : la mthode prive [suivi] permet de faire des logs cran. La mthode [run] en use pour permettre le suivi du thread dans son excution. le thread va chercher incrmenter de 1 le nombre denfants de la personne P didentifiant [id]. Cette mise jour peut ncessiter plusieurs tentatives. Prenons deux threads [TH1] et [TH2]. [TH1] demande une copie de la personne P la couche [dao]. Il lobtient et constate quelle a la version V1. [TH1] est interrompu. [TH2] qui le suivait fait la mme chose et obtient la mme version V1 de la personne P. [TH2] est interrompu. [TH2] reprend la main, incrmente le nombre denfants de P et sauvegarde ses modifications. Nous savons qualors, celles-ci sont sauvegardes et que la version de P va passer V2. [TH1] a fini son travail. [TH2] reprend la main et fait de mme. Sa mise jour de P sera refuse car il dtient une copie de P de version V1 alors que loriginal P a dsormais la version V2. [TH2] doit alors reprendre tout le cycle [lecture -> mise jour -> sauvegarde]. Cest pourquoi, nous trouvons la boucle des lignes 3272. Dans celle-ci, le thread : demande une copie de la personne P modifier (ligne 34) attend 10 ms (ligne 43). 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 daugmenter la probabilit de conflits. 148/264

Les bases du dveloppement web MVC en Java, par l'exemple

incrmente le nombre denfants de P (ligne 54) et sauvegarde P (ligne 56). Si le thread na pas la bonne version de P, une exception sera dclenche par la couche [dao]. On rcupre alors le code de lexception (ligne 61) pour vrifier que cest bien le code 3 (mauvaise version de P). Si ce nest pas le cas, on relance lexception destination de la mthode appelante, au final la mthode de test [test4]. Si on a lexception de code 3, alors on recommence le cycle [lecture -> mise jour -> sauvegarde]. Si on na pas dexception, alors la mise jour a t faite et le travail du thread est termin.

Que donnent les tests ? Dans la premire configuration teste :

on commente linstruction dattente dans la mthode [saveOne] de [DaoImpl] (ligne 83, page 142).
// on attend 10 ms //wait(10);

la mthode [test4] cre 100 threads (ligne 8, page 146).


// cration de N threads de mise jour du nombre d'enfants final int N = 100;

On obtient les rsultats suivants :

Les cinq tests ont t russis. Dans la seconde configuration teste :

on dcommente linstruction dattente dans la mthode [saveOne] de [DaoImpl] (ligne 83, page 142).
// on attend 10 ms wait(10);

la mthode [test4] cre 2 threads (ligne 8, page 146).


// cration de N threads de mise jour du nombre d'enfants final int N = 2;

On obtient les rsultats suivants :

Le test [test4] a chou. On a cr deux threads chargs chacun dincrmenter de 1 le nombre denfants dune personne P qui au dpart en avait 0. On attendait donc 2 enfants aprs excution des deux threads, or on nen a quun. Les bases du dveloppement web MVC en Java, par l'exemple 149/264

Suivons les logs cran de [test4] pour comprendre ce qui sest pass :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 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 dbut attente lanc 0 -> 1 pour la version 1 dbut 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 rcupr une copie de la personne P et trouve son nombre denfants 0 ligne 3 : il rencontre le [Thread.sleep(10)] de sa mthode [run] et sarrte donc au temps [1145536368171] (ms) ligne 4 : le thread n 1 rcupre alors le processeur et commence son travail ligne 5 : il a rcupr une copie de la personne P et trouve son nombre denfants 0 ligne 6 : il rencontre le [Thread.sleep(10)] de sa mthode [run] et sarrte donc ligne 7 : le thread n 0 rcupre le processeur au temps [1145536368187] (ms), c.a.d. 16 ms aprs lavoir perdu. ligne 8 : idem pour le thread n 1 ligne 9 : le thread n 0 a fait sa mise jour et pass le nombre denfants 1 ligne 10 : le thread n 1 a fait de mme

La question est de savoir pourquoi le thread n 1 a-t-il pu faire sa mise jour alors que normalement il ne dtenait plus la bonne version de la personne P qui venait dtre mise jour par le thread n 0. Tout dabord, 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. Que faisait-il ce moment ? Il excutait la mthode [saveOne] de la couche [dao]. Celle-ci a le squelette suivant (cf page 142) :
1. public void saveOne(Personne personne) { 2.... 3. // modification - on cherche la personne 4..... 5. // a-t-on la bonne version de l'original ? 6.... 7. // on attend 10 ms 8. wait(10); 9. // c'est bon - on fait la modification 10. ... 11.}

le thread n 0 a excut [saveOne] et est all jusqu la ligne 8 o l, il a t oblig de lcher le processeur. Entretemps, il a lu la version de la personne P et ctait 1 parce que la personne P navait pas encore t mise jour. le processeur tant devenu libre, cest le thread n 1 qui en a hrit. Il a, son tour, excut [saveOne] et est all jusqu la ligne 8 o l il a t oblig de lcher le processeur. Entre-temps, il a lu la version de la personne P et ctait 1 parce que la personne P navait toujours pas t mise jour. le processeur tant devenu libre, cest le thread n 0 qui en a hrit. A partir de la ligne 9, il a fait sa mise jour et pass le nombre denfants 1. Puis la mthode [run] du thread n 0 sest termine et le thread a affich le log qui disait quil avait pass le nombre denfants 1 (ligne 9). le processeur tant devenu libre, cest le thread n 1 qui en a hrit. A partir de la ligne 9, il a fait sa mise jour et pass le nombre denfants 1. Pourquoi 1 ? Parce quil dtient une copie de P avec un nombre denfants 0. Cest le log (ligne 5) qui le dit. Puis la mthode [run] du thread n 1 sest termine et le thread a affich le log qui disait quil avait pass le nombre denfants 1 (ligne 10).

Do vient le problme ? Il vient du fait que le thread n 0 na pas eu le temps de valider sa modification et donc de changer la version de la personne P avant que le thread n 1 nessaie de lire cette version pour savoir si la personne P avait chang. Ce cas de figure est peu probable mais pas impossible. Il a fallu forcer le thread n 0 perdre le processeur pour le faire apparatre avec simplement deux threads. Sans cet artifice, la configuration prcdente navait pas russi faire apparatre ce mme cas avec 100 threads. Le test [test4] avait t russi. Quelle est la solution ? Il y en a sans doute plusieurs. Lune delles, simple mettre en oeuvre, est de synchroniser la mthode [saveOne] :
public synchronized void saveOne(Personne personne)

Les bases du dveloppement web MVC en Java, par l'exemple

150/264

Le mot cl [synchronized] assure quun seul thread la fois peut excuter la mthode. Ainsi le thread n 1 ne sera-t-il autoris excuter [saveOne] que lorsque le thread n 0 en sera sorti. On est alors sr que la version de la personne P aura t change lorsque le thread n 1 va entrer dans [saveOne]. Sa mise jour sera alors refuse car il naura pas la bonne version de P. Ce sont les quatres mthodes de la couche [dao] quil faudrait synchroniser. Nous dcidons cependant de garder cette couche telle quelle a t dcrite et de reporter la synchronisation sur la couche [service]. A cela plusieurs raisons :

nous faisons lhypothse que laccs la couche [dao] se fait toujours au travers dune couche [service]. Cest le cas dans notre application web. il peut tre ncessaire de synchroniser galement laccs aux mthodes de la couche [service] pour dautres raisons que celles qui nous feraient synchroniser celles de la couche [dao]. Dans ce cas, il est inutile de synchroniser les mthodes de la couche [dao]. Si on est assurs que : tout accs la couche [dao] passe par la couche [service] quun unique thread la fois utilise la couche [service] alors on est assurs que les mthodes de la couche [dao] ne seront pas excuts par deux threads en mme temps.

Nous dcouvrons maintenant la couche [service].

14.6

La couche [service]

La couche [service] est constitue des classes et interfaces suivantes :

[IService] est linterface prsente par la couche [dao] [ServiceImpl] est une implmentation de celle-ci

Linterface [IService] est la suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. package istia.st.springmvc.personnes.service; import istia.st.springmvc.personnes.entites.Personne; import java.util.Collection; public interface IService { // liste de toutes les personnes Collection getAll(); // obtenir une personne particulire Personne getOne(int id); // ajouter/modifier une personne void saveOne(Personne personne); // supprimer une personne void deleteOne(int id); }

Elle est identique linterface [IDao]. Limplmentation [ServiceImpl] de linterface [IService] est la suivante :
1. package istia.st.springmvc.personnes.service; 2. 3. import istia.st.springmvc.personnes.dao.IDao; 4. import istia.st.springmvc.personnes.entites.Personne; 5. 6. import java.util.Collection; 7. 8. public class ServiceImpl implements IService { 9. 10. // la couche [dao] 11. private IDao dao; 12. 13. public IDao getDao() { 14. return dao; 15. } 16. 17. public void setDao(IDao dao) { 18. this.dao = dao; 19. } 20. 21. // liste des personnes 22. public synchronized Collection getAll() { 23. return dao.getAll(); Les bases du dveloppement web MVC en Java, par l'exemple

151/264

24. } 25. 26. // obtenir une personne en particulier 27. public synchronized Personne getOne(int id) { 28. return dao.getOne(id); 29. } 30. 31. // ajouter ou modifier une personne 32. public synchronized void saveOne(Personne personne) { 33. dao.saveOne(personne); 34. } 35. 36. // suppression d'une personne 37. public synchronized void deleteOne(int id) { 38. dao.deleteOne(id); 39. } 40. }

lignes 10-19 : lattribut [IDao dao] est une rfrence sur la couche [dao]. Il sera initialis par Spring IoC. lignes 22-24 : implmentation de la mthode [getAll] de linterface [IService]. La mthode se contente de dlguer la demande la couche [dao]. lignes 27-29 : implmentation de la mthode [getOne] de linterface [IService]. La mthode se contente de dlguer la demande la couche [dao]. lignes 32-34 : implmentation de la mthode [saveOne] de linterface [IService]. La mthode se contente de dlguer la demande la couche [dao]. lignes 37-39 : implmentation de la mthode [deleteOne] de linterface [IService]. La mthode se contente de dlguer la demande la couche [dao]. toutes les mthodes sont synchronises (mot cl synchronized) assurant quun seul thread la fois pourra utiliser la couche [service] et donc la couche [dao].

14.7

Tests de la couche [service]

Un test JUnit est crit pour la couche [service] :

[TestService] est le test JUnit. Les tests faits sont strictement identiques ceux faits pour la couche [dao]. Le squelette de [TestService] 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. 27. 28. package istia.st.springmvc.personnes.tests; ... public class TestService extends TestCase { // couche [service] private ServiceImpl service; // constructeur public TestService() { service = new ServiceImpl(); DaoImpl dao=new DaoImpl(); service.setDao(dao); } // liste des personnes private void doListe(Collection personnes) { ... } // test1 public void test1() throws ParseException { // liste actuelle Collection personnes = service.getAll(); int nbPersonnes = personnes.size(); // affichage doListe(personnes);

Les bases du dveloppement web MVC en Java, par l'exemple

152/264

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.

... }

// ajout d'une personne Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat( "dd/MM/yyyy").parse("01/02/2006"), true, 1); service.saveOne(p1); int id1 = p1.getId(); // vrification - on aura un plantage si la personne n'est pas trouve p1 = service.getOne(id1); assertEquals("X", p1.getNom());

// modification-suppression d'un lment inexistant public void test2() throws ParseException { ... } // gestion des versions de personne public void test3() throws ParseException, InterruptedException { ... } // optimistic locking - accs multi-threads public void test4() throws Exception { // ajout d'une personne Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat( "dd/MM/yyyy").parse("01/02/2006"), true, 0); service.saveOne(p1); int id1 = p1.getId(); // cration de N threads de mise jour du nombre d'enfants final int N = 100; Thread[] taches = new Thread[N]; for (int i = 0; i < taches.length; i++) { taches[i] = new ThreadServiceMajEnfants("thread n " + i, service, id1); taches[i].start(); } ... } // tests de validit de saveOne public void test5() throws ParseException { ... }

lignes 9 : la couche [service] teste de type [ServiceImpl]. lignes 11-15 : le constructeur du test JUnit cre une instance de la couche [service] tester (ligne 12), cre une instance de la couche [dao] (ligne 13) et indique la couche [service] qu'elle doit utiliser cette couche [dao] (ligne 14).

La mthode [test1] teste les quatre mthodes de linterface [IService] de faon identique la mthode de test de la couche [dao] de mme nom. Simplement, on accde la couche [service] (lignes 25, 32, 35) plutt qu la couche [dao]. La mthode [test4] cherche mettre en lumire les problmes daccs concurrents aux mthodes de la couche [service]. Elle est, l encore, identique la mthode de test [test4] de la couche [dao]. Il y a cependant quelques dtails qui changent : on sadresse la couche [service] plutt qu la couche [dao] (ligne 55) on passe aux threads une rfrence la couche [service] plutt qu la couche [dao] (ligne 61) Le type [ThreadServiceMajEnfants] est lui aussi quasi identique au type [ThreadDaoMajEnfants] au dtail prs quil travaille avec la couche [service] et non la couche [dao] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. package istia.st.springmvc.personnes.tests; import istia.st.mvc.personnes.dao.DaoException; import istia.st.mvc.personnes.entites.Personne; import istia.st.mvc.personnes.service.IService; public class ThreadServiceMajEnfants extends Thread { // nom du thread private String name; // rfrence sur la couche [service] private IService service; // l'id de la personne sur qui on va travailler private int idPersonne; public ThreadServiceMajEnfants(String name, IService service, int idPersonne) { this.name = name; this.service = service; this.idPersonne = idPersonne; }

Les bases du dveloppement web MVC en Java, par l'exemple

153/264

21. 22. public void run() { 23. ... 24. } 25. 26. // suivi 27. private void suivi(String message) { 28. System.out.println(name + " : " + message); 29. } 30. 31. }

ligne 12 : le thread travaille avec la couche [service]

Nous faisons les tests avec la configuration qui a pos problme la couche [dao] :

on dcommente linstruction dattente dans la mthode [saveOne] de [DaoImpl] (ligne 83, page 142).
// on attend 10 ms wait(10);

la mthode [test4] cre 100 threads (ligne 65, page 153).


// cration de N threads de mise jour du nombre d'enfants final int N = 100;

Les rsultats obtenus sont les suivants :


thread n 93 [1145541451687] : 98 -> 99 pour la version 99 thread n 93 [1145541451687] : dbut attente thread n 44 [1145541451687] : fin attente thread n 93 [1145541451687] : fin attente thread n 93 [1145541451703] : L'original de la personne [[4,99,X,X,01/02/2006,true,99]] a chang depuis sa lecture initiale thread n 93 [1145541451703] : 99 -> 100 pour la version 100 thread n 93 [1145541451703] : dbut 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 dernires lignes des logs cran Tous les tests ont t russis Cest la synchronisation des mthodes de la couche [service] qui a permis le succs du test [test4].

14.8

La couche [web]

Rappelons larchitecture 3tier de notre application :

Application web
couche [web]

Servlet Utilisateur JSP1 JSP2 JSPn


La couche [web] va offrir des crans lutilisateur pour lui permettre de grer le groupe de personnes :

couche [metier]
Modles

couche [dao]

Donnes

liste des personnes du groupe ajout dune personne au groupe modification dune personne du groupe suppression dune personne du groupe 154/264

Les bases du dveloppement web MVC en Java, par l'exemple

Pour cela, elle va sappuyer sur la couche [service] qui elle mme fera appel la couche [dao]. Nous avons dj prsent les crans grs par la couche [web] (page 137). Pour dcrire la couche web, nous allons prsenter successivement :

sa configuration ses vues son contrleur quelques tests

14.8.1

Configuration de lapplication web

Le projet Eclipse de lapplication est le suivant :

dans le paquetage [istia.st.mvc.personnes.web], on trouve le contrleur [Application]. les pages JSP / JSTL sont dans [WEB-INF/vues]. le dossier [lib] contient les archives tierces ncessaires lapplication. Elles sont visibles dans le dossier [Web App Libraries].

[web.xml] Le fichier [web.xml] est le fichier exploit par le serveur web pour charger lapplication. Son contenu est le suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. <?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-personnes-01</display-name> <!-- ServletPersonne --> <servlet> <servlet-name>personnes</servlet-name> <servlet-class> istia.st.mvc.personnes.web.Application </servlet-class> <init-param>

Les bases du dveloppement web MVC en Java, par l'exemple

155/264

14. <param-name>urlEdit</param-name> 15. <param-value>/WEB-INF/vues/edit.jsp</param-value> 16. </init-param> 17. <init-param> 18. <param-name>urlErreurs</param-name> 19. <param-value>/WEB-INF/vues/erreurs.jsp</param-value> 20. </init-param> 21. <init-param> 22. <param-name>urlList</param-name> 23. <param-value>/WEB-INF/vues/list.jsp</param-value> 24. </init-param> 25. </servlet> 26. <!-- Mapping ServletPersonne--> 27. <servlet-mapping> 28. <servlet-name>personnes</servlet-name> 29. <url-pattern>/do/*</url-pattern> 30. </servlet-mapping> 31. <!-- fichiers d'accueil --> 32. <welcome-file-list> 33. <welcome-file>index.jsp</welcome-file> 34. </welcome-file-list> 35. <!-- Page d'erreur inattendue --> 36. <error-page> 37. <exception-type>java.lang.Exception</exception-type> 38. <location>/WEB-INF/vues/exception.jsp</location> 39. </error-page> 40. </web-app>

lignes 27-30 : les url [/do/*] seront traites par la servlet [personnes] lignes 9-12 : la servlet [personnes] est une instance de la classe [Application], une classe que nous allons construire. lignes 13-24 : dfinissent trois paramtres [urlList, urlEdit, urlErreurs] identifiant les Url des pages JSP des vues [list, edit, erreurs]. lignes 32-34 : lapplication a une page dentre par dfaut [index.jsp] qui se trouve la racine du dossier de lapplication web. lignes 36-39 : lapplication a une page derreurs par dfaut qui est affiche lorsque le serveur web rcupre une exception non gre par l'application. ligne 37 : la balise <exception-type> indique le type d'exception gre par la directive <error-page>, ici le type [java.lang.Exception] et driv, donc toutes les exceptions. ligne 38 : la balise <location> indique la page JSP afficher lorsqu'une exception du type dfini par <exception-type> se produit. 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> prcise un type T1 et qu'une exception de type T2 non driv de T1 remonte jusqu'au serveur web, celui-ci envoie au client une page d'exception propritaire gnralement peu conviviale. D'o l'intrt de la balise <error-page> dans le fichier [web.xml].

[index.jsp] Cette page est prsente si un utilisateur demande directement le contexte de lapplication sans prciser durl, c.a.d. ici [/personnes-01]. Son contenu est le suivant :
1. 2. 3. 4. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <c:redirect url="/do/list"/>

[index.jsp] redirige le client vers lurl [/do/list]. Cette url affiche la liste des personnes du groupe.

14.8.2

Les pages JSP / JSTL de lapplication

La vue [list.jsp] Elle sert afficher la liste des personnes :

Les bases du dveloppement web MVC en Java, par l'exemple

156/264

Son code 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %> <html> <head> <title>MVC - Personnes</title> </head> <body background="<c:url value="/ressources/standard.jpg"/>"> <h2>Liste des personnes</h2> <table border="1"> <tr> <th>Id</th> <th>Version</th> <th>Pr&eacute;nom</th> <th>Nom</th> <th>Date de naissance</th> <th>Mari&eacute;</th> <th>Nombre d'enfants</th> <th></th> </tr> <c:forEach var="personne" items="${personnes}"> <tr> <td><c:out value="${personne.id}"/></td> <td><c:out value="${personne.version}"/></td> <td><c:out value="${personne.prenom}"/></td> <td><c:out value="${personne.nom}"/></td> <td><dt:format pattern="dd/MM/yyyy">${personne.dateNaissance.time}</dt:format></td> <td><c:out value="${personne.marie}"/></td> <td><c:out value="${personne.nbEnfants}"/></td> <td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html>

cette vue reoit un lment dans son modle : l'lment [personnes] associ un objet de type [ArrayList] dobjets de type [Personne] lignes 22-34 : on parcourt la liste ${personnes} pour afficher un tableau HTML contenant les personnes du groupe. ligne 31 : lurl pointe par le lien [Modifier] est paramtre par le champ [id] de la personne courante afin que le contrleur associ lurl [/do/edit] sache quelle est la personne modifier. ligne 32 : il est fait de mme pour le lien [Supprimer]. ligne 28 : pour afficher la date de naissance de la personne sous la forme JJ/MM/AAAA, on utilise la balise <dt> de la bibliothque de balise [DateTime] du projet Apache [Jakarta Taglibs] :

Les bases du dveloppement web MVC en Java, par l'exemple

157/264

Le fichier de description de cette bibliothque de balises est dfini ligne 3.

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. C'est la valeur -1 du paramtre [id] qui indique qu'on a affaire un ajout plutt qu'une modification.

La vue [edit.jsp] Elle sert afficher le formulaire dajout dune nouvelle personne ou de modification dune personne existante :

la vue [list.jsp]

la vue [edit.jsp] Le code de la vue [edit.jsp] 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. 27. 28. 29. <%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %> <html> <head> <title>MVC - Personnes</title> </head> <body background="../ressources/standard.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;nom</td> <td> <input type="text" value="${prenom}" name="prenom" size="20">

Les bases du dveloppement web MVC en Java, par l'exemple

158/264

30. </td> 31. <td>${erreurPrenom}</td> 32. </tr> 33. <tr> 34. <td>Nom</td> 35. <td> 36. <input type="text" value="${nom}" name="nom" size="20"> 37. </td> 38. <td>${erreurNom}</td> 39. </tr> 40. <tr> 41. <td>Date de naissance (JJ/MM/AAAA)</td> 42. <td> 43. <input type="text" value="${dateNaissance}" name="dateNaissance"> 44. </td> 45. <td>${erreurDateNaissance}</td> 46. </tr> 47. <tr> 48. <td>Mari&eacute;</td> 49. <td> 50. <c:choose> 51. <c:when test="${marie}"> 52. <input type="radio" name="marie" value="true" checked>Oui 53. <input type="radio" name="marie" value="false">Non 54. </c:when> 55. <c:otherwise> 56. <input type="radio" name="marie" value="true">Oui 57. <input type="radio" name="marie" value="false" checked>Non 58. </c:otherwise> 59. </c:choose> 60. </td> 61. </tr> 62. <tr> 63. <td>Nombre d'enfants</td> 64. <td> 65. <input type="text" value="${nbEnfants}" name="nbEnfants"> 66. </td> 67. <td>${erreurNbEnfants}</td> 68. </tr> 69. </table> 70. <br> 71. <input type="hidden" value="${id}" name="id"> 72. <input type="hidden" value="${version}" name="version"> 73. <input type="submit" value="Valider"> 74. <a href="<c:url value="/do/list"/>">Annuler</a> 75. </form> 76. </body> 77. </html>

Cette vue prsente un formulaire d'ajout d'une nouvelle personne ou de mise jour d'une personne existante. Par la suite et pour simplifier l'criture, nous utiliserons l'unique terme de [mise jour]. Le bouton [Valider] (ligne 73) provoque le POST du formulaire l'url [/do/validate] (ligne 16). Si le POST choue, la vue [edit.jsp] est raffiche avec la ou les erreurs qui se sont produites, sinon la vue [list.jsp] est affiche.

la vue [edit.jsp] affiche aussi bien sur un GET que sur un POST qui choue, reoit les lments suivants dans son modle : attribut GET identifiant de la personne mise idem jour idem sa version son prnom son nom sa date de naissance son tat marital son nombre d'enfants vide vide vide vide prnom 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]. Vide si pas d'erreur. signale un prnom erron vide sinon signale un nom erron vide sinon signale une date de naissance errone vide sinon 159/264 POST

id version prenom nom dateNaissance marie nbEnfants erreurEdit

erreurPrenom erreurNom erreurDateNaissance

Les bases du dveloppement web MVC en Java, par l'exemple

attribut
erreurNbEnfants

GET vide

POST signale un nombre d'enfants erron vide sinon

lignes 11-15 : si le POST du formulaire se passe mal, on aura [erreurEdit!=''] et un message d'erreur sera affich. ligne 16 : le formulaire sera post lurl [/do/validate] ligne 20 : l'lment [id] du modle est affich ligne 24 : l'lment [version] du modle est affich lignes 26-32 : saisie du prnom de la personne : lors de laffichage initial du formulaire (GET), ${prenom} affiche la valeur actuelle du champ [prenom] de lobjet [Personne] mis jour et ${erreurPrenom} est vide. en cas derreur aprs le POST, on raffiche la valeur saisie ${prenom} ainsi que le message derreur 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 ltat mari ou non de la personne avec un bouton radio. On utilise la valeur du champ [marie] de lobjet [Personne] pour savoir lequel des deux boutons radio doit tre coch. lignes 62-68 : saisie du nombre denfants 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, -1 pour un ajout, autre chose pour une modification. ligne 72 : un champ HTML cach nomm [version] et ayant pour valeur le champ [id] de la personne en cours de mise jour. ligne 73 : le bouton [Valider] de type [Submit] du formulaire ligne 74 : un lien permettant de revenir la liste des personnes. Il a t libell [Annuler] parce quil permet de quitter le formulaire sans le valider.

La vue [exception.jsp] Elle sert afficher une page signalant quil sest produit une exception non gre par lapplication et qui est remonte jusquu serveur web. Par exemple, supprimons une personne qui nexiste pas dans le groupe :

la vue [list.jsp] - il ny a pas de personne did=7 Le code de la vue [exception.jsp] est le suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.

la vue [exception.jsp] on a demand la suppression de la personne did=7 en tapant la main lurl dans le navigateur.

<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%> <%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> <%@ page isErrorPage="true" %> <% %> response.setStatus(200);

<html> <head> <title>MVC - Personnes</title> </head> <body background="<c:url value="/ressources/standard.jpg"/>"> <h2>MVC - personnes</h2> L'exception suivante s'est produite : <%= exception.getMessage()%> <br><br> <a href="<c:url value="/do/list"/>">Retour &agrave; la liste</a> </body> </html>

Les bases du dveloppement web MVC en Java, par l'exemple

160/264

cette vue reoit une cl dans son modle l'lment [exception] qui est lexception qui a t intercepte par le serveur web. Pour que cet lment soit inclus dans le modle de la page JSP par le serveur web, il faut que la page ait dfini la balise de la ligne 3. ligne 6 : on fixe 200 le code d'tat HTTP de la rponse. C'est le premier entte HTTP de la rponse. Le code 200 signifie au client que sa demande a t honore. Gnralement un document HTML a t intgr dans la rponse du serveur. C'est le cas ici. Si on ne fixe pas 200 le code d'tat HTTP de la rponse, il aura ici la valeur 500 qui signifie qu'il s'est produit une erreur. En effet, le serveur web ayant intercept une exception non gre trouve cette situation anormale et le signale par le code 500. La raction au code HTTP 500 diffre selon les navigateurs : Firefox affiche le document HTML qui peut accompagner cette rponse alors qu'IE ignore ce document et affiche sa propre page. C'est pour cette raison que nous avons remplac le code 500 par le code 200. ligne 16 : le texte de lexception est affich ligne 18 : on propose lutilisateur un lien pour revenir la liste des personnes

La vue [erreurs.jsp] Elle sert afficher une page signalant les erreurs d'initialisation de l'application, c.a.d. les erreurs dtectes lors de l'excution de la mthode [init] de la servlet du contrleur. Ce peut tre par exemple l'absence d'un paramtre dans le fichier [web.xml] comme le montre l'exemple ci-dessous :

Le code de la page [erreurs.jsp] est le suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. <%@ 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>MVC - 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 reoit dans son modle un lment [erreurs] qui est un objet de type [ArrayList] d'objets [String], ces derniers tant des messages d'erreurs. Ils sont affichs par la boucle des lignes 13-15.

14.8.3

Le contrleur de lapplication

Le contrleur [Application] est dfini dans le paquetage [istia.st.mvc.personnes.web] :

Structure et initialisation du contrleur Le squelette du contrleur [Application] est le suivant :


1. 2. 3. 4. 5. 6. package istia.st.mvc.personnes.web; import istia.st.mvc.personnes.dao.DaoException; ... @SuppressWarnings("serial")

Les bases du dveloppement web MVC en Java, par l'exemple

161/264

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. 90. 91. 92.

public class Application extends HttpServlet { // paramtres d'instance private String urlErreurs = null; private ArrayList erreursInitialisation = new ArrayList<String>(); private String[] paramtres = { "urlList", "urlEdit", "urlErreurs" }; private Map params = new HashMap<String, String>(); // service ServiceImpl service=null; // init @SuppressWarnings("unchecked") public void init() throws ServletException { // on rcupre les paramtres d'initialisation de la servlet ServletConfig config = getServletConfig(); // on traite les autres paramtres d'initialisation String valeur = null; for (int i = 0; i < paramtres.length; i++) { // valeur du paramtre valeur = config.getInitParameter(paramtres[i]); // paramtre prsent ? if (valeur == null) { // on note l'erreur erreursInitialisation.add("Le paramtre [" + paramtres[i] + "] n'a pas t initialis"); } else { // on mmorise la valeur du paramtre params.put(paramtres[i], valeur); } } // l'url de la vue [erreurs] a un traitement particulier urlErreurs = config.getInitParameter("urlErreurs"); if (urlErreurs == null) throw new ServletException( "Le paramtre [urlErreurs] n'a pas t initialis"); // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(); dao.init(); // instanciation de la couche [service] service = new ServiceImpl(); service.setDao(dao); } // GET @SuppressWarnings("unchecked") public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { .... } // affichage liste des personnes private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } // modification / ajout d'une personne private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } // validation modification / ajout d'une personne private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } // validation modification / ajout d'une personne public void doValidatePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } // affichage formulaire pr-rempli private void showFormulaire(HttpServletRequest request, HttpServletResponse response, String erreurEdit) throws ServletException, IOException{ ... } // post public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // on passe la main au GET doGet(request, response); }

Les bases du dveloppement web MVC en Java, par l'exemple

162/264

lignes 20-36 : on rcupre les paramtres attendus dans le fichier [web.xml]. lignes 39-41 : le paramtre [urlErreurs] doit tre obligatoirement prsent car il dsigne l'url de la vue [erreurs] capable d'afficher les ventuelles erreurs d'initialisation. S'il n'existe pas, on interrompt l'application en lanant une [ServletException] (ligne 40). Cette exception va remonter au serveur web et tre gre par la balise <error-page> du fichier [web.xml]. La vue [exception.jsp] est donc affiche :

Le lien [Retour la liste] ci-dessus est inoprant. L'utiliser redonne la mme rponse tant que l'application n'a pas t modifie et recharge. Il est utile pour d'autres types d'exceptions comme nous l'avons dj vu.

ligne 43 : cre une instance [DaoImpl] implmentant la couche [dao] ligne 44 : initialise cette instance (cration d'une liste initiale de trois personnes) ligne 46 : cre une instance [ServiceImpl] implmentant la couche [service] ligne 47 : initialise la couche [service] en lui donnant une rfrence sur la couche [dao]

Aprs l'initialisation du contrleur, les mthodes de celui-ci disposent d'une rfrence [service] sur la couche [service] (ligne 15) qu'elles vont utiliser pour excuter les actions demandes par l'utilisateur. Celles-ci vont tre interceptes par la mthode [doGet] qui va les faire traiter par une mthode particulire du contrleur : Url
/do/list /do/edit /do/validate /do/delete

Mthode HTTP mthode contrleur doListPersonnes GET GET POST GET doEditPersonne doValidatePersonne doDeletePersonne

La mthode [doGet] Cette mthode a pour but d'orienter le traitement des actions demandes par l'utilisateur vers la bonne mthode. Son code est le suivant :
1.// GET 2. @SuppressWarnings("unchecked") 3. public void doGet(HttpServletRequest request, HttpServletResponse response) 4. throws IOException, ServletException { 5. 6. // on vrifie comment s'est passe l'initialisation de la servlet 7. if (erreursInitialisation.size() != 0) { 8. // on passe la main la page d'erreurs 9. request.setAttribute("erreurs", erreursInitialisation); 10. getServletContext().getRequestDispatcher(urlErreurs).forward(request, response); 11. // fin 12. return; 13. } 14. // on rcupre la mthode d'envoi de la requte 15. String mthode = request.getMethod().toLowerCase(); 16. // on rcupre l'action excuter 17. String action = request.getPathInfo(); 18. // action ? 19. if (action == null) { 20. action = "/list"; 21. } 22. // excution action 23. if (mthode.equals("get") && action.equals("/list")) { 24. // liste des personnes 25. doListPersonnes(request, response); 26. return; 27. } 28. if (mthode.equals("get") && action.equals("/delete")) { 29. // suppression d'une personne 30. doDeletePersonne(request, response); Les bases du dveloppement web MVC en Java, par l'exemple

163/264

31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. }

return; } if (mthode.equals("get") && action.equals("/edit")) { // prsentation formulaire ajout / modification d'une personne doEditPersonne(request, response); return; } if (mthode.equals("post") && action.equals("/validate")) { // validation formulaire ajout / modification d'une personne doValidatePersonne(request, response); return; } // autres cas doListPersonnes(request, response);

lignes 7-13 : on vrifie que la liste des erreurs d'initialisation est vide. Si ce n'est pas le cas, on fait afficher la vue [erreurs(erreurs)] qui va signaler la ou les erreurs. ligne 15 : on rcupre la mthode [get] ou [post] que le client a utilise pour faire sa requte. ligne 17 : on rcupre la valeur du paramtre [action] de la requte. lignes 23-27 : traitement de la requte [GET /do/list] qui demande la liste des personnes. lignes 28-32 : traitement de la requte [GET /do/delete] qui demande la suppression d'une personne. lignes 33-37 : traitement de la requte [GET /do/edit] qui demande le formulaire de mise jour d'une personne. lignes 38-42 : traitement de la requte [POST /do/validate] qui demande la validation de la personne mise jour. ligne 44 : si l'action demande n'est pas l'une des cinq prcdentes, alors on fait comme si c'tait [GET /do/list].

La mthode [doListPersonnes] Cette mthode traite la requte [GET /do/list] qui demande la liste des personnes :

Son code est le suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. // affichage liste des personnes private void doListPersonnes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // le modle de la vue [list] request.setAttribute("personnes", service.getAll()); // affichage de la vue [list] getServletContext() .getRequestDispatcher((String) params.get("urlList")).forward(request, response); }

ligne 5 : on demande la couche [service] la liste des personnes du groupe et on met celle-ci dans le modle sous la cl " personnes ". ligne 7 : on fait afficher la vue [list.jsp] dcrite page 156.

La mthode [doDeletePersonne] Cette mthode traite la requte [GET /do/delete?id=XX] qui demande la suppression de la personne d'id=XX. L'url [/do/delete?id=XX] est celle des liens [Supprimer] de la vue [list.jsp] :

Les bases du dveloppement web MVC en Java, par l'exemple

164/264

dont le code est le suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. ... <html> <head> <title>MVC - personnes</title> </head> <body background="<c:url value="/ressources/standard.jpg"/>"> ... <c:forEach var="personne" items="${personnes}"> <tr> ... <td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html>

Ligne 12, on voit lurl [/do/delete?id=XX] du lien [Supprimer]. La mthode [doDeletePersonne] qui doit traiter cette url doit supprimer la personne did=XX puis faire afficher la nouvelle liste des personnes du groupe. Son code est le suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. // validation modification / ajout d'une personne private void doDeletePersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // on rcupre l'id de la personne int id = Integer.parseInt(request.getParameter("id")); // on supprime la personne service.deleteOne(id); // on redirige vers la liste des personnes response.sendRedirect("list"); }

ligne 5 : lurl traite est de la forme [/do/delete?id=XX]. On rcupre la valeur [XX] du paramtre [id]. ligne 7 : on demande la couche [service] la suppression de la personne ayant lid obtenu. Nous ne faisons aucune vrification. Si la personne quon cherche supprimer nexiste pas, la couche [dao] lance une exception que laisse remonter la couche [service]. Nous ne la grons pas non plus ici, dans le contrleur. Elle remontera donc jusquau serveur web qui par configuration fera afficher la page [exception.jsp], dcrite page 160 :

ligne 9 : si la suppression a eu lieu (pas dexception), on demande au client de se rediriger vers l'Url relative [list]. Comme celle qui vient d'tre traite est [/do/delete], l'Url de redirection sera [/do/list]. Le navigateur sera donc amen faire un [GET /do/list] qui provoquera l'affichage de la liste des personnes.

La mthode [doEditPersonne] Cette mthode traite la requte [GET /do/edit?id=XX] qui demande le formulaire de mise jour de la personne d'id=XX. L'url [/do/edit?id=XX] est celle des liens [Modifier] et celui du lien [Ajout] de la vue [list.jsp] :

dont le code est le suivant :


Les bases du dveloppement web MVC en Java, par l'exemple

165/264

1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.

... <html> <head> <title>MVC - personnes</title> </head> <body background="<c:url value="/ressources/standard.jpg"/>"> ... <c:forEach var="personne" items="${personnes}"> <tr> ... <td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td> <td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td> </tr> </c:forEach> </table> <br> <a href="<c:url value="/do/edit?id=-1"/>">Ajout</a> </body> </html>

Ligne 11, on voit lurl [/do/edit?id=XX] du lien [Modifier] et ligne 17, l'url [/do/edit?id=-1] du lien [Ajout]. La mthode [doEditPersonne] doit faire afficher le formulaire ddition de la personne did=XX ou s'il sagit dun ajout prsenter un formulaire vide.

Le code de la mthode [doEditPersonne] 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. 27. 28. 29. 30. 31. 32. 33. 34. // modification / ajout d'une personne private void doEditPersonne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // on rcupre l'id de la personne int id = Integer.parseInt(request.getParameter("id")); // ajout ou modification ? Personne personne = null; if (id != -1) { // modification - on rcupre la personne modifier personne = service.getOne(id); } else { // ajout - on cre une personne vide personne = new Personne(); personne.setId(-1); } // on met l'objet [Personne] dans le modle de la vue [edit] request.setAttribute("erreurEdit", ""); request.setAttribute("id", personne.getId()); request.setAttribute("version", personne.getVersion()); request.setAttribute("prenom", personne.getPrenom()); request.setAttribute("nom", personne.getNom()); Date dateNaissance = personne.getDateNaissance(); if (dateNaissance != null) { request.setAttribute("dateNaissance", new SimpleDateFormat( "dd/MM/yyyy").format(dateNaissance)); } else { request.setAttribute("dateNaissance", ""); } request.setAttribute("marie", personne.getMarie()); request.setAttribute("nbEnfants", personne.getNbEnfants()); // affichage de la vue [edit] getServletContext() .getRequestDispatcher((String) params.get("urlEdit")).forward(request, response); }

Les bases du dveloppement web MVC en Java, par l'exemple

166/264

le GET a pour cible une url du type [/do/edit?id=XX]. Ligne 5, nous rcuprons la valeur de [id]. Ensuite il y a deux cas : 1. id est diffrent de -1. Alors il sagit dune modification et il faut afficher un formulaire pr-rempli avec les informations de la personne modifier. Ligne 10, cette personne est demande la couche [service]. 2. id est gal -1. Alors il sagit dun ajout et il faut afficher un formulaire vide. Pour cela, une personne vide est cre lignes 13-14. l'objet [Personne] obtenu est plac dans le modle de la page [edit.jsp] dcrite page 158. Ce modle comprend les lments suivants [erreurEdit, id, version, prenom, erreurPrenom, nom, erreurNom, dateNaissance, erreurDateNaissance, marie, nbEnfants, erreurNbEnfants]. Ces lments sont initialiss lignes 17-30 l'exception de ceux dont la valeur est la chane vide [erreurPrenom, erreurNom, erreurDateNaissance, erreurNbEnfants]. On sait qu'en leur absence dans le modle, la bibliothque JSTL affichera une chane vide pour leur valeur. Bien que l'lment [erreurEdit] ait galement pour valeur une chane vide, il est nanmoins initialis car un test est fait sur sa valeur dans la page [edit.jsp]. une fois le modle prt, le contrle est pass la page [edit.jsp], lignes 32-33, qui va gnrer la vue [edit].

La mthode [doValidatePersonne] Cette mthode traite la requte [POST /do/validate] qui valide le formulaire de mise jour. Ce POST est dclench par le bouton [Valider] :

Rappelons les lments de saisie du formulaire HTML de la vue ci-dessus :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. <form method="post" action="<c:url value="/do/validate"/>"> .... <input type="text" value="${prenom}" name="prenom" size="20"> .... <input type="text" value="${nom}" name="nom" size="20"> .... <input type="text" value="${dateNaissance}" name="dateNaissance"> ... <input type="radio" name="marie" value="true" checked>Oui .... <input type="text" value="${nbEnfants}" name="nbEnfants"> .... <input type="hidden" value="${id}" name="id"> <input type="hidden" value="${version}" name="version"> <input type="submit" value="Valider"> </form>

La requte POST contient les paramtres [prenom, nom, dateNaissance, marie, nbEnfants, id, version] et est poste l'url [/do/validate] (ligne 1). Elle est traite par la mthode [doValidatePersonne] suivante :
1.// validation modification / ajout d'une personne 2. public void doValidatePersonne(HttpServletRequest request, 3. HttpServletResponse response) throws ServletException, IOException { 4. // on rcupre les lments posts 5. boolean formulaireErron = false; 6. boolean erreur; 7. // le prnom 8. String prenom = request.getParameter("prenom").trim(); Les bases du dveloppement web MVC en Java, par l'exemple

167/264

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

168/264

96. 97. 98. 99. 100. 101.

request.setAttribute("nbEnfants", request.getParameter("nbEnfants") .trim()); // affichage de la vue [edit] getServletContext() .getRequestDispatcher((String) params.get("urlEdit")).forward(request, response); }

lignes 8-14 : le paramtre [prenom] de la requte POST est rcupr et sa validit vrifie. S'il s'avre incorrect, l'lment [erreurPrenom] est initialis avec un message d'erreur et plac dans les attributs de la requte. lignes 16-22 : on opre de faon similaire pour le paramtre [nom] lignes 24-32 : on opre de faon similaire pour le paramtre [dateNaissance] ligne 34 : on rcupre le paramtre [marie]. On ne fait pas de vrification sur sa validit parce qu' priori il provient de la valeur d'un bouton radio. Ceci dit, rien n'empche un programme de faire un [POST /personnes-01/do/validate] accompagn d'un paramtre [marie] fantaisiste. Nous devrions donc tester la validit de ce paramtre. Ici, on se repose sur notre gestion des exceptions qui provoquent l'affichage de la page [exception.jsp] si le contrleur ne les gre pas lui-mme. Si donc, la conversion du paramtre [marie] en boolen choue ligne 34, une exception en sortira qui aboutira l'envoi de la page [exception.jsp] au client. Ce fonctionnement nous convient. lignes 34-54 : on rcupre le paramtre [nbEnfants] et on vrifie sa valeur. ligne 56 : on rcupre le paramtre [id] sans vrifier sa valeur ligne 58 : on fait de mme pour le paramtre [version] lignes 60-65 : si le formulaire est erron, il est raffich avec les messages d'erreurs construits prcdemment lignes 67-69 : s'il est valide, on construit un nouvel objet [Personne] avec les lments du formulaire lignes 70-78 : la personne est sauvegarde. La sauvegarde peut chouer. Dans un cadre multi-utilisateurs, la personne modifier a pu tre supprime ou bien dj modifie par quelquun dautre. Dans ce cas, la couche [dao] va lancer une exception quon gre ici. ligne 80 : sil ny a pas eu dexception, on redirige le client vers lurl [/do/list] pour lui prsenter le nouvel tat du groupe. ligne 75 : sil y a eu exception lors de la sauvegarde, on redemande le raffichage du formulaire initial en lui passant le message d'erreur de l'exception (3ime paramtre).

La mthode [showFormulaire] (lignes 84-101) construit le modle ncessaire la page [edit.jsp] avec les valeurs saisies (request.getParameter(" ... ")). On se rappelle que les messages d'erreurs ont dj t placs dans le modle par la mthode [doValidatePersonne]. La page [edit.jsp] est affiche lignes 99-100.

14.9

Les tests de lapplication web

Un certain nombre de tests ont t prsents au paragraphe 14.1, page 137. Nous invitons le lecteur les rejouer. Nous montrons ici dautres copies dcran qui illustrent les cas de conflits daccs aux donnes dans un cadre multi-utilisateurs : [Firefox] sera le navigateur de lutilisateur U1. Celui-ci demande lurl [http://localhost:8080/personnes-01] :

[IE] sera le navigateur de lutilisateur U2. Celui-ci demande la mme Url :

Les bases du dveloppement web MVC en Java, par l'exemple

169/264

Lutilisateur U1 entre en modification de la personne [Lemarchand] :

Lutilisateur U2 fait de mme :

Lutilisateur U1 fait des modifications et valide :

Les bases du dveloppement web MVC en Java, par l'exemple

170/264

rponse du serveur

validation Lutilisateur U2 fait de mme :

validation

le conflit de version a t dtect Lutilisateur U2 revient la liste des personnes avec le lien [Annuler] du formulaire :

Il trouve la personne [Lemarchand] telle que U1 la modifie. Maintenant U2 supprime [Lemarchand] :

Les bases du dveloppement web MVC en Java, par l'exemple

171/264

- rponse du serveur

- Suppression U1 a toujours sa propre liste et veut modifier [Lemarchand] de nouveau :

- modification

- rponse du serveur : il na pas trouv [Lemarchand]

U1 utilise le lien [Retour la liste] pour voir de quoi il retourne :

Il dcouvre queffectivement [Lemarchand] ne fait plus partie de la liste...

14.10

Conclusion

Nous avons mis en oeuvre l'architecture MVC dans une architecture 3tier [web, metier, dao] sur un exemple basique de gestion dune liste de personnes. Cela nous a permis dutiliser les concepts qui avaient t prsents dans les prcdentes sections. Dans la version tudie, la liste des personnes tait maintenue en mmoire. Nous tudierons prochainement des versions o cette liste sera maintenue dans une table de base de donnes. Mais auparavant, nous allons introduire un outil appel Spring IoC, qui facilite l'intgration des diffrentes couches d'une application ntier.

15 Spring IoC
15.1 Introduction

Nous nous proposons de dcouvrir les possibilits de configuration et d'intgration du framework Spring (http://www.springframework.org) ainsi que de dfinir et utiliser la notion d'IoC (Inversion of Control), galement appele injection de dpendance (Dependency Injection)
Les bases du dveloppement web MVC en Java, par l'exemple

172/264

Considrons l'application 3-tier que nous venons de construire :

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Pour rpondre aux demandes de l'utilisateur, le contleur [Application] doit s'adresser la couche [service]. Dans notre exemple, celle-ci tait une instance de type [DaoImpl]. le contleur [Application] a obtenu une rfrence sur la couche [service] dans sa mthode [init] (paragraphe 14.8.3, page 161) :
1. 2. 3. 4. 5. 6. 7. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. @SuppressWarnings("serial") public class Application extends HttpServlet { ... // service ServiceImpl service=null; ... // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(); dao.init(); // instanciation de la couche [service] service = new ServiceImpl(); service.setDao(dao); }

ligne 6 : la couche [dao] a t instancie par la cration explicite d'une instance [DaoImpl] ligne 9 : la couche [service] a t instancie par la cration explicite d'une instance [ServiceImpl]

Rappelons que les classes [DaoImpl] et [ServiceImpl] implmentent des interfaces, respectivement les interfaces [IDao] et [IService]. Dans des versions venir, l'interface [IDao] sera implmente par une classe grant une liste des personnes place en base de donnes. Appelons cette classe [DaoBD] pour l'exemple. Remplacer l'implmentation [DaoImpl] de la couche [dao) par l'implmentation [DaoBD] va ncessiter une recompilation de la couche [web]. En effet, la ligne 6 ci-dessus qui instancie la couche [dao] avec un type [DaoImpl] doit dsormais l'instancier avec un type [DaoBD]. Notre couche [web] est donc dpendante de la couche [dao]. La ligne 9 ci-dessus montre qu'elle est galement dpendante de la couche [service]. Spring IoC va nous permettre de crer une application 3tier o les couches sont indpendantes des autres, c.a.d. que changer l'une ne ncessite pas de changer les autres. Cela apporte une grande souplesse dans l'volution de l'application. L'architecture prcdente va voluer de la faon suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

173/264

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Spring IoC

Avec [Spring IoC], le contrleur [Application] va obtenir la rfrence dont il a besoin sur la couche [service] de la faon suivante : 1. 2. 3. dans sa mthode [init], il va demander la couche [Spring IoC] de lui donner une rfrence sur la couche [service] [Spring IoC] va alors exploiter un fichier XML de configuration qui lui indique quelle classe doit tre instancie et comment elle doit tre initialise. [Spring IoC] rend au contrleur [Application] la rfrence de la couche [service] cre.

L'avantage de cette solution est que dsormais le nom des classes instanciant les diffrentes couches n'est plus cod en dur dans la mthode [init] du contrleur mais simplement prsent dans un fichier de configuration. Changer l'implmentation d'une couche induira un changement dans ce fichier de configuration mais pas dans le contrleur. Prsentons maintenant les possibilits de [Spring IoC] l'aide d'exemples.

15.2
15.2.1

Spring IoC par la pratique


Spring

[Spring IoC] est une partie d'un projet plus large disponible l'url [http://www.springframework.org/] (mai 2006) :

Les bases du dveloppement web MVC en Java, par l'exemple

174/264

[1] : Spring utilise diverses technologies tierces appeles ici dpendances. Il faut tlcharger la version avce dpendances afin d'viter d'tre obligs ensuite de tlcharger les bibliothques des outils tiers. [2] : l'arborescence du fichier zipp tlcharg [3] : la distribution [Spring], c.a.d. les archives .jar du projet Spring lui-mme sans ses dpendances. L'aspect [IoC] de Spring est assur par les archives [spring-core.jar, spring-beans.jar]. [4,5] : les archives des outils tiers

15.2.2

Projets Eclipse des exemples

Nous allons construire trois exemples illustrant l'utilisation de Spring IoC. Ils seront tous dans le projet Eclipse suivant :

Le projet [springioc-exemples] est configur pour que les fichiers source et les classes compiles soient la racine du dossier du projet :

[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 bibliothques du projet [spring-core.jar, spring-beans.jar] seront trouvs dans le dossier [dist] de la distribution de Spring et [commons-logging.jar] dans le dossier [lib/jakarta-commons]. Ces trois archives ont t incluses dans le Classpath de l'application.

Les bases du dveloppement web MVC en Java, par l'exemple

175/264

15.2.3

Exemple 1

Les lments de l'exemple 1 ont t placs dans le paquetage [springioc01] du projet :

La classe [Personne] est la suivante :


1. package istia.st.springioc01; 2. 3. public class Personne { 4. 5. // caractristiques 6. private String nom; 7. private int age; 8. 9. // affichage Personne 10. public String toString() { 11. return "nom=[" + this.nom + "], age=[" + this.age + "]"; 12. } 13. 14. // init-close 15. public void init() { 16. System.out.println("init personne [" + this.toString() + "]"); 17. } 18. 19. public void close() { 20. System.out.println("destroy personne [" + this.toString() + "]"); 21. } 22. 23. // getters-setters 24. public int getAge() { 25. return age; 26. } 27. 28. public void setAge(int age) { 29. this.age = age; 30. } 31. 32. public String getNom() { 33. return nom; 34. } 35. 36. public void setNom(String nom) { 37. this.nom = nom; 38. } 39. 40. }

La classe prsente : - lignes 6-7 : deux champs privs nom et age - lignes 23-38 : les mthodes de lecture (get) et d'criture (set) de ces deux champs - lignes 10-12 : une mthode toString pour rcuprer la valeur de l'objet [Personne] sous la forme d'une chane de caractres - lignes 15-21 : une mthode init qui sera appele par Spring la cration de l'objet, une mthode close qui sera appele la destruction de l'objet Pour instancier des objets de type [Personne] l'aide de Spring, nous utiliserons le fichier [spring-config-01.xml] suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. <?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.springioc01.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Simon" /> <property name="age" value="40" /> </bean> <bean id="personne2" class="istia.st.springioc01.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Brigitte" /> <property name="age" value="20" /> </bean> </beans>

lignes 3, 12 : la balise <beans> est la balise racine des fichiers de configuration Spring. A l'intrieur de cette balise, la balise <bean> sert dfinir les diffrents objets crer. lignes 4-7 : dfinition d'un bean 176/264

Les bases du dveloppement web MVC en Java, par l'exemple

ligne 4 : le bean s'appelle [personne1] (attribut id) et est une instance de la classe [istia.st.springioc01.Personne] (attribut class). La mthode [init] de l'instance sera appele une fois celle-ci cre (attribut init-method) et la mthode [close] de l'instance sera appele avant sa destruction (attribut destroy-method). ligne 5 : dfinissent la valeur donner la proprit [nom] (attribut name) de l'instance [Personne] cre. Pour faire cette initialisation, Spring utilisera la mthode [setNom]. Il faut donc que cette mthode existe. C'est le cas ici. ligne 6 : idem pour la proprit [age]. lignes 8-11 : dfinition analogue d'un bean nomm [personne2]

La classe de test [Main] est 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. package istia.st.springioc01; 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-01.xml")); // rcupration du bean [personne1] Personne personne1 = (Personne) bf.getBean("personne1"); System.out.println("personne1=" + personne1.toString()); // rcupration du bean [personne2] Personne personne2 = (Personne) bf.getBean("personne2"); System.out.println("personne2=" + personne2.toString()); // rcupration du bean [personne2] une nouvelle fois personne2 = (Personne) bf.getBean("personne2"); System.out.println("personne2=" + personne2.toString()); // on supprime tous les beans bf.destroySingletons(); }

Commentaires : - ligne 10 : pour obtenir les beans dfinis dans le fichier [spring-config-01.xml], nous utilisons un objet de type [XmlBeanFactory] qui permet d'instancier les beans dfinis dans un fichier XML. Le fichier [spring-config01.xml] sera plac dans le [ClassPath] de l'application, c.a.d. dans l'un des rpertoires explors par la machine virtuelle Java lorsqu'elle cherche une classe rfrence par l'application. L'objet [ClassPathResource] sert rechercher une ressource dans le [ClassPath] d'une application, ici le fichier [spring-config-01.xml]. L'objet [bf] obtenu (Bean Factory) permet d'obtenir la rfrence d'un bean nomm "XX" par l'instruction bf.getBean("XX"). - ligne 12 : on demande une rfrence sur le bean nomm [personne1] dans le fichier [spring-config-01.xml]. - ligne 13 : on affiche la valeur de l'objet [Personne] correspondant. - lignes 15-16 : on fait de mme pour le bean nomm [personne2]. - lignes 18-19 : on redemande le bean nomm [personne2]. - ligne 21 : on supprime tous les beans de [bf] c.a.d. ceux crs partir du fichier [spring-config-01.xml]. L'excution de la classe [Main] donne les rsultats suivants :
1. 2. 3. 4. 5. 6. 7. init personne [nom=[Simon], age=[40]] personne1=nom=[Simon], age=[40] init personne [nom=[Brigitte], age=[20]] personne2=nom=[Brigitte], age=[20] personne2=nom=[Brigitte], age=[20] destroy personne [nom=[Simon], age=[40]] destroy personne [nom=[Brigitte], age=[20]]

Commentaires : - la ligne 1 a t obtenue par l'excution de la ligne 12 de [Main]. L'opration


Personne personne1 = (Personne) bf.getBean("personne1");

a forc la cration du bean [personne1]. Parce que dans la dfinition du bean [personne1] on avait crit [initmethod="init"], la mthode [init] de l'objet [Personne] cr a t excute. Le message correspondant est affich. - ligne 2 : la ligne 13 de [Main] a fait afficher la valeur de l'objet [Personne] cr. - lignes 3-4 : le mme phnomne se rpte pour le bean nomm [personne2]. - ligne 5 : l'opration des lignes 18-19 de [Main]
personne2 = (Personne) bf.getBean("personne2"); System.out.println("personne2=" + personne2.toString());

Les bases du dveloppement web MVC en Java, par l'exemple

177/264

n'a pas provoqu la cration d'un nouvel objet de type [Personne]. Si cela avait t le cas, on aurait eu l'affichage de la mthode [init]. C'est le principe du singleton. Spring, par dfaut, ne cre qu'un seul exemplaire des beans de son fichier de configuration. C'est un service de rfrences d'objet. Si on lui demande la rfrence d'un objet non encore cr, il le cre et en rend une rfrence. Si l'objet a dj t cr, Spring se contente d'en donner une rfrence. Ici [personne2] ayant dj t cr, Spring se contente d'en rendre une rfrence. les affichages des lignes 6-7 ont t provoqus par la ligne 21 de [Main] qui demande la destruction de tous les beans rfrencs par l'objet [XmlBeanFactory bf], donc les beans [personne1, personne2]. Parce que ces deux beans ont l'attribut [destroy-method="close"], la mthode [close] des deux beans est excute et provoque l'affichage des lignes 6-7.

Les bases d'une configuration Spring tant maintenant acquises, nous serons dsormais un peu plus rapides dans nos explications.

15.2.4

Exemple 2

Les lments de l'exemple 2 sont placs 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. La classe [Voiture] est la suivante :
1. package istia.st.springioc02; 2. 3. public class Voiture { 4. // caractristiques 5. private String marque; 6. private String type; 7. private Personne propritaire; 8. 9. // constructeurs 10. public Voiture() { 11. } 12. 13. public Voiture(String marque, String type, Personne propritaire) { 14. setMarque(marque); 15. setType(type); 16. setPropritaire(propritaire); 17. } 18. 19. // toString 20. public String toString() { 21. return "Voiture : marque=[" + this.marque + "] type=[" + this.type 22. + "] propritaire=[" + this.propritaire + "]"; 23. } 24. 25. // getters-setters 26. public String getMarque() { 27. return marque; 28. } 29. 30. public void setMarque(String marque) { 31. this.marque = marque; 32. } 33. 34. public Personne getPropritaire() { 35. return propritaire; 36. } 37. 38. public void setPropritaire(Personne propritaire) { 39. this.propritaire = propritaire; 40. } 41. 42. public String getType() { 43. return type; 44. } 45. 46. public void setType(String type) { 47. this.type = type; 48. } 49. Les bases du dveloppement web MVC en Java, par l'exemple

178/264

50. // init-close 51. public void init() { 52. System.out.println("init voiture [" + this.toString() + "]"); 53. } 54. 55. public void close() { 56. System.out.println("destroy voiture [" + this.toString() + "]"); 57. } 58. }

La classe prsente : - lignes 5-7 : trois champs privs type, marque et propritaire. Ces champs peuvent tre initialiss et lus par des mthodes publiques de beans get et set des lignes 26-48. Ils peuvent tre galement initialiss l'aide du constructeur Voiture(String, String, Personne) dfini lignes 13-17. La classe possde galement un constructeur sans arguments afin de suivre la norme JavaBean. - lignes 20-23 : une mthode toString pour rcuprer la valeur de l'objet [Voiture] sous la forme d'une chane de caractres - lignes 51-57 : une mthode init qui sera appele par Spring juste aprs la cration de l'objet, une mthode close qui sera appele la destruction de l'objet Pour crer des objets de type [Voiture], nous utiliserons le fichier Spring [spring-config-02.xml] suivant :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. <?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.springioc02.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Simon" /> <property name="age" value="40" /> </bean> <bean id="personne2" class="istia.st.springioc02.Personne" init-method="init" destroy-method="close"> <property name="nom" value="Brigitte" /> <property name="age" value="20" /> </bean> <bean id="voiture1" class="istia.st.springioc02.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 dfinis dans [spring-config-01.xml] un bean de cl "voiture1" de type [Voiture] (lignes 12-17). Pour initialiser ce bean, on aurait pu crire :
1. <bean id="voiture1" class="istia.st.springioc.domain.Voiture" init-method="init" destroy-method="close"> 2. <property name="marque" value="Peugeot"/> 3. <property name="type" value="307"/> 4. <property name="propritaire"> 5. <ref local="personne2"/> 6. </property> 7.</bean>

Plutt que de choisir cette mthode dj prsente dans l'exemple 1, nous avons choisi d'utiliser ici le constructeur Voiture(String, String, Personne) de la classe.

ligne 12 : dfinition du nom du bean, de sa classe, de la mthode excuter aprs son instanciation, de la mthode excuter aprs sa suppression. ligne 13 : valeur du 1er paramtre du constructeur [Voiture(String, String, Personne)]. ligne 14 : valeur du 2ime paramtre du constructeur [Voiture(String, String, Personne)]. lignes 15-17 : valeur du 3ime paramtre du constructeur [Voiture(String, String, Personne)]. Ce paramtre est de type [Personne]. On lui fournit comme valeur, la rfrence (balise ref) du bean [personne2] dfini dans le mme fichier (attribut local).

Pour nos tests, nous utiliserons la classe [Main] suivante :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. package istia.st.springioc02; 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-02.xml")); // rcupration du bean [voiture1] Voiture Voiture1 = (Voiture) bf.getBean("voiture1"); System.out.println("Voiture1=" + Voiture1.toString());

Les bases du dveloppement web MVC en Java, par l'exemple

179/264

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

La mthode [main] demande la rfrence du bean [voiture1] (ligne 12) et l'affiche (ligne 13). Les rsultats sont les suivants :
1. 2. 3. 4. 5. init personne [nom=[Brigitte], age=[20]] init voiture [Voiture : marque=[Peugeot] type=[307] propritaire=[nom=[Brigitte], age=[20]]] Voiture1=Voiture : marque=[Peugeot] type=[307] propritaire=[nom=[Brigitte], age=[20]] destroy voiture [Voiture : marque=[Peugeot] type=[307] propritaire=[nom=[Brigitte], age=[20]]] destroy personne [nom=[Brigitte], age=[20]]

Commentaires : 1. la mthode [main] demande une rfrence sur le bean [voiture1] (ligne 12). Spring commence la cration du bean [voiture1] car ce bean n'a pas encore t cr (singleton). Parce que le bean [voiture1] rfrence le bean [personne2], ce dernier bean est construit son tour. Le bean [personne2] a t cr. Sa mthode [init] est alors excute (ligne 1) des rsultats. Le bean [voiture1] est ensuite instanci. Sa mthode [init] est alors excute (ligne 2) des rsultats. 2. la ligne 3 des rsultats provient de la ligne 13 de [main] : la valeur du bean [voiture1] est affiche. 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 rsultats.

15.2.5

Exemple 3

Les lments de l'exemple 3 sont placs 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. // caractristiques 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 dveloppement 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 privs 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 dfinit pas de constructeur sans argument. On rappelle qu'en l'absence de tout constructeur, il existe un constructeur "par dfaut" 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 possdant 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 implmentant l'interface List avec diffrentes valeurs. lignes 20-23 : la balise <map> permet de faire la mme chose avec un champ implmentant 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")); // rcupration 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 rfrence sur le bean [groupe1] et on affiche la valeur de celui-ci.

Les rsultats 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 dveloppement web MVC en Java, par l'exemple

181/264

7.

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

Commentaires :

ligne 12 de [Main], on demande une rfrence du bean [groupe1]. Spring commence la cration de ce bean. Parce que le bean [groupe1] rfrence les beans [personne1] et [personne2], ces deux beans sont crs (lignes 1 et 2) des rsultats. Le bean [groupe1] est ensuite instanci et sa mthode [init] excute (ligne 3 des rsultats). la ligne 13 de [Main] fait afficher la ligne 4 des rsultats. la ligne 15 de [Main] fait afficher les lignes 5-7 des rsultats.

15.3

Configuration d'une application n tier avec Spring

Considrons une application 3-tier ayant la structure suivante :

utilisateur

Couche interface utilisateur [ui]

Couche mtier [metier]

Couche d'accs aux donnes [dao]

Donnes

Nous nous proposons de montrer ici l'intrt de Spring pour construire une telle architecture. les trois couches seront rendues indpendantes grce l'utilisation d'interfaces Java l'intgration des trois couches sera ralise 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 implmentations de cette interface [2] : la couche [metier] : [IMetier] : l'interface de la couche [Metier1, Metier2] : deux implmentations de cette interface [3] : la couche [ui] : [IUi] : l'interface de la couche [Ui1, Ui2] : deux implmentations de cette interface [4] : les fichiers de configuration Spring de l'application. Nous configurerons l'application de deux faons. [5] : les bibliothques ncessaires l'application. Ce sont celles utilises dans les exemples prcdents. [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 dveloppement web MVC en Java, par l'exemple

Le but de cet exemple est de montrer que nous pouvons changer l'implmentation d'une ou plusieurs couches de l'application avec un impact zro sur les autres couches. Tout se passe dans le fichier de configuration de Spring. La couche [dao] La couche [dao] implmente 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'implmentation [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'implmentation [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 [mtier] La couche [mtier] implmente 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'implmentation [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'implmentation [Metier2] sera la suivante :


1. 2. 3. 4. package istia.st.springioc.troistier.metier; import istia.st.springioc.troistier.dao.IDao;

Les bases du dveloppement 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] implmente 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'implmentation [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'implmentation [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 dveloppement web MVC en Java, par l'exemple

184/264

24. }

Les fichiers de configuration Spring Le premier [spring-config-01.xml] :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- la classe dao --> <bean id="dao" class="istia.st.springioc.troistier.dao.Dao1"/> <!-- la classe mtier --> <bean id="metier" class="istia.st.springioc.troistier.metier.Metier1"> <property name="dao"> <ref local="dao" /> </property> </bean> <!-- la classe UI --> <bean id="ui" class="istia.st.springioc.troistier.ui.Ui1"> <property name="metier"> <ref local="metier" /> </property> </bean> </beans>

Le second [spring-config-02.xml] :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- la classe dao --> <bean id="dao" class="istia.st.springioc.troistier.dao.Dao2"/> <!-- la classe mtier --> <bean id="metier" class="istia.st.springioc.troistier.metier.Metier2"> <property name="dao"> <ref local="dao" /> </property> </bean> <!-- la classe UI --> <bean id="ui" class="istia.st.springioc.troistier.ui.Ui2"> <property name="metier"> <ref local="metier" /> </property> </bean> </beans>

Les programmes de test Le programme [Main1] est le suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. package istia.st.springioc.troistier.main; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import istia.st.springioc.troistier.ui.IUi; public class Main1 { public static void main(String[] args) { // on rcupre une implmentation de l'interface IUi IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-01.xml"))).getBean("ui"); // on utilise la classe int a = 10, b = 20; int res = ui.doSomethingInUiLayer(a, b); // on affiche le rsultat System.out.println("ui(" + a + "," + b + ")=" + res); } }

Le programme [Main1] utilise le fichier de configuration [spring-config-01.xml] et donc les implmentations [Ui1, Metier1, Dao1] des couches. Les rsultats obtenus sur la console Eclipse :
ui(10,20)=34

Le programme [Main2] est le suivant :


1. 2. 3. 4. 5. 6. 7. 8. 9. package istia.st.springioc.troistier.main; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; import istia.st.springioc.troistier.ui.IUi; public class Main2 {

Les bases du dveloppement web MVC en Java, par l'exemple

185/264

10. public static void main(String[] args) { 11. // on rcupre une implmentation de l'interface IUi 12. IUi ui = (IUi) (new XmlBeanFactory(new ClassPathResource("spring-config-02.xml"))).getBean("ui"); 13. // on utilise la classe 14. int a = 10, b = 20; 15. int res = ui.doSomethingInUiLayer(a, b); 16. // on affiche le rsultat 17. System.out.println("ui(" + a + "," + b + ")=" + res); 18. } 19. 20. }

Le programme [Main2] utilise le fichier de configuration [spring-config-02.xml] et donc les implmentations [Ui2, Metier2, Dao2] des couches. Les rsultats obtenus sur la console Eclipse :
ui(10,20)=-10

15.4

Conclusion

L'application que nous avons construite possde une grande souplesse d'volution. On y change l'implmentation d'une couche par simple configuration. Le code des autres couches reste inchang. Ceci est obtenu grce au concept IoC qui est l'un des deux piliers de Spring. L'autre pilier est AOP (Aspect Oriented Programming) que nous n'avons pas prsent. Il permet d'ajouter, galement par configuration, du "comportement" une mthode de classe sans modifier le code de celle-ci.

16 Application web MVC dans une architecture 3tier Exemple 2


16.1 Introduction

Nous avons crit l'application [personnes-01] ayant la structure suivante :

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

La couche [dao] implmentait la liste des personnes gre l'aide d'un objet [ArrayList]. Cela nous a permis de ne pas nous apesantir sur les couches [dao] et [service] pour nous concentrer sur la couche [web]. Nous souhaitons faire voluer l'application vers un environnement plus raliste o la liste des personnes serait mmorise dans une table de bases de donnes. Cela va nous amener changer la couche [dao]. Cela va impacter les deux autres couches. Pour profiter de l'indpendance des couches amene par Spring IoC, nous allons reprendre l'application [personnes-01] et la configurer avec Spring IoC :

Les bases du dveloppement web MVC en Java, par l'exemple

186/264

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Spring IoC
La nouvelle application s'appellera [personnes-02]. Une fois qu'elle aura t crite, nous savons que nous pourrons changer les couches [dao] et [service] sans changement du code de la couche [web]. C'est cela que nous recherchons. Nous crons un nouveau projet Eclipse [personnes-02] par copier / coller du projet [personnes-01] comme il a t expliqu au paragraphe 6.2, page 78 :

En [1], nous voyons apparatre le fichier de configuration dans lequel nous allons dfinir les beans des couches [dao] et [service]. Son contenu est le suivant :
1. 2. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/springbeans.dtd"> 3. <beans> 4. <!-- la classe dao --> 5. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImpl" init-method="init"/> 6. <!-- la classe service --> 7. <bean id="service" class="istia.st.mvc.personnes.service.ServiceImpl"> 8. <property name="dao"> 9. <ref local="dao" /> 10. </property> 11. </bean> 12. </beans>

ligne 5 : dfinit le bean nomm [dao] comme une instance de la classe [DaoImpl]. Aprs instanciation, la mthode [init] de l'instance est excute. lignes 7-10 : dfinissent le bean nomm [service] comme une instance de la classe [ServiceImpl]. lignes 8-10 : la proprit [dao] de l'instance [DaoImpl] est initialise avec la rfrence de la couche [dao] cre ligne 5. Rappelons que la classe [ServiceImpl] a bien la proprit [dao] et le setter qui va avec :
1. 2. 3. 4. 5. 6. 7. 8. 9. public class ServiceImpl implements IService { // la couche [dao] private IDao dao; public IDao getDao() { return dao; }

Les bases du dveloppement web MVC en Java, par l'exemple

187/264

10. public void setDao(IDao dao) { 11. this.dao = dao; 12. } 13. ...

Seul ce fichier ne fait rien bien sr. Le contrleur [Application] va l'utiliser dans sa mthode [init] pour instancier la couche [service]. Rappelons la version prcdente de la mthode [init] du contrleur :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. @SuppressWarnings("serial") public class Application extends HttpServlet { ... // service ServiceImpl service = null; // init @SuppressWarnings("unchecked") public void init() throws ServletException { ... // instanciation de la couche [dao] DaoImpl dao = new DaoImpl(); dao.init(); // instanciation de la couche [service] service = new ServiceImpl(); service.setDao(dao); }

En ligne 5, nous avions t obligs de nommer explicitement la classe d'implmentation de la couche [service] et en ligne 12, celle de la couche [dao]. Avec Spring IoC, la mthode [init] devient la suivante :
1. @SuppressWarnings("serial") 2. public class Application extends HttpServlet { 3. // paramtres d'instance 4. ... 5. 6. // service 7. private IService service = null; 8. 9. // init 10. @SuppressWarnings("unchecked") 11. public void init() throws ServletException { 12. ... 13. // instanciation de la couche [service] 14. service = (IService) new XmlBeanFactory(new ClassPathResource("spring-config.xml")).getBean("service"); 15. }

ligne 7 : le champ priv [service] n'est plus de type [ServiceImpl] mais de type [IService], c.a.d. du type de l'interface de la couche [service]. La couche [web] n'est donc plus lie une implmentation particulire de cette interface. ligne 14 : initialisation du champ [service] partir du fichier de configuration [spring-config.xml].

Ce sont les seules modifications faire. Intgrons cette nouvelle application dans Tomcat, lanons celui-ci puis demandons l'Url [http://localhost:8080/personnes-02] :

16.2

Mise en archives de lapplication web

Nous avons dvelopp un projet Eclipse / Tomcat dune application 3tier :

Les bases du dveloppement web MVC en Java, par l'exemple

188/264

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Spring IoC

Dans une version venir, le groupe de personnes va tre place dans une table de base de donnes.

Cela va induire une rcriture de la couche [dao]. Cela, on peut aisment le comprendre. La couche [service] va tre galement modifie. Actuellement, son unique rle est dassurer un accs synchronis aux donnes gres par la couche [dao]. Dans ce but, nous avons synchronis toutes les mthodes de la couche [service]. Nous avons expliqu pourquoi cette synchronisation tait place dans cette couche plutt que dans la couche [dao]. Dans la nouvelle version, la couche [service] aura encore lunique rle de synchronisation des accs mais celle-ci sera assure par des transactions de base de donnes plutt que par une synchronisation de mthodes Java. La couche [web] va elle rester inchange.

Afin de faciliter le passage dune version lautre, nous crons un nouveau projet Eclipse [mvc-personnes-02B], copie du projet prcdent [mvc-personnes-02] mais o les couches [web, service, dao, entites] ont t mises dans des archives .jar :

Le dossier [src] ne contient dsormais que le fichier de configuration Spring [spring-config.xml]. Il contenait auparavant galement le code source des classes Java. Ces lments ont disparu, remplacs par leurs lments compils mis dans les archives [personnes-*.jar] montrs en [1] :

Les bases du dveloppement web MVC en Java, par l'exemple

189/264

- couche [entites] - couche [dao]

- couche [service]

- couche [web]

Le projet [mvc-personnes-02B] a t configur pour inclure les archives [personnes-*.jar ] dans son ClassPath. Nous dployons le projet web [mvc-personnes-02B] au sein de Tomcat :

Pour tester le projet, nous lanons Tomcat puis demandons lurl [http://localhost:8080/personnes02B] :

Le lecteur est invit faire des tests complmentaires. Dans la version avec base de donnes, nous allons changer les couches [service] et [dao]. Nous voulons montrer quil suffira alors, de remplacer dans le projet prcdent les archives [personnes-dao.jar] et [personnes-service.jar] par les nouvelles archives pour que notre application fonctionne dsormais avec une base de donnes. Nous naurons pas toucher aux archives de la couche [web] et de la couche [entites].

17 Application web MVC dans une architecture 3tier Exemple 3 Sgbd Firebird
17.1 La base de donnes Firebird

Dans cette nouvelle version, nous allons installer la liste des personnes dans une table de base de donnes Firebird. On trouvera dans le document [http://tahe.developpez.com/divers/sql-firebird/] des informations pour installer et grer ce SGBD. Dans ce qui suit, les copies dcran proviennent d IBExpert, un client dadministration des SGBD Interbase et Firebird. La base de donnes sappelle [dbpersonnes.gdb]. Elle contient une table [PERSONNES] :

Les bases du dveloppement web MVC en Java, par l'exemple

190/264

La table [PERSONNES] contiendra la liste des personnes gre par lapplication web. Elle a t construite avec les ordres SQL suivants :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. CREATE TABLE PERSONNES ( ID INTEGER NOT NULL, "VERSION" INTEGER NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, MARIE SMALLINT NOT NULL, NBENFANTS SMALLINT NOT NULL ); 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<>''); CHK_MARIE_PERSONNES check (MARIE=0 OR MARIE=1); CHK_NOM_PERSONNES check (NOM<>''); CHK_ENFANTS_PERSONNES check (NBENFANTS>=0);

ALTER TABLE PERSONNES ADD CONSTRAINT PK_PERSONNES PRIMARY KEY (ID);

lignes 2-10 : la structure de la table [PERSONNES], destine sauvegarder des objets de type [Personne], reflte la structure de cet objet. Le type boolen nexistant pas dans Firebird, le champ [MARIE] (ligne 8) a t dclar de type [SMALLINT], un entier. Sa valeur sera 0 (pas mari) ou 1 (mari). lignes 13-16 : des contraintes dintgrit qui refltent celles du validateur de donnes [ValidatePersonne]. ligne 19 : le champ ID est cl primaire de la table [PERSONNES]

La table [PERSONNES] pourrait avoir le contenu suivant :

La base [dbpersonnes.gdb] a, outre la table [PERSONNES], un objet appel gnrateur et nomm [GEN_PERSONNES_ID]. Ce gnrateur dlivre des nombres entiers successifs que nous utiliserons pour donner sa valeur, la cl primaire [ID] de la classe [PERSONNES]. Prenons un exemple pour illustrer son fonctionnement :

- le gnrateur a actuellement la valeur 96

- double-cliquer sur [GEN_PERSONNES_ID]


Les bases du dveloppement web MVC en Java, par l'exemple

191/264

- mettons lordre SQL ci-dessus (F12) -> - la valeur obtenue est lancienne valeur du gnrateur +1 On peut constater que la valeur du gnrateur [GEN_PERSONNES_ID] a chang (double-clic dessus + F5 pour rafrachir) :

Lordre SQL
SELECT GEN_ID ( GEN_PERSONNES_ID,1 ) FROM RDB$DATABASE

permet donc davoir la valeur suivante du gnrateur [GEN_PERSONNES_ID]. GEN_ID est une fonction interne de Firebird et [RDB$DATABASE], une table systme de ce SGBD.

17.2

Le projet Eclipse des couches [dao] et [service]

Pour dvelopper les couches [dao] et [service] de notre application avec base de donnes, nous utiliserons le projet Eclipse [mvc-personnes-03] suivant :

Le projet est un simple projet Java, pas un projet web Tomcat. Rappelons que la version 2 de notre application va utiliser la couche [web] de la version 1. Cette couche na donc pas tre crite. Dossier [src] Ce dossier contient les codes source des couches [dao] et [service] :

On y trouve diffrents paquetages :


Les bases du dveloppement web MVC en Java, par l'exemple

192/264

[istia.st.mvc.personnes.dao] : contient la couche [dao] [istia.st.mvc.personnes.entites] : contient la classe [Personne] [istia.st.mvc.personnes.service] : contient la classe [service] [istia.st.mvc.personnes.tests] : contient les tests JUnit des couches [dao] et [service]

ainsi que des fichiers de configuration qui doivent tre dans le ClassPath de lapplication. Dossier [database] Ce dossier contient la base de donnes Firebird des personnes :


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.

[dbpersonnes.gdb] est la base de donnes. [dbpersonnes.sql] est le script SQL de gnration de la base :
/******************************************************************************/ /*** Generated by IBExpert 2006.03.07 27/04/2006 10:27:11 ***/ /******************************************************************************/ SET SQL DIALECT 3; SET NAMES NONE; CREATE DATABASE 'C:\data\2005-2006\webjava\dvp-spring-mvc\mvc-38\database\DBPERSONNES.GDB' USER 'SYSDBA' PASSWORD 'masterkey' PAGE_SIZE 16384 DEFAULT CHARACTER SET NONE;

/******************************************************************************/ /*** Generators ***/ /******************************************************************************/ CREATE GENERATOR GEN_PERSONNES_ID; SET GENERATOR GEN_PERSONNES_ID TO 787;

/******************************************************************************/ /*** Tables ***/ /******************************************************************************/

CREATE TABLE PERSONNES ( ID INTEGER NOT NULL, "VERSION" INTEGER NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(30) NOT NULL, DATENAISSANCE DATE NOT NULL, MARIE SMALLINT NOT NULL, NBENFANTS SMALLINT NOT NULL );

INSERT INTO PERSONNES (ID, "VERSION", NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) VALUES (1, 1, 'Major', 'Joachim', '1984-11-13', 1, 2); 42. INSERT INTO PERSONNES (ID, "VERSION", NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) VALUES (2, 1, 'Humbort', 'Mlanie', '1985-02-12', 0, 1); 43. INSERT INTO PERSONNES (ID, "VERSION", NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) VALUES (3, 1, 'Lemarchand', 'Charles', '1986-03-01', 0, 0); 44. 45. COMMIT WORK; 46. 47. 48. 49. /* Check constraints definition */ 50. 51. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_PRENOM_PERSONNES check (PRENOM<>''); 52. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_NOM_PERSONNES check (NOM<>''); 53. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_MARIE_PERSONNES check (MARIE=0 OR MARIE=1); 54. ALTER TABLE PERSONNES ADD CONSTRAINT CHK_ENFANTS_PERSONNES check (NBENFANTS>=0); 55. 56. 57. /******************************************************************************/ 58. /*** Primary Keys ***/ 59. /******************************************************************************/ 60. Les bases du dveloppement web MVC en Java, par l'exemple

193/264

61. ALTER TABLE PERSONNES ADD CONSTRAINT PK_PERSONNES PRIMARY KEY (ID);

Dossier [lib] Ce dossier contient les archives ncessaires lapplication :

On notera la prsence du pilote JDBC [firebirdsql-full.jar] du SGBD Firebird ainsi que d'un certain nombre d'archives [spring*.jar]. 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. On peut aussi n'utiliser que les seules archives ncessaires au projet. C'est ce que nous avons fait ici en nous laissant guider par les erreurs de classes absentes signales par Eclipse et les noms des archives partielles de Spring. Toutes ces archives du dossier [lib] ont t places dans le Classpath du projet. Dossier [dist] Ce dossier contiendra les archives issues de la compilation des classes de lapplication :

[personnes-dao.jar] : archive de la couche [dao] [personnes-service.jar] : archive de la couche [service]

17.3
17.3.1

La couche [dao]
Les composantes de la couche [dao]

La couche [dao] est constitue des classes et interfaces suivantes :

[IDao] est linterface prsente par la couche [dao] [DaoImplCommon] est une implmentation de celle-ci o le groupe de personnes se trouve dans une table de base de donnes. [DaoImplCommon] regroupe des fonctionnalits indpendantes du SGBD. [DaoImplFirebird] est une classe drive de [DaoImplCommon] pour grer spcifiquement une base Firebird. [DaoException] est le type des exceptions non contrles, lances par la couche [dao]. Cette classe est celle de la version 1.

Linterface [IDao] est la suivante :


1. package istia.st.mvc.personnes.dao;

Les bases du dveloppement web MVC en Java, par l'exemple

194/264

2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.

import istia.st.mvc.personnes.entites.Personne; import java.util.Collection; public interface IDao { // liste de toutes les personnes Collection getAll(); // obtenir une personne particulire Personne getOne(int id); // ajouter/modifier une personne void saveOne(Personne personne); // supprimer une personne void deleteOne(int id); }

linterface a les mmes quatre mthodes que dans la version prcdente.

La classe [DaoImplCommon] implmentant cette interface sera la suivante :


1. 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. 49. 50. 51. 52. 53. 54. package istia.st.mvc.personnes.dao; import istia.st.mvc.personnes.entites.Personne; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; import java.util.Collection; public class DaoImplCommon extends SqlMapClientDaoSupport implements IDao { // liste des personnes public Collection getAll() { ... } // obtenir une personne en particulier public Personne getOne(int id) { ... } // suppression d'une personne public void deleteOne(int id) { ... } // ajouter ou modifier une personne public void saveOne(Personne personne) { // le paramtre personne est-il valide ? check(personne); // ajout ou modification ? if (personne.getId() == -1) { // ajout insertPersonne(personne); } else { updatePersonne(personne); } } // ajouter une personne protected void insertPersonne(Personne personne) { ... } // modifier une personne protected void updatePersonne(Personne personne) { ... } // vrification validit d'une personne private void check(Personne p) { ... } ... }

lignes 8-9 : la classe [DaoImpl] implmente linterface [IDao] et donc les quatre mthodes [getAll, getOne, saveOne, deleteOne]. lignes 27-37 : la mthode [saveOne] utilise deux mthodes internes [insertPersonne] et [updatePersonne] selon qu'on doit faire un ajout ou une modification de personne. ligne 50 : la mthode prive [check] est celle de la version prcdente. Nous ne reviendrons pas dessus. 195/264

Les bases du dveloppement web MVC en Java, par l'exemple

ligne 8 : pour implmenter [SqlMapClientDaoSupport].

linterface

[IDao],

la

classe

[DaoImpl]

drive

de

la

classe

Spring

17.3.2

La couche daccs aux donnes [iBATIS]

La classe Spring [SqlMapClientDaoSupport] utilise un framework tierce [Ibatis SqlMap] disponible lurl [http://ibatis.apache.org/] :

[iBATIS] est un projet Apache qui facilite la construction de couches [dao] sappuyant sur des bases de donnes. Avec [iBATIS], l'architecture de la couche d'accs aux donnes est la suivante : utilisateur

Couche d'accs aux donnes [dao]

Couche d'accs aux donnes [iBATIS]

Couche d'accs aux donnes [JDBC]

Donnes

[iBATIS] s'insre entre la couche [dao] de l'application et le pilote JDBC de la base de donnes. Il existe des alternatives [iBATIS] telle, par exemple, l'alternative [Hibernate] :

Les bases du dveloppement web MVC en Java, par l'exemple

196/264

utilisateur

Couche d'accs aux donnes [dao]

Couche d'accs aux donnes [Hibernate]

Couche d'accs aux donnes [JDBC]

Donnes

Lutilisation du framework [iBATIS] ncessite deux archives [ibatis-common, ibatis-sqlmap] qui ont t toutes deux places dans le dossier [lib] du projet :

La classe [SqlMapClientDaoSupport] encapsule la partie gnrique de lutilisation du framework [iBATIS], c.a.d. des parties de code quon retrouve dans toutes les couche [dao] utilisant l'outil [iBATIS]. Pour crire la partie non gnrique du code, cest dire ce qui est spcifique la couche [dao] que lon crit, il suffit de driver la classe [SqlMapClientDaoSupport]. Cest ce que nous faisons ici. La classe [SqlMapClientDaoSupport] est dfinie comme suit :

Parmi les mthodes de cette classe, lune delles permet de configurer le client [iBATIS] avec lequel on va exploiter la base de donnes :

Lobjet [SqlMapClient sqlMapClient] est lobjet [IBATIS] utilis pour accder une base de donnes. A lui tout seul, il implmente la couche [iBATIS] de notre architecture :

utilisateur

Couche d'accs aux donnes [SqlMapDaoClientSupport : DaoImplCommon]

Couche d'accs aux donnes [SqlMapClient]

Couche d'accs aux donnes [JDBC]

Donnes

Une squence typique dactions avec cet objet est la suivante : 1. demander une connexion un pool de connexions 197/264

Les bases du dveloppement web MVC en Java, par l'exemple

2. 3. 4. 5.

ouvrir une transaction excuter une srie dordres SQL mmorise dans un fichier de configuration fermer la transaction rendre la connexion au pool

Si notre implmentation [DaoImplCommon] travaillait directement avec [iBATIS], elle devrait faire cette squence de faon rpte. Seule lopration 3 est spcifique une couche [dao], les autres oprations tant gnriques. La classe Spring [SqlMapClientDaoSupport] assurera elle-mme les oprations 1, 2, 4 et 5, dlguant lopration 3 sa classe drive, ici la classe [DaoImplCommon]. Pour pouvoir fonctionner, la classe [SqlMapClientDaoSupport] a besoin dune rfrence sur lobjet iBATIS [SqlMapClient sqlMapClient] qui va assurer le dialogue avec la base de donnes. Cet objet a besoin de deux choses pour fonctionner :

un objet [DataSource] connect la base de donnes auprs duquel il va demander des connexions un (ou des) fichier de configuration o sont externaliss les ordres SQL excuter. En effet, ceux-ci ne sont pas dans le code Java. Ils sont identifis par un code dans un fichier de configuration et lobjet [SqlMapClient sqlMapClient] utilise ce code pour faire excuter un ordre SQL particulier.

Un embryon de configuration de notre couche [dao] qui reflterait l'architecture ci-dessus serait le suivant :
1. <!-- la classes d'acc la couche [dao] --> 2. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> 3. <property name="sqlMapClient"> 4. <ref local="sqlMapClient"/> 5. </property> 6.</bean>

Ici la proprit [sqlMapClient] (ligne 3) de la classe [DaoImplCommon] (ligne 2) est initialise. Elle lest par la mthode [setSqlMapClient] de la classe [DaoImpl]. Cette classe na pas cette mthode. Cest sa classe parent [SqlMapClientDaoSupport] qui la. Cest donc elle qui est en ralit initialise ici. Maintenant ligne 4, on fait rfrence un objet nomm " sqlMapClient " qui reste construire. Celui-ci, on la dit, est de type [SqlMapClient], un type [iBATIS] :

[SqlMapClient] est une interface. Spring offre la classe [SqlMapClientFactoryBean] pour obtenir un objet implmentant cette interface :

Rappelons que nous cherchons instancier un objet implmentant linterface [SqlMapClient]. Ce nest apparemment pas le cas de la classe [SqlMapClientFactoryBean]. Celle-ci implmente linterface [FactoryBean] (cf ci-dessus). Celle-ci a la mthode [getObject()] suivante :

Lorsquon demande Spring une instance dun objet implmentant linterface [FactoryBean], il :

cre une instance [I] de la classe - ici il cre une instance de type [SqlMapClientFactoryBean]. rend la mthode appelante, le rsultat de la mthode [I].getObject() - la mthode [SqlMapClientFactoryBean].getObject() va rendre ici un objet implmentant linterface [SqlMapClient].

Les bases du dveloppement web MVC en Java, par l'exemple

198/264

Pour pouvoir rendre un objet implmentant linterface [SqlMapClient], la classe [SqlMapClientFactoryBean] a besoin de deux informations ncessaires cet objet :

un objet [DataSource] connect la base de donnes auprs duquel il va demander des connexions un (ou des) fichier de configuration o sont externaliss les ordres SQL excuter

La classe [SqlMapClientFactoryBean] possde les mthodes set pour initialiser ces deux proprits :

Nous progressons... Notre fichier de configuration se prcise et devient :


1.<!-- SqlMapCllient --> 2. <bean id="sqlMapClient" 3. class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 4. <property name="dataSource"> 5. <ref local="dataSource"/> 6. </property> 7. <property name="configLocation"> 8. <value>classpath:sql-map-config-firebird.xml</value> 9. </property> 10. </bean> 11. <!-- la classes d'acc la couche [dao] --> 12. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> 13. <property name="sqlMapClient"> 14. <ref local="sqlMapClient"/> 15. </property> 16. </bean>

lignes 2-3 : le bean " sqlMapClient " est de type [SqlMapClientFactoryBean]. De ce qui vient dtre expliqu, nous savons que lorsque nous demandons Spring une instance de ce bean, nous obtenons un objet implmentant linterface iBATIS [SqlMapClient]. Cest ce dernier objet qui sera donc obtenu en ligne 14. lignes 7-9 : nous indiquons que le fichier de configuration ncessaire lobjet iBATIS [SqlMapClient] sappelle " sqlmap-config-firebird.xml " et quil doit tre cherch dans le ClassPath de lapplication. La mthode [SqlMapClientFactoryBean].setConfigLocation est ici utilise. lignes 4-6 : nous initialisons la proprit [dataSource] de [SqlMapClientFactoryBean] avec sa mthode [setDataSource].

Ligne 5, nous faisons rfrence un bean appel " dataSource " qui reste construire. Si on regarde le paramtre attendu par la mthode [setDataSource] de [SqlMapClientFactoryBean], on voit quil est de type [DataSource] :

On a de nouveau affaire une interface dont il nous faut trouver une classe dimplmentation. Le rle dune telle classe est de fournir une application, de faon efficace, des connexions une base de donnes particulire. Un SGBD ne peut maintenir ouvertes simultanment un grand nombre de connexions. Pour diminuer le nombre de connexions ouvertes un moment donn, on est amens, pour chaque change avec la base, :

ouvrir une connexion commencer une transaction mettre des ordres SQL fermer la transaction fermer la connexion

Ouvrir et fermer des connexions de faon rpte est coteux en temps. Pour rsoudre ces deux problmes (limiter la fois le nombre de connexions ouvertes un moment donn, et limiter le cot douverture / fermeture de celles-ci, les classes implmentant linterface [DataSource] procdent souvent de la faon suivante :

elles ouvrent ds leur instanciation, N connexions avec la base de donnes vise. N a en gnral une valeur par dfaut et peut le plus souvent tre dfini dans un fichier de configuration. Ces N connexions vont rester tout le temps ouvertes et forment un pool de connexions disponibles pour les threads de lapplication.

Les bases du dveloppement web MVC en Java, par l'exemple

199/264

lorsquun thread de lapplication demande une ouverture de connexion, lobjet [DataSource] lui donne lune des N connexions ouvertes au dmarrage, sil en reste de disponibles. Lorsque lapplication ferme la connexion, cette dernire nest en ralit pas ferme mais simplement remise dans le pool des connexions disponibles.

Il existe diverses implmentations de linterface [DataSource] disponibles librement. Nous allons utiliser ici limplmentation [commons DBCP] disponible lurl [http://jakarta.apache.org/commons/dbcp/] :

Lutilisation de loutil [commons DBCP] ncessite deux archives [commons-dbcp, commons-pool] qui ont t toutes deux places dans le dossier [lib] du projet :

La classe [BasicDataSource] de [commons DBCP] fournit limplmentation [DataSource] dont nous avons besoin :

Cette classe va nous fournir un pool de connexions pour accder la base Firebird [dbpersonnes.gdb] de notre application. Pour cela, il faut lui donner les informations dont elle a besoin pour crer les connexions du pool : 1. 2. 3. 4. le nom du pilote JDBC utiliser initialis avec [setDriverClassName] le nom de lurl de la base de donnes exploiter - initialis avec [setUrl] lidentifiant de lutilisateur propritaire de la connexion initialis avec [setUsername] (et non pas setUserName comme on aurait pu s'y attendre) son mot de passe - initialis avec [setPassword]

Le fichier de configuration de notre couche [dao] pourra tre le suivant :


1. 2. 3. 4. 5. 6. 7. 8. <?xml version="1.0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- la source de donnees DBCP --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>org.firebirdsql.jdbc.FBDriver</value>

Les bases du dveloppement web MVC en Java, par l'exemple

200/264

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.

</property> <!-- attention : ne pas laisser d'espaces entre les deux balises <value> de lurl --> <property name="url"> <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes.gdb</value> </property> <property name="username"> <value>sysdba</value> </property> <property name="password"> <value>masterkey</value> </property> </bean> <!-- SqlMapCllient --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-firebird.xml</value> </property> </bean> <!-- la classes d'acc la couche [dao] --> <bean id="dao" class="istia.st.mvc.personnes.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 : lurl de la base Firebird [dbpersonnes.gdb]. On fera particulirement attention lcriture de celle-ci. Il ne doit y avoir aucun espace entre les balises <value> et lurl. lignes 14-16 : le propritaire de la connexion ici, [sysdba] qui est ladministrateur par dfaut des distributions Firebird lignes 17-19 : son mot de passe [masterkey] galement la valeur par dfaut

On a beaucoup progress mais il reste toujours des points de configuration lucider : la ligne 28 rfrence le fichier [sql-mapconfig-firebird.xml] qui doit configurer le client [SqlMapClient] diBATIS. Avant dtudier son contenu, montrons lemplacement de ces fichiers de configuration dans notre projet Eclipse :

[spring-config-test-dao-firebird.xml] est le fichier de configuration de la couche [dao] que nous venons dtudier [sql-map-config-firebird.xml] est rfrenc par [spring-config-test-dao-firebird.xml]. Nous allons ltudier. [personnes-firebird.xml] est rfrenc par [sql-map-config-firebird.xml]. Nous allons ltudier.

Les trois fichiers prcdents sont dans le dossier [src]. Sous Eclipse, cela signifie qu lexcution ils seront prsents dans le dossier [bin] du projet (non reprsent ci-dessus). Ce dossier fait partie du ClassPath de lapplication. Au final, les trois fichiers prcdents seront donc bien prsents dans le ClassPath de lapplication. Cest ncessaire. Le fichier [sql-map-config-firebird.xml] est le suivant :
1. 2. 3. 4. 5. 6. 7. 8. <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <sqlMap resource="personnes-firebird.xml"/> </sqlMapConfig>

ce fichier doit avoir <sqlMapConfig> comme balise racine (lignes 6 et 8) ligne 7 : la balise <sqlMap> sert dsigner les fichiers qui contiennent les ordres SQL excuter. Il y a souvent, mais ce nest pas obligatoire, un fichier par table. Cela permet de rassembler les ordres SQL sur une table donne dans un mme fichier. Mais on trouve frquemment des ordres SQL impliquant plusieurs tables. Dans ce cas, la dcomposition Les bases du dveloppement web MVC en Java, par l'exemple 201/264

prcdente ne tient pas. Il faut simplement se rappeler que lensemble des fichiers dsigns par les balises <sqlMap> seront fusionns. Ces fichiers sont cherchs dans le ClassPath de lapplication. Le fichier [personnes-firebird.xml] dcrit les ordres SQL qui vont tre mis sur la table [PERSONNES] de la base de donnes Firebird [dbpersonnes.gdb]. Son contenu 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd"> <sqlMap> <!-- alias classe [Personne] --> <typeAlias alias="Personne.classe" type="istia.st.mvc.personnes.entites.Personne"/> <!-- mapping table [PERSONNES] - objet [Personne] --> <resultMap id="Personne.map" class="Personne.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> <!-- liste de toutes les personnes --> <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select> <!-- obtenir une personne en particulier --> <select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#value#</select> <!-- ajouter une personne --> <insert id="Personne.insertOne" parameterClass="Personne.classe"> <selectKey keyProperty="id"> SELECT GEN_ID(GEN_PERSONNES_ID,1) as "value" FROM RDB$$DATABASE </selectKey> insert into PERSONNES(ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) VALUES(#id#, #version#, #nom#, #prenom#, #dateNaissance#, #marie#, #nbEnfants#) </insert> <!-- mettre jour une personne --> <update id="Personne.updateOne" parameterClass="Personne.classe"> update PERSONNES set VERSION=#version#+1, NOM=#nom#, PRENOM=#prenom#, DATENAISSANCE=#dateNaissance#, MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# and VERSION=#version#</update> <!-- supprimer une personne --> <delete id="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 lcriture du fichier on donne lalias (synonyme) [Personne.classe] la classe [istia.st.springmvc.personnes.entites.Personne]. lignes 12-21 : fixe les correspondances entre colonnes de la table [PERSONNES] et champs de lobjet [Personne]. lignes 23-24 : lordre SQL [select] pour obtenir toutes les personnes de la table [PERSONNES] lignes 26-27 : lordre SQL [select] pour obtenir une personne particulire de la table [PERSONNES] lignes 29-36 : lordre SQL [insert] qui insre une personne dans la table [PERSONNES] lignes 38-41 : lordre SQL [update] qui met jour une personne de la table [PERSONNES] lignes 42-44 : lordre SQL [delete] qui supprime une personne de la table [PERSONNES]

Le rle et la signification du contenu du fichier [personnes-firebird.xml] vont tre expliqus via ltude de la classe [DaoImplCommon] qui implmente la couche [dao].

17.3.3

La classe [DaoImplCommon]

Revenons sur larchitecture daccs aux donnes : utilisateur

Couche d'accs aux donnes [SqlMapDaoClientSupport : DaoImplCommon]

Couche d'accs aux donnes [SqlMapClient]

Couche d'accs aux donnes [JDBC]

Donnes

Les bases du dveloppement web MVC en Java, par l'exemple

202/264

La classe [DaoImplCommon] est 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. 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. package istia.st.mvc.personnes.dao; import istia.st.mvc.personnes.entites.Personne; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; import java.util.Collection; public class DaoImplCommon extends SqlMapClientDaoSupport implements IDao { // liste des personnes public Collection getAll() { ... } // obtenir une personne en particulier public Personne getOne(int id) { ... } // suppression d'une personne public void deleteOne(int id) { ... } // ajouter ou modifier une personne public void saveOne(Personne personne) { // le paramtre personne est-il valide ? check(personne); // ajout ou modification ? if (personne.getId() == -1) { // ajout insertPersonne(personne); } else { updatePersonne(personne); } } // ajouter une personne protected void insertPersonne(Personne personne) { ... } // modifier une personne protected void updatePersonne(Personne personne) { ... } // vrification validit d'une personne private void check(Personne p) { ... } ... }

Nous allons tudier les mthodes les unes aprs les autres. getAll Cette mthode permet dobtenir toutes les personnes de la liste. Son code est le suivant :
1. // liste des personnes 2. public Collection getAll() { 3. return getSqlMapClientTemplate().queryForList("Personne.getAll", null); 4.}

Rappelons-nous tout dabord que la classe [DaoImplCommon] drive de la classe Spring [SqlMapClientDaoSupport]. Cest cette classe qui a la mthode [getSqlMapClientTemplate()] utilise ligne 3 ci-dessus. Cette mthode a la signature suivante :

Le type [SqlMapClientTemplate] encapsule lobjet [SqlMapClient] de la couche [iBATIS]. Cest par lui quon aura accs la base de donnes. Le type [iBATIS] SqlMapClient pourrait tre directement utilis puisque la classe [SqlMapClientDaoSupport] y a accs : Les bases du dveloppement web MVC en Java, par l'exemple 203/264

Linconvnient de la classe [iBATIS] SqlMapClient est quelle lance des exceptions de type [SQLException], un type d'exception contrle, c.a.d. qui doit tre gre par un try / catch ou dclare dans la signature des mthodes qui la lance. Or souvenons-nous que la couche [dao] implmente une interface [IDao] dont les mthodes ne comportent pas dexceptions dans leurs signatures. Les mthodes des classes dimplmentation de linterface [IDao] ne peuvent donc, elles non plus, avoir dexceptions dans leurs signatures. Il nous faut donc intercepter chaque exception [SQLException] lance par la couche [iBATIS] et lencapsuler dans une exception non contrle. Le type [DaoException] de notre projet ferait laffaire pour cette encapsulation. Plutt que de grer nous-mmes ces exceptions, nous allons les confier au type Spring [SqlMapClientTemplate] qui encapsule lobjet [SqlMapClient] de la couche [iBATIS]. En effet [SqlMapClientTemplate] a t construit pour intercepter les exceptions [SQLException] lances par la couche [SqlMapClient] et les encapsuler dans un type [DataAccessException] non contrl. Ce comportement nous convient. On se souviendra simplement que la couche [dao] est dsormais susceptible de lancer deux types dexceptions non contrles : notre type propritaire [DaoException] le type Spring [DataAccessException] Le type [SqlMapClientTemplate] est dfini comme suit :

Il implmente linterface [SqlMapClientOperations] suivante :

Cette interface dfinit des mthodes capables dexploiter le contenu du fichier [personnes-firebird.xml] : [queryForList]

Cette mthode permet dmettre un ordre [SELECT] et den rcuprer le rsultat sous forme dune liste dobjets :

[statementName] : lidentifiant (id) de lordre [select] dans le fichier de configuration [parameterObject] : lobjet " paramtre " pour un [select] paramtr. Lobjet " paramtre " peut prendre deux formes : un objet respectant la norme Javabean : les paramtres de lordre [select] sont alors les noms des champs du Javabean. A lexcution de lordre [select], ils sont remplacs par les valeurs de ces champs. un dictionnaire : les paramtres de lordre [select] sont alors les cls du dictionnaire. A lexcution de lordre [select], celles-ci sont remplaces par leurs valeurs associes dans le dictionnaire. si le [SELECT] ne ramne aucune ligne, le rsultat [List] est un objet vide d'lments mais pas null ( vrifier).

[queryForObject]

Les bases du dveloppement web MVC en Java, par l'exemple

204/264

Cette mthode est identique dans son esprit la prcdente mais elle ne ramne quun unique objet. Si le [SELECT] ne ramne aucune ligne, le rsultat est le pointeur null. [insert]

Cette mthode permet dexcuter un ordre SQL [insert] paramtr par le second paramtre. L'objet rendu est la cl primaire de la ligne qui a t insre. Il n'y a pas d'obligation utiliser ce rsultat. [update]

Cette mthode permet dexcuter un ordre SQL [update] paramtr par le second paramtre. Le rsultat est le nombre de lignes modifies par l'ordre SQL [update]. [delete]

Cette mthode permet dexcuter un ordre SQL [delete] paramtr par le second paramtre. Le rsultat est le nombre de lignes supprimes par l'ordre SQL [delete]. Revenons la mthode [getAll] de la classe [DaoImplCommon] :
1. // liste des personnes 2. public Collection getAll() { 3. return getSqlMapClientTemplate().queryForList("Personne.getAll", null); 4.}

ligne 4 : lordre [select] nomm " Personne.getAll " est excut. Il nest pas paramtr et donc lobjet " paramtre " est null.

Dans [personnes-firebird.xml], lordre [select] nomm " Personne.getAll " 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 sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" "http://www.ibatis.com/dtd/sql-map-2.dtd"> <sqlMap> <!-- alias classe [Personne] --> <typeAlias alias="Personne.classe" type="istia.st.mvc.personnes.entites.Personne"/> <!-- mapping table [PERSONNES] - objet [Personne] --> <resultMap id="Personne.map" class="Personne.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> <!-- liste de toutes les personnes --> <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select> ... </sqlMap>

ligne 23 : lordre SQL " Personne.getAll " est non paramtr (absence de paramtres dans le texte de la requte). la ligne 3 de la mthode [getAll] demande lexcution de la requte [select] appele " Personne.getAll ". Celle-ci va tre excute. [iBATIS] sappuie sur JDBC. On sait alors que le rsultat de la requte va tre obtenu sous la forme dun objet [ResultSet]. Ligne 23, lattribut [resultMap] de la balise <select> indique [iBATIS] quel " resultMap " il doit utiliser pour transformer chaque ligne du [ResultSet] obtenu en objet. Cest le " resultMap " [Personne.map] 205/264

Les bases du dveloppement web MVC en Java, par l'exemple

dfini lignes 12-21 qui indique comment passer dune ligne de la table [PERSONNES] un objet de type [Personne]. [iBATIS] va utiliser ces correspondances pour fournir une liste dobjets [Personne] partir des lignes de lobjet [ResultSet]. la ligne 3 de la mthode [getAll] renvoie alors une collection dobjets [Personne] la mthode [queryForList] peut lancer une exception Spring [DataAccessException]. Nous la laissons remonter.

Nous expliquons les autres mthodes de la classe [AbstractDaoImpl] plus rapidement, lessentiel sur lutilisation d[iBATIS] ayant t dit dans ltude de la mthode [getAll]. getOne Cette mthode permet dobtenir une personne identifie par son [id]. Son code est le suivant :
1. // obtenir une personne en particulier 2. public Personne getOne(int id) { 3. // on la rcupre dans la BD 4. Personne personne = (Personne) getSqlMapClientTemplate() 5. .queryForObject("Personne.getOne", new Integer(id)); 6. // a-t-on rcupr qq chose ? 7. if (personne == null) { 8. // on lance une exception 9. throw new DaoException( 10. "La personne d'id [" + id + "] n'existe pas", 2); 11. } 12. // on rend la personne 13. return personne; 14. }

ligne 4 : demande lexcution de lordre [select] nomm " Personne.getOne ". Celui-ci est le suivant dans le fichier [personnes-firebird.xml] :
1. <!-- obtenir une personne en particulier --> 2. <select id="Personne.getOne" resultMap="Personne.map" parameterClass="int"> 3. select ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM 4. PERSONNES WHERE ID=#value#</select>

Lordre SQL est paramtr par le paramtre #value# (ligne 4). Lattribut #value# dsigne la valeur du paramtre pass lordre SQL, lorsque ce paramtre est de type simple : Integer, Double, String, ... Dans les attributs de la balise <select>, lattribut [parameterClass] indique que le paramtre est de type entier (ligne 2). Ligne 5 de [getOne], on voit que ce paramtre est lidentifiant de la personne cherche sous la forme dun objet Integer. Ce changement de type est obligatoire puisque le second paramtre de [queryForList] doit tre de type [Object]. Le rsultat de la requte [select] sera transformer en objet via lattribut [resultMap="Personne.map"] (ligne 2). On obtiendra donc un type [Personne].

lignes 7-11 : si la requte [select] na ramen aucune ligne, on rcupre alors le pointeur null en ligne 4. Cela signifie quon na pas trouv la personne cherche. Dans ce cas, on lance une [DaoException] de code 2 (lignes 9-10). ligne 13 : sil ny a pas eu dexception, alors on rend lobjet [Personne] demand.

deleteOne Cette mthode permet de supprimer une personne identifie par son [id]. Son code est le suivant :
1. // suppression d'une personne 2. public void deleteOne(int id) { 3. // on supprime la personne 4. int n = getSqlMapClientTemplate().delete("Personne.deleteOne", 5. new Integer(id)); 6. // a-t-on russi 7. if (n == 0) { 8. throw new DaoException("Personne d'id [" + id + "] inconnue", 2); 9. } 10. }

lignes 4-5 : demande lexcution de lordre [delete] nomm " Personne.deleteOne ". Celui-ci est le suivant dans le fichier [personnes-firebird.xml] :
1.<!-- supprimer une personne --> 2. <delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 3. ID=#value# </delete>

Les bases du dveloppement web MVC en Java, par l'exemple

206/264

Lordre SQL est paramtr par le paramtre #value# (ligne 3) de type [parameterClass="int"] (ligne 2). Ce sera lidentifiant de la personne cherche (ligne 5 de deleteOne)

ligne 4 : le rsultat de la mthode [SqlMapClientTemplate].delete est le nombre de lignes dtruites. lignes 7-8 : si la requte [delete] na dtruit aucune ligne, cela signifie que la personne nexiste pas. On lance une [DaoException] de code 2 (ligne 8).

saveOne Cette mthode permet dajouter une nouvelle personne ou de modifier une personne existante. Son code est le suivant :
1. // ajouter ou modifier une personne 2. public void saveOne(Personne personne) { 3. // le paramtre personne est-il valide ? 4. check(personne); 5. // ajout ou modification ? 6. if (personne.getId() == -1) { 7. // ajout 8. insertPersonne(personne); 9. } else { 10. updatePersonne(personne); 11. } 12. } 13....

ligne 4 : on vrifie la validit de la personne avec la mthode [check]. Cette mthode existait dj dans la version prcdente et avait t alors commente. Elle lance une [DaoException] si la personne est invalide. On laisse remonter celle-ci. ligne 6 : si on arrive l, cest quil ny a pas eu dexception. La personne est donc valide. lignes 6-11 : selon lid de la personne, on a affaire un ajout (id= -1) ou une mise jour (id<> -1). Dans les deux cas, on fait appel deux mthodes internes la classe : insertPersonne : pour lajout updatePersonne : pour la mise jour

insertPersonne Cette mthode permet dajouter une nouvelle personne. Son code est le suivant :
1.// ajouter une personne 2. protected void insertPersonne(Personne personne) { 3. // 1re version 4. personne.setVersion(1); 5. // on attend 10 ms - pour les tests mettre true au lieu de false 6. if (true) 7. wait(10); 8. // on insre la nouvelle personne dans la table de la BD 9. getSqlMapClientTemplate().insert("Personne.insertOne", personne); 10. }

ligne 4 : on met 1 le n de version de la personne que lon est en train de crer ligne 9 : on fait linsertion via la requte nomme " Personne.insertOne " qui est la suivante :
1. 2. 3. 4. 5. 6. 7. 8. <insert id="Personne.insertOne" parameterClass="Personne.classe"> <selectKey keyProperty="id"> SELECT GEN_ID(GEN_PERSONNES_ID,1) as "value" FROM RDB$$DATABASE </selectKey> insert into PERSONNES(ID, VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) VALUES(#id#, #version#, #nom#, #prenom#, #dateNaissance#, #marie#, #nbEnfants#) </insert>

Cest une requte paramtre et le paramtre est de type [Personne] (parameterClass="Personne.classe", ligne 1). Les champs de lobjet [Personne] passs en paramtre (ligne 9 de insertPersonne) sont utiliss pour remplir les colonnes de la ligne qui va tre insre dans la table [PERSONNES] (lignes 5-8). On a un problme rsoudre. Lors d'une insertion, lobjet [Personne] insrer a son id gal -1. Il faut remplacer cette valeur par une cl primaire valide. On utilise pour cela les lignes 2-4 de la balise <selectKey> ci-dessus. Elles indiquent : la requte SQL excuter pour obtenir une valeur de cl primaire. Celle indique ici est celle que nous avons prsente au paragraphe 17.1 - page 192. Deux points sont noter : as " value " est obligatoire. On peut aussi crire as value mais value est un mot cl de Firebird qui a du tre protg par des guillemets.
Les bases du dveloppement web MVC en Java, par l'exemple

207/264

la table Firebird s'appelle en ralit [RDB$DATABASE]. Mais le caractre $ est interprt par [iBATIS]. Il a t protg en le ddoublant. le champ de l'objet [Personne] qu'il faut initialiser avec la valeur rcupre par l'ordre [SELECT], ici le champ [id]. C'est l'attribut [keyProperty] de la ligne 2 qui indique ce champ.

lignes 6-7 : pour le besoin des tests, nous serons amens attendre 10 ms avant de faire l'insertion, ceci pour voir sil y a des conflits entre threads qui voudraient faire en mme temps des ajouts.

updatePersonne Cette mthode permet de modifier une personne existant dj dans la table [PERSONNES]. Son code est le suivant :
1.// modifier une personne 2. protected void updatePersonne(Personne personne) { 3. // on attend 10 ms - pour les tests mettre true au lieu de false 4. if (true) 5. wait(10); 6. // modification 7. int n = getSqlMapClientTemplate() 8. .update("Personne.updateOne", personne); 9. if (n == 0) 10. throw new DaoException("La personne d'Id [" + personne.getId() 11. + "] n'existe pas ou bien a t modifie", 2); 12. }

une mise jour peut chouer pour au moins deux raisons : 1. la personne mettre jour nexiste pas 2. la personne mettre jour existe mais le thread qui veut la modifier na pas la bonne version lignes 7-8 : la requte SQL [update] nomme " Personne.updateOne " est excute. C'est la suivante :
1. 2. 3. <!-- mettre jour une personne --> <update id="Personne.updateOne" parameterClass="Personne.classe"> update PERSONNES set VERSION=#version#+1, NOM=#nom#, PRENOM=#prenom#, DATENAISSANCE=#dateNaissance#, 4. MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# and 5. VERSION=#version#</update>

ligne 2 : la requte est paramtre et admet pour paramtre un type [Personne] (parameterClass="Personne.classe"). Celui-ci est la personne modifier (ligne 8 updatePersonne). on ne veut modifier que la personne de la table [PERSONNES] ayant le mme n [id] et la mme version [version] que le paramtre. Cest pourquoi, on a la contrainte [WHERE ID=#id# and VERSION=#version#]. Si cette personne est trouve, elle est mise jour avec la personne paramtre et sa version est augmente de 1 (ligne 3 ci-dessus).

ligne 9 : on rcupre le nombre de lignes mises jour. lignes 10-11 : si ce nombre est nul, on lance une [DaoException] de code 2, indiquant que, soit la personne mettre jour nexiste pas, soit elle a chang de version entre-temps.

17.4
17.4.1

Tests de la couche [dao]


Tests de l'implmentation [DaoImplCommon]

Maintenant que nous avons crit la couche [dao], nous nous proposons de la tester avec des tests JUnit :

Avant de faire des tests intensifs, nous pouvons commencer par un simple programme de type [main] qui va afficher le contenu de la table [PERSONNES]. Cest la classe [MainTestDaoFirebird] :
1. 2. 3. package istia.st.mvc.personnes.tests; import istia.st.mvc.personnes.dao.IDao;

Les bases du dveloppement web MVC en Java, par l'exemple

208/264

4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.

import java.util.Collection; import java.util.Iterator; import org.springframework.beans.factory.xml.XmlBeanFactory; import org.springframework.core.io.ClassPathResource; public class MainTestDaoFirebird { public static void main(String[] args) { IDao dao = (IDao) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-dao-firebird.xml"))).getBean("dao"); // liste actuelle Collection personnes = dao.getAll(); // affichage console Iterator iter = personnes.iterator(); while (iter.hasNext()) { System.out.println(iter.next()); } } }

Le fichier de configuration [spring-config-test-dao-firebird.xml] de la couche [dao], utilis lignes 13-14, est le suivant :
1. <?xml version="1.0" encoding="ISO_8859-1"?> 2. <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> 3. <beans> 4. <!-- la source de donnees DBCP --> 5. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 6. destroy-method="close"> 7. <property name="driverClassName"> 8. <value>org.firebirdsql.jdbc.FBDriver</value> 9. </property> 10. <!-- attention : ne pas laisser d'espaces entre les deux balises <value> --> 11. <property name="url"> 12. <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes.gdb</value> 13. </property> 14. <property name="username"> 15. <value>sysdba</value> 16. </property> 17. <property name="password"> 18. <value>masterkey</value> 19. </property> 20. </bean> 21. <!-- SqlMapCllient --> 22. <bean id="sqlMapClient" 23. class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 24. <property name="dataSource"> 25. <ref local="dataSource"/> 26. </property> 27. <property name="configLocation"> 28. <value>classpath:sql-map-config-firebird.xml</value> 29. </property> 30. </bean> 31. <!-- la classes d'acc la couche [dao] --> 32. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> 33. <property name="sqlMapClient"> 34. <ref local="sqlMapClient"/> 35. </property> 36. </bean> 37. </beans>

Ce fichier est celui tudi au paragraphe 17.3.2, page 200. Pour le test, le SGBD Firebird est lanc. Le contenu de la table [PERSONNES] est le suivant :

Lexcution du programme [MainTestDaoFirebird] donne les rsultats cran suivants :

Les bases du dveloppement web MVC en Java, par l'exemple

209/264

On a bien obtenu la liste des personnes. On peut passer au test JUnit. Le test JUnit [TestDaoFirebird] 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. 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. package istia.st.mvc.personnes.tests; import import import import import import import import import import java.text.ParseException; java.text.SimpleDateFormat; java.util.Collection; java.util.Iterator; org.springframework.beans.factory.xml.XmlBeanFactory; org.springframework.core.io.ClassPathResource; istia.st.mvc.personnes.dao.DaoException; istia.st.mvc.personnes.dao.IDao; istia.st.mvc.personnes.entites.Personne; junit.framework.TestCase;

public class TestDaoFirebird extends TestCase { // couche [dao] private IDao dao; public IDao getDao() { return dao; } public void setDao(IDao dao) { this.dao = dao; } // constructeur public void setUp() { dao = (IDao) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-dao-firebird.xml"))).getBean("dao"); } // liste des personnes private void doListe(Collection personnes) { ... } // test1 public void test1() throws ParseException { ... } // modification-suppression d'un lment inexistant public void test2() throws ParseException { .. } // gestion des versions de personne public void test3() throws ParseException, InterruptedException { ... } // optimistic locking - accs multi-threads public void test4() throws Exception { ... } // tests de validit de saveOne public void test5() throws ParseException { .... } // insertions multi-threads public void test6() throws ParseException, InterruptedException{ ... }

Les bases du dveloppement web MVC en Java, par l'exemple

210/264

les tests [test1] [test5] sont les mmes que dans la version 1, sauf [test4] qui a lgrement volu. Le test [test6] est lui nouveau. Nous ne commentons que ces deux tests.

[test4] [test4] a pour objectif de tester la mthode [updatePersonne - DaoImplCommon]. On rappelle le code de celle-ci :
1.// modifier une personne 2. protected void updatePersonne(Personne personne) { 3. // on attend 10 ms - pour les tests mettre true au lieu de false 4. if (true) 5. wait(10); 6. // modification 7. int n = getSqlMapClientTemplate() 8. .update("Personne.updateOne", personne); 9. if (n == 0) 10. throw new DaoException("La personne d'Id [" + personne.getId() 11. + "] n'existe pas ou bien a t modifie", 2); 12. }

lignes 4-5 : on attend 10 ms. On force ainsi le thread qui excute [updatePersonne] perdre le processeur, ce qui peut augmenter nos chances de voir des conflits daccs entre threads concurrents.

[test4] lance N=100 threads chargs dincrmenter, en mme temps, de 1 le nombre denfants de la mme personne. On veut voir comment les conflits de version et les conflits daccs sont grs.
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. public void test4() throws Exception { // ajout d'une personne Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat( "dd/MM/yyyy").parse("01/02/2006"), true, 0); dao.saveOne(p1); int id1 = p1.getId(); // cration de N threads de mise jour du nombre d'enfants final int N = 100; Thread[] taches = new Thread[N]; for (int i = 0; i < taches.length; i++) { taches[i] = new ThreadDaoMajEnfants("thread n " + i, dao, id1); taches[i].start(); } // on attend la fin des threads for (int i = 0; i < taches.length; i++) { taches[i].join(); } // on rcupre la personne p1 = dao.getOne(id1); // elle doit avoir N enfants assertEquals(N, p1.getNbEnfants()); // suppression personne p1 dao.deleteOne(p1.getId()); // vrification boolean erreur = false; int codeErreur = 0; try { p1 = dao.getOne(p1.getId()); } catch (DaoException ex) { erreur = true; codeErreur = ex.getCode(); } // on doit avoir une erreur de code 2 assertTrue(erreur); assertEquals(2, codeErreur); }

Les threads sont crs lignes 8-13. Chacun va augmenter de 1 le nombre d'enfants de la personne cre lignes 3-5. Les threads [ThreadDaoMajEnfants ] de mise jour sont les suivants :
1. package istia.st.mvc.personnes.tests; 2. 3. import java.util.Date; 4. 5. import istia.st.mvc.personnes.dao.DaoException; 6. import istia.st.mvc.personnes.dao.IDao; 7. import istia.st.mvc.personnes.entites.Personne; 8. 9. public class ThreadDaoMajEnfants extends Thread { 10. // nom du thread 11. private String name; 12. 13. // rfrence sur la couche [dao] 14. private IDao dao; Les bases du dveloppement web MVC en Java, par l'exemple

211/264

15. 16. // l'id de la personne sur qui on va travailler 17. private int idPersonne; 18. 19. // constructeur 20. public ThreadDaoMajEnfants(String name, IDao dao, int idPersonne) { 21. this.name = name; 22. this.dao = dao; 23. this.idPersonne = idPersonne; 24. } 25. 26. // coeur du thread 27. public void run() { 28. // suivi 29. suivi("lanc"); 30. // on boucle tant qu'on n'a pas russi incrmenter de 1 31. // le nbre d'enfants de la personne idPersonne 32. boolean fini = false; 33. int nbEnfants = 0; 34. while (!fini) { 35. // on rcupre une copie de la personne d'idPersonne 36. Personne personne = dao.getOne(idPersonne); 37. nbEnfants = personne.getNbEnfants(); 38. // suivi 39. suivi("" + nbEnfants + " -> " + (nbEnfants + 1) 40. + " pour la version " + personne.getVersion()); 41. // attente de 10 ms pour abandonner le processeur 42. try { 43. // suivi 44. suivi("dbut attente"); 45. // on s'interrompt pour laisser le processeur 46. Thread.sleep(10); 47. // suivi 48. suivi("fin attente"); 49. } catch (Exception ex) { 50. throw new RuntimeException(ex.toString()); 51. } 52. // attente termine - on essaie de valider la copie 53. // entre-temps d'autres threads ont pu modifier l'original 54. int codeErreur = 0; 55. try { 56. // incrmente de 1 le nbre d'enfants de cette copie 57. personne.setNbEnfants(nbEnfants + 1); 58. // on essaie de modifier l'original 59. dao.saveOne(personne); 60. // on est pass - l'original a t modifi 61. fini = true; 62. } catch (DaoException ex) { 63. // on rcupre le code erreur 64. codeErreur = ex.getCode(); 65. // si une erreur d'ID ou de version de code erreur 2, on ressaie la mise jour 66. switch (codeErreur) { 67. case 2: 68. suivi("version corrompue ou personne inexistante"); 69. break; 70. default: 71. // exception non gre - on laisse remonter 72. throw ex; 73. } 74. } 75. } 76. // suivi 77. suivi("a termin et pass le nombre d'enfants " + (nbEnfants + 1)); 78. } 79. 80. // suivi 81. private void suivi(String message) { 82. System.out.println(name + " [" + new Date().getTime() + "] : " 83. + message); 84. } 85. }

Une mise jour de personne peut chouer parce que la personne quon veut modifier nexiste pas ou quelle a t mise jour auparavant par un autre thread. Ces deux cas sont ici grs lignes 67-69. Dans ces deux cas en effet, la mthode [updatePersonne] lance une [DaoException] de code 2. Le thread sera alors ramen recommencer la procdure de mise jour depuis son dbut (boucle while, ligne 34). [test6] [test6] a pour objectif de tester la mthode [insertPersonne - DaoImplCommon]. On rappelle le code de celle-ci :
1.// ajouter une personne 2. protected void insertPersonne(Personne personne) { Les bases du dveloppement web MVC en Java, par l'exemple

212/264

3. 4. 5. 6. 7. 8. 9. 10. }

// 1re version personne.setVersion(1); // on attend 10 ms - pour les tests mettre true au lieu de false if (true) wait(10); // on insre la nouvelle personne dans la table de la BD getSqlMapClientTemplate().insert("Personne.insertOne", personne);

lignes 6-7 : on attend 10 ms pour forcer le thread qui excute [insertPersonne] perdre le processeur et augmenter ainsi nos chances de voir apparatre des conflits dus des threads qui font des insertions en mme temps.

Le code de [test6] est le suivant :


1. // insertions multi-threads 2. public void test6() throws ParseException, InterruptedException{ 3. // cration d'une personne 4. Personne p = new Personne(-1, "X", "X", new SimpleDateFormat( 5. "dd/MM/yyyy").parse("01/02/2006"), true, 0); 6. // qu'on duplique N fois dans un tableau 7. final int N = 100; 8. Personne[] personnes=new Personne[N]; 9. for(int i=0;i<personnes.length;i++){ 10. personnes[i]=new Personne(p); 11. } 12. // cration de N threads d'insertion - chaque thread insre 1 personne 13. Thread[] taches = new Thread[N]; 14. for (int i = 0; i < taches.length; i++) { 15. taches[i] = new ThreadDaoInsertPersonne("thread n " + i, dao, personnes[i]); 16. taches[i].start(); 17. } 18. // on attend la fin des threads 19. for (int i = 0; i < taches.length; i++) { 20. // thread n i 21. taches[i].join(); 22. // supression personne 23. dao.deleteOne(personnes[i].getId()); 24. } 25.}

On cre 100 threads qui vont insrer en mme temps 100 personnes diffrentes. Ces 100 threads vont tous obtenir une cl primaire pour la personne quils doivent insrer puis tre interrompus pendant 10 ms (ligne 10 insertPersonne) avant de pouvoir faire leur insertion. On veut vrifier que les choses se passent bien et que notamment ils obtiennent bien des valeurs de cl primaire diffrentes.

lignes 7-11 : un tableau de 100 personnes est cr. Ces personnes sont toutes des copies de la personne p cre lignes 4-5. lignes 14-17 : les 100 threads d'insertion sont lancs. Chacun d'eux est charg d'insrer l'une des 100 personnes cre prcdemment.. lignes 19-23 : [test6] attend la fin de chacun des 100 threads qu'il a lancs. Lorsqu'il a dtect la fin du thread n i, il supprime la personne que ce thread vient d'insrer.

Le thread dinsertion [ThreadDaoInsertPersonne] est le suivant :


1. 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.mvc.personnes.tests; import java.util.Date; import istia.st.mvc.personnes.dao.IDao; import istia.st.mvc.personnes.entites.Personne; public class ThreadDaoInsertPersonne extends Thread { // nom du thread private String name; // rfrence sur la couche [dao] private IDao dao; // l'id de la personne sur qui on va travailler private Personne personne; // constructeur public ThreadDaoInsertPersonne(String name, IDao dao, Personne personne) { this.name = name; this.dao = dao; this.personne = personne; } // coeur du thread

Les bases du dveloppement web MVC en Java, par l'exemple

213/264

25. public void run() { 26. // suivi 27. suivi("lanc"); 28. // insertion 29. dao.saveOne(personne); 30. // suivi 31. suivi("a termin"); 32. } 33. 34. // suivi 35. private void suivi(String message) { 36. System.out.println(name + " [" + new Date().getTime() + "] : " 37. + message); 38. } 39. }

lignes 19-22 : le constructeur du thread mmorise la personne qu'il doit insrer et la couche [dao] qu'il doit utiliser pour faire cette insertion. ligne 30 : la personne est insre. Si une exception se produit, elle remonte [test6].

Tests Aux tests, on obtient les rsultats suivants :

- le test [test6] russit.

- le test [test4] choue. Le test [test4] choue donc. Le nombre d'enfants est pass 69 au lieu de 100 attendu. Que s'est-il pass ? Examinons les logs cran. Ils montrent l'existence d'exceptions lances par Firebird :
1. 2. 3. 4. 5. 6. 7. 8. 9. Exception in thread "Thread-62" org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [HY000]; error code [335544336]; --- The error occurred in personnes-firebird.xml. --- The error occurred while applying a parameter map. --- Check the Personne.updateOne-InlineParameterMap. --- Check the statement (update failed). --- Cause: org.firebirdsql.jdbc.FBSQLException: GDS Exception. 335544336. deadlock update conflicts with concurrent update; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException: --- The error occurred in personnes-firebird.xml. --- The error occurred while applying a parameter map.

ligne 1 on a eu une exception Spring [org.springframework.jdbc.UncategorizedSQLException]. C'est une exception non contrle qui a t utilise pour encapsuler une exception lance par le pilote JDBC de Firebird, dcrite ligne 6. ligne 6 le pilote JDBC de Firebird a lanc une exception de type [org.firebirdsql.jdbc.FBSQLException] et de code d'erreur 335544336. ligne 7 : indique qu'on a eu un conflit d'accs entre deux threads qui voulaient mettre jour en mme temps la mme ligne de la table [PERSONNES].

Ce n'est pas une erreur irrcuprable. Le thread qui intercepte cette exception peut retenter la mise jour. Il faut pour cela modifier le code de [ThreadDaoMajEnfants] :
1. 2. 3. try { // incrmente de 1 le nbre d'enfants de cette copie personne.setNbEnfants(nbEnfants + 1);

Les bases du dveloppement web MVC en Java, par l'exemple

214/264

4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.

// on essaie de modifier l'original dao.saveOne(personne); // on est pass - l'original a t modifi fini = true; } catch (DaoException ex) { // on rcupre le code erreur codeErreur = ex.getCode(); // si une erreur d'ID ou de version de code ereur 2, on ressaie la mise jour switch (codeErreur) { case 2: suivi("version corrompue ou personne inexistante"); break; default: // exception non gre - on laisse remonter throw ex; }

ligne 8 : on gre une exception de type [DaoException]. D'aprs ce qui a t dit, il nous faudrait grer l'exception qui est apparue aux tests, le type [org.springframework.jdbc.UncategorizedSQLException]. On ne peut cependant pas se contenter de grer ce type qui est un type gnrique de Spring destin encapsuler des exceptions qu'il ne connat pas. Spring connat les exceptions mises par les pilotes JDBC d'un certain nombre de SGBD tels Oracle, MySQL, Postgres, DB2, SQL Server, ... mais pas Firebird. Aussi toute exception lance par le pilote JDBC de Firebird se trouve-t-elle encapsule dans le type Spring [org.springframework.jdbc.UncategorizedSQLException] :

On voit ci-dessus, que la classe [UncategorizedSQLException] drive de la classe [DataAccessException] que nous avons voque, paragraphe 17.3.3 page 204. Il est possible de connatre l'exception qui a t encapsule dans [UncategorizedSQLException] grce sa mthode [getSQLException] :

Cette exception de type [SQLException] est celle lance par la couche [iBATIS] qui elle mme encapsule l'exception lance par le pilote JDBC de la base de donnes. La cause exacte de l'exception de type [SQLException] peut tre obtenue par la mthode :

On obtient l'objet de type [Throwable] qui a t lanc par le pilote JDBC :

Le type [Throwable] est la classe parent de [Exception]. Ici il nous faudra vrifier que l'objet de type [Throwable] lanc par le pilote JDBC de Firebird et cause de l'exception [SQLException] lance par la couche [iBATIS] est bien une exception de type [org.firebirdsql.gds.GDSException] et de code
Les bases du dveloppement web MVC en Java, par l'exemple

215/264

d'erreur 335544336. Pour rcuprer le code erreur, nous pourrons utiliser la mthode [getErrorCode()] de la classe [org.firebirdsql.gds.GDSException]. Si nous utilisons dans le code de [ThreadDaoMajEnfants] l'exception [org.firebirdsql.gds.GDSException], alors ce thread ne pourra travailler qu'avec le SGBD Firebird. Il en sera de mme du test [test4] qui utilise ce thread. Nous voulons viter cela. En effet, nous souhaitons que nos tests JUnit restent valables quelque soit le SGBD utilis. Pour arriver ce rsultat, on dcide que la couche [dao] lancera une [DaoException] de code 4 lorsqu'une exception de type " conflit de mise jour " est dtecte et ce, quelque soit le SGBD sous-jacent. Ainsi, le thread [ThreadDaoMajEnfants] pourra-t-il tre rcrit comme suit :
1. 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. package istia.st.mvc.personnes.tests; ... public class ThreadDaoMajEnfants extends Thread { ... // coeur du thread public void run() { ... while (!fini) { // on rcupre une copie de la personne d'idPersonne Personne personne = dao.getOne(idPersonne); nbEnfants = personne.getNbEnfants(); ... // attente termine - on essaie de valider la copie // entre-temps d'autres threads ont pu modifier l'original int codeErreur = 0; try { // incrmente de 1 le nbre d'enfants de cette copie personne.setNbEnfants(nbEnfants + 1); // on essaie de modifier l'original dao.saveOne(personne); // on est pass - l'original a t modifi fini = true; } catch (DaoException ex) { // on rcupre le code erreur codeErreur = ex.getCode(); // si une erreur d'ID ou de version 2 ou un deadlock 4, on // ressaie la mise jour switch (codeErreur) { case 2: suivi("version corrompue ou personne inexistante"); break; case 4: suivi("conflit de mise jour"); break; default: // exception non gre - on laisse remonter throw ex; } } } // suivi suivi("a termin et pass le nombre d'enfants " + (nbEnfants + 1)); } ... }

lignes 34-36 : l'exception de type [DaoException] de code 4 est intercepte. Le thread [ThreadDaoMajEnfants] va tre forc de recommencer la procdure de mise jour son dbut (ligne 10)

Notre couche [dao] doit donc tre capable de reconnatre une exception de type " conflit de mise jour ". Celle-ci est mise par un pilote JDBC et lui est spcifique. Cette exception doit tre gre dans la mthode [updatePersonne] de la classe [DaoImplCommon] :
1. // modifier une personne 2. protected void updatePersonne(Personne personne) { 3. // on attend 10 ms - pour les tests mettre true au lieu de false 4. if (true) 5. wait(10); 6. // modification 7. int n = getSqlMapClientTemplate() 8. .update("Personne.updateOne", personne); 9. if (n == 0) 10. throw new DaoException("La personne d'Id [" + personne.getId() 11. + "] n'existe pas ou bien a t modifie", 2); 12. }

Les lignes 7-11 doivent tre entoures par un try / catch. Pour le SGBD Firebird, il nous faut vrifier que l'exception qui a caus l'chec de la mise jour est de type [org.firebirdsql.gds.GDSException] et a comme code d'erreur 335544336. Si on met
Les bases du dveloppement web MVC en Java, par l'exemple

216/264

ce type de test dans [DaoImplCommon], on va lier cette classe au SGBD Firebird, ce qui n'est videmment pas souhaitable. Si on veut garder un caractre gnraliste la classe [DaoImplCommon], il nous faut la driver et grer l'exception dans une classe spcifique Firebird. C'est ce que nous faisons maintenant.

17.4.2

La classe [DaoImplFirebird]

Son code est le suivant :


1. 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. package istia.st.mvc.personnes.dao; import istia.st.mvc.personnes.entites.Personne; public class DaoImplFirebird extends DaoImplCommon { // modifier une personne protected void updatePersonne(Personne personne) { // on attend 10 ms - pour les tests mettre true au lieu de false if (true) wait(10); // modification try { // on modifie la personne qui a la bonne version int n = getSqlMapClientTemplate().update("Personne.updateOne", personne); if (n == 0) throw new DaoException("La personne d'Id [" + personne.getId() + "] n'existe pas ou bien a t modifie", 2); } catch (org.springframework.jdbc.UncategorizedSQLException ex) { if (ex.getSQLException().getCause().getClass().isAssignableFrom( org.firebirdsql.jdbc.FBSQLException.class)) { org.firebirdsql.jdbc.FBSQLException cause = (org.firebirdsql.jdbc.FBSQLException) ex .getSQLException().getCause(); if (cause.getErrorCode() == 335544336) { throw new DaoException( "Conflit d'accs au mme enregistrement", 4); } } else { throw ex; } } } // attente private void wait(int N) { // on attend N ms try { Thread.sleep(N); } catch (InterruptedException e) { // on affiche la trace de l'exception e.printStackTrace(); return; } } }

ligne 5 : la classe [DaoImplFirebird] drive de [DaoImplCommon], la classe que nous venons dtudier. Elle redfinit, lignes 8-33, la mthode [updatePersonne] qui nous pose problme. lignes 20 : nous interceptons l'exception Spring de type [UncategorizedSQLException] lignes 21-22 : nous vrifions que l'exception sous-jacente de type [SQLException] et lance par la couche [iBATIS] a pour cause une exception de type [org.firebirdsql.jdbc.FBSQLException] ligne 25 : on vrifie de plus que le code erreur de cette exception Firebird est 335544336, le code d'erreur du " deadlock ". lignes 26-27 : si toutes ces conditions sont runies, une [DaoException] de code 4 est lance. lignes 36-44 : la mthode [wait] permet darrter le thread courant de N millisecondes. Elle na dutilit que pour les tests.

Nous sommes prts pour les tests de la nouvelle couche [dao].

17.4.3

Tests de l'implmentation [DaoImplFirebird]

Le fichier de configuration des tests [spring-config-test-dao-firebird.xml] est modifi pour utiliser l'implmentation [DaoImplFirebird] :
1. 2. <?xml version="1.0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">

Les bases du dveloppement web MVC en Java, par l'exemple

217/264

3. <beans> 4. <!-- la source de donnees DBCP --> 5. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 6. destroy-method="close"> 7. <property name="driverClassName"> 8. <value>org.firebirdsql.jdbc.FBDriver</value> 9. </property> 10. <!-- attention : ne pas laisser d'espaces entre les deux balises <value> --> 11. <property name="url"> 12. <value>jdbc:firebirdsql:localhost/3050:C:/data/2005-2006/eclipse/dvp-eclipse-tomcat/mvcpersonnes-03/database/dbpersonnes.gdb</value> 13. </property> 14. <property name="username"> 15. <value>sysdba</value> 16. </property> 17. <property name="password"> 18. <value>masterkey</value> 19. </property> 20. </bean> 21. <!-- SqlMapCllient --> 22. <bean id="sqlMapClient" 23. class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 24. <property name="dataSource"> 25. <ref local="dataSource"/> 26. </property> 27. <property name="configLocation"> 28. <value>classpath:sql-map-config-firebird.xml</value> 29. </property> 30. </bean> 31. <!-- la classes d'acc la couche [dao] --> 32. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplFirebird"> 33. <property name="sqlMapClient"> 34. <ref local="sqlMapClient"/> 35. </property> 36. </bean> 37. </beans>

ligne 32 : la nouvelle implmentation [DaoImplFirebird] de la couche [dao].

Les rsultats du test [test4] qui avait chou prcdemment sont les suivants :

[test4] a t russi. Les dernires lignes de logs cran sont les suivantes :
1. 2. 3. 4. 5. 6. 7. 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 dbut attente fin attente a termin et pass le nombre d'enfants 100

La dernire ligne indique que cest le thread n 36 qui a termin le dernier. La ligne 3 montre un conflit de version qui a forc le thread n 36 reprendre sa procdure de mise jour de la personne (ligne 4). Dautres logs montrent des conflits daccs lors des mises jour :
1. 2. 3. 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 dun conflit de mise jour : lorsque la commande SQL [update] a t mise sur la table [PERSONNES], la ligne quil fallait mettre jour tait verrouille par un autre thread. Ce conflit d'accs va obliger le thread n 75 retenter sa mise jour. Pour terminer avec [test4] on remarquera une diffrence notable avec les rsultats du mme test dans la version 1 o il avait chou cause de problmes de synchronisation. Les mthodes de la couche [dao] de la version 1 ntant pas synchronises, des conflits daccs apparaissaient. Ici, nous navons pas eu besoin de synchroniser la couche [dao]. Nous avons simplement gr les conflits daccs signals par Firebird.
Les bases du dveloppement web MVC en Java, par l'exemple

218/264

Excutons maintenant la totalit du test JUnit de la couche [dao] :

Il semble donc quon ait une couche [dao] valide. Pour la dclarer valide avec une forte probabilit, il nous faudrait faire davantage de tests. Nanmoins, nous la considrerons comme oprationnelle.

17.5
17.5.1

La couche [service]
Les composantes de la couche [service]

La couche [service] est constitue des classes et interfaces suivantes :

[IService] est linterface prsente par la couche [service] [ServiceImpl] est une implmentation de celle-ci

Linterface [IService] est 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. 25. package istia.st.mvc.personnes.service; import istia.st.mvc.personnes.entites.Personne; import java.util.Collection; public interface IService { // liste de toutes les personnes Collection getAll(); // obtenir une personne particulire Personne getOne(int id); // ajouter/modifier une personne void saveOne(Personne personne); // supprimer une personne void deleteOne(int id); // sauvegarder plusieurs personnes void saveMany(Personne[] personnes); // supprimer plusieurs personnes void deleteMany(int ids[]);

linterface a les mmes quatre mthodes que dans la version 1 mais elle en a deux de plus : saveMany : permet de sauvegarder plusieurs personnes en mme temps de faon atomique. Soit elles sont toutes sauvegardes, soit aucune ne lest. deleteMany : permet de supprimer plusieurs personnes en mme temps de faon atomique. Soit elles sont toutes supprimes, soit aucune ne lest. Ces deux mthodes ne seront pas utilises par lapplication web. Nous les avons rajoutes pour illustrer la notion de transaction sur une base de donnes. Les deux mthodes devront en effet tre excutes au sein dune transaction pour obtenir latomicit dsire.

La classe [ServiceImpl] implmentant cette interface sera la suivante :


Les bases du dveloppement web MVC en Java, par l'exemple

219/264

1. 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. 49. 50. 51. 52. 53. 54. 55.

package istia.st.mvc.personnes.service; import istia.st.mvc.personnes.entites.Personne; import istia.st.mvc.personnes.dao.IDao; import java.util.Collection; public class ServiceImpl implements IService { // la couche [dao] private IDao dao; public IDao getDao() { return dao; } public void setDao(IDao dao) { this.dao = dao; } // liste des personnes public Collection getAll() { return dao.getAll(); } // obtenir une personne en particulier public Personne getOne(int id) { return dao.getOne(id); } // ajouter ou modifier une personne public void saveOne(Personne personne) { dao.saveOne(personne); } // suppression d'une personne public void deleteOne(int id) { dao.deleteOne(id); } // sauvegarder une collection de personnes public void saveMany(Personne[] personnes) { // on boucle sur le tableau des personnes for (int i = 0; i < personnes.length; i++) { dao.saveOne(personnes[i]); } } // supprimer une collection de personnes public void deleteMany(int[] ids) { // ids : les id des personnes supprimer for (int i = 0; i < ids.length; i++) { dao.deleteOne(ids[i]); } }

les mthodes [getAll, getOne, insertOne, saveOne] font appel aux mthodes de la couche [dao] de mme nom. lignes 42-47 : la mthode [saveMany] sauvegarde, une par une, les personnes du tableau pass en paramtre. lignes 50-55 : la mthode [deleteMany] supprime, une par une, les personnes dont on lui a pass le tableau des id en paramtre

Nous avons dit que les mthodes [saveMany] et [deleteMany] devaient se faire au sein dune transaction pour assurer laspect tout ou rien de ces mthodes. Nous pouvons constater que le code ci-dessus ignore totalement cette notion de transaction. Celle-ci napparatra que dans le fichier de configuration de la couche [service].

17.5.2

Configuration de la couche [service]

Ci-dessus, ligne 11, on voit que limplmentation [ServiceImpl] dtient une rfrence sur la couche [dao]. Celle-ci, comme dans la version 1, sera initialise par Spring au moment de linstanciation de la couche [service - ServiceImpl]. Le fichier de configuration qui permettra linstanciation de la couche [service] sera le suivant :
1. <?xml version="1.0" encoding="ISO_8859-1"?> 2. <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> 3. <beans> 4. <!-- la source de donnees DBCP --> 5. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 6. destroy-method="close"> 7. <property name="driverClassName"> 8. <value>org.firebirdsql.jdbc.FBDriver</value> Les bases du dveloppement web MVC en Java, par l'exemple

220/264

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.

</property> <property name="url"> <!-- 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.gdb</value> </property> <property name="username"> <value>sysdba</value> </property> <property name="password"> <value>masterkey</value> </property> </bean> <!-- SqlMapCllient --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-firebird.xml</value> </property> </bean> <!-- la classes d'accs la couche [dao] --> <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplFirebird"> <property name="sqlMapClient"> <ref local="sqlMapClient"/> </property> </bean> <!-- gestionnaire de transactions --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <!-- la classes d'accs la couche [service] --> <bean id="service" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <bean class="istia.st.mvc.personnes.service.ServiceImpl"> <property name="dao"> <ref local="dao"/> </property> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_SUPPORTS,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]. Cette configuration a t explique lors de ltude de la couche [dao] au paragraphe 17.3.2 page 200. lignes 38-64 : configurent la couche [service]

Ligne 46, on peut voir que limplmentation de la couche [service] est faite par le type [TransactionProxyFactoryBean]. On sattendait trouver le type [ServiceImpl]. [TransactionProxyFactoryBean] est un type prdfini de Spring. Comment se peut-il quun type prdfini puisse implmenter linterface [IService] qui elle, est spcifique notre application ? Dcouvrons tout dabord la classe [TransactionProxyFactoryBean] :

Les bases du dveloppement web MVC en Java, par l'exemple

221/264

Nous voyons quelle implmente linterface [FactoryBean]. Nous avons dj rencontr cette interface. Nous savons que lorsquune application demande Spring, une instance dun type implmentant [FactoryBean], Spring rend non pas une instance [I] de ce type, mais lobjet rendu par la mthode [I].getObject() :

Dans notre cas, la couche [service] va tre implmente par lobjet rendu par [TransactionProxyFactoryBean].getObject(). Quelle est la nature de cet objet ? Nous nallons pas rentrer dans les dtails car ils sont complexes. Ils relvent de ce quon appelle Spring AOP (Aspect Oriented Programming). Nous allons tenter dclaircir les choses avec de simples schmas. AOP permet la chose suivante :

on a deux classes C1 et C2, C1 utilisant l'interface [I2] prsente par C2 :

[C1]

[C2]

grce AOP, on peut placer, de faon transparente pour les deux classes, un intercepteur entre les classes C1 et C2 :

[C1]

1 4

[intercepteur]

2 [C2]

La classe [C1] a t compile pour travailler avec l'interface [I2] que [C2] implmente. Au moment de lexcution, AOP vient placer la classe [intercepteur] entre [C1] et [C2]. Pour que cela soit possible, il faut bien sr que la classe [intercepteur] prsente [C1] la mme interface [I2] que [C2]. A quoi cela peut-il servir ? La documentation Spring donne quelques exemples. On peut vouloir faire, par exemple, des logs l'occasion des appels une mthode M particulire de [C2], pour faire un audit de cette mthode. Dans [intercepteur], on crira alors une mthode [M] qui fait ces logs. Lappel de [C1] [C2].M va se passer ainsi (cf schma ci-dessus) : 1. 2. 3. 4. [C1] appelle la mthode M de [C2]. Cest en fait la mthode M de [intercepteur] qui sera appele. Cela est possible si [C1] sadresse une interface [I2] plutt qu une implmentation particulire de [I2]. Il suffit alors que [intercepteur] implmente [I2]. la mthode M de [intercepteur] fait les logs et appelle la mthode M de [C2] vise initialement par [C1]. la mthode M de [C2] sexcute et rend son rsultat la mthode M de [intercepteur] qui peut ventuellement ajouter quelque chose ce qui a t fait en 2. la mthode M de [intercepteur] rend un rsultat la mthode appelante de [C1]

On voit que la mthode M de [intercepteur] peut faire quelque chose avant et aprs lappel de la mthode M de [C2]. Vis vis de [C1], elle enrichit donc la mthode M de [C2]. On peut donc voir la technologie AOP comme une faon denrichir linterface prsente par une classe. Comment ce concept sapplique-t-il notre couche [service] ? Si on implmente la couche [service] directement avec une instance [ServiceImpl], notre application web aura larchitecture suivante :

[web]

[service] [ServiceImpl]

[dao] [DaoImplCommon]

Si on implmente la couche [service] avec une instance [TransactionProxyFactoryBean], on aura larchitecture suivante :

Les bases du dveloppement web MVC en Java, par l'exemple

222/264

[web]

1 7

[proxy 2 transactionnel]

[service] 3 [ServiceImpl]

[dao] [DaoImplCommon]

On peut dire que la couche [service] est instancie avec deux objets :

lobjet que nous appelons ci-dessus [proxy transactionnel] et qui est en fait lobjet rendu par la mthode [getObject] de [TransactionProxyFactoryBean]. Cest cet objet qui fera linterface de la couche [service] avec la couche [web]. Il implmente par construction linterface [IService]. une instance [ServiceImpl] qui elle aussi implmente linterface [IService]. Elle seule sait comment travailler avec la couche [dao], aussi est-elle ncessaire.

Imaginons que la couche [web] appelle la mthode [saveMany] de linterface [IService]. Nous savons que fonctionnellement, les ajouts / mises jour faits par cette mthode doivent l'tre dans une transaction. Soit ils russissent tous, soit aucun nest fait. Nous avons prsent la mthode [saveMany] de la classe [ServiceImpl] et nous avons point le fait quelle navait pas la notion de transaction. La mthode [saveMany] du [proxy transactionnel] va enrichir la mthode [saveMany] de la classe [ServiceImpl] avec cette notion de transaction. Suivons le schma ci-dessus : 1. 2. 3. 4. 5. 6. la couche [web] appelle la mthode [saveMany] de linterface [IService]. la mthode [saveMany] de [proxy transactionnel] est excute. Elle commence une transaction. Il faut quelle ait les informations suffisantes pour le faire, notamment un objet [DataSource] pour obtenir une connexion au SGBD. Puis elle fait appel la mthode [saveMany] de [ServiceImpl]. celle-ci sexcute. Elle fait appel de faon rpte la couche [dao] pour excuter les insertions ou les mises jour. Les ordres SQL excuts cette occasion le sont dans la transaction commence en 2. supposons quune de ces oprations choue. La couche [dao] va laisser remonter une exception vers la couche [service], en loccurrence la mthode [saveMany] de linstance [ServiceImpl]. celle-ci ne fait rien et laisse remonter lexception jusqu la mthode [saveMany] de [proxy transactionnel]. rception de lexception, la mthode [saveMany] de [proxy transactionnel] qui est propritaire de la transaction fait un [rollback] de celle-ci pour annuler la totalit des mises jour, puis laisse remonter lexception jusqu la couche [web] qui sera charge de la grer.

A ltape 4, nous avons suppos quune des insertions ou des mises jour chouait. Si ce nest pas le cas, en [5] aucune exception ne remonte. Idem en [6]. Dans ce cas, la mthode [saveMany] de [proxy transactionnel] fait un [commit] de la transaction pour valider la totalit des mises jour. Nous avons maintenant une ide plus prcise de larchitecture mise en place par le bean [TransactionProxyFactoryBean]. Revenons sur la configuration de celui-ci :
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. <!-- gestionnaire de transactions --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref local="dataSource"/> </property> </bean> <!-- la classes d'accs la couche [service] --> <bean id="service" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager"> <ref local="transactionManager"/> </property> <property name="target"> <bean class="istia.st.mvc.personnes.service.ServiceImpl"> <property name="dao"> <ref local="dao"/> </property> </bean> </property> <property name="transactionAttributes"> <props> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> <prop key="save*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>

Suivons cette configuration la lumire de larchitecture qui est configure :

Les bases du dveloppement web MVC en Java, par l'exemple

223/264

[web]

1 7

[proxy 2 transactionnel]

[service] 3 [ServiceImpl]

[dao] [DaoImplCommon]

[proxy transactionnel] va grer les transactions. Spring offre plusieurs stratgies de gestion de celles-ci. [proxy transactionnel] a besoin dune rfrence sur le gestionnaire de transactions choisi. lignes 11 13 : dfinissent lattribut [transactionManager] du bean [TransactionProxyFactoryBean] avec une rfrence sur un gestionnaire de transactions. Celui-ci est dfini lignes 2 7. lignes 2-7 : le gestionnaire de transactions est de type [DataSourceTransactionManager] :

[DataSourceTransactionManager] est un gestionnaire de transactions adapt aux SGBD accds via un objet [DataSource]. Il ne sait grer que les transactions sur un unique SGBD. Il ne sait pas grer des transactions distribues sur plusieurs SGBD. Ici, nous navons quun seul SGBD. Aussi ce gestionnaire de transactions convient-il. Lorsque [proxy transactionnel] va dmarrer une transaction, il va le faire sur une connexion attache au thread. Cest cette connexion qui sera utilise dans toutes les couches qui mnent la base de donnes : [ServiceImpl, DaoImplCommon, SqlMapClientTemplate, JDBC]. La classe [DataSourceTransactionManager] a besoin de connatre la source de donnes auprs de laquelle elle doit demander une connexion pour lattacher au thread. Celle-ci est dfinie lignes 4-6 : cest la mme source de donnes que celle utilise par la couche [dao] (cf paragraphe 17.5.2, page 220).

lignes 14-19 : lattribut " target " indique la classe qui doit tre intercepte, ici la classe [ServiceImpl]. Cette information est ncessaire pour deux raisons : la classe [ServiceImpl] doit tre instancie puisque cest elle qui assure le dialogue avec la couche [dao] [TransactionProxyFactoryBean] doit gnrer un proxy qui prsente la couche [web] la mme interface que [ServiceImpl]. lignes 21-27 : indiquent quelles mthodes de [ServiceImpl], le proxy doit intercepter. Lattribut [transactionAttributes], ligne 21, indique quelles mthodes de [ServiceImpl] ncessitent une transaction et quels sont les attributs de celle-ci : ligne 23 : les mthodes dont le nom commencent par get [getOne, getAll] sexcutent dans une transaction dattribut [PROPAGATION_REQUIRED,readOnly] : PROPAGATION_REQUIRED : la mthode sexcute dans une transaction s'il y en a dj une attache au thread, sinon une nouvelle est cre et la mthode sexcute dedans. readOnly : transaction en lecture seule Ici les mthodes [getOne] et [getAll] de [ServiceImpl] sexcuteront dans une transaction alors qu'en fait ce n'est pas ncessaire. Il s'agit chaque fois d'une opration constitue d'un unique ordre SELECT. On ne voit pas l'utilit de mettre ce SELECT dans une transaction. ligne 24 : les mthodes dont le nom commencent par save, [saveOne, saveMany] sexcutent dans une transaction dattribut [PROPAGATION_REQUIRED]. ligne 25 : les mthodes [deleteOne] et [deleteMany] de [ServiceImpl] sont configures de faon identique aux mthodes [saveOne, saveMany].

Dans notre couche [service], seules les mthodes [saveMany] et [deleteMany] ont besoin de sexcuter dans une transaction. La configuration aurait pu tre rduite aux lignes suivantes :
1. <property name="transactionAttributes"> 2. <props> 3. <prop key="saveMany">PROPAGATION_REQUIRED</prop> 4. <prop key="deleteMany">PROPAGATION_REQUIRED</prop> 5. </props> 6. </property>

Les bases du dveloppement web MVC en Java, par l'exemple

224/264

17.6

Tests de la couche [service]

Maintenant que nous avons crit et configur la couche [service], nous nous proposons de la tester avec des tests JUnit :

Le fichier de configuration [spring-config-test-service-firebird.xml] de la couche [service] est celui qui a t dcrit au paragraphe 17.5.2, page 220. Le test JUnit [TestServiceFirebird] 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. 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. package istia.st.mvc.personnes.tests; ... public class TestServiceFirebird extends TestCase { // couche [service] private IService service; public IService getService() { return service; } public void setService(IService service) { this.service = service; } // setup public void setUp() { service = (IService) (new XmlBeanFactory(new ClassPathResource( "spring-config-test-service-firebird.xml"))).getBean("service"); } // liste des personnes private void doListe(Collection personnes) { ... } // test1 public void test1() throws ParseException { ... } // modification-suppression d'un lment inexistant public void test2() throws ParseException { ... } // gestion des versions de personne public void test3() throws ParseException, InterruptedException { ... } // optimistic locking - accs multi-threads public void test4() throws Exception { ... } // tests de validit de saveOne public void test5() throws ParseException { ... } // insertions multi-threads public void test6() throws ParseException, InterruptedException{ ... } // tests de la mthode deleteMany public void test7() throws ParseException { // liste actuelle Collection personnes = service.getAll();

Les bases du dveloppement web MVC en Java, par l'exemple

225/264

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

226/264

150. 151. 152. 153. 154. 155. } 156. 157.}

int nbPersonnes5 = personnes.size(); // vrification - on doit tre revenu au point de dpart assertEquals(nbPersonnes5, nbPersonnes1); // affichage doListe(personnes);

lignes 19-22 : le programme teste des couches [dao] et [service] configures par le fichier [spring-config-test-servicefirebird.xml], celui tudi dans la section prcdente. les tests [test1] [test6] sont identiques dans leur esprit leurs homologues de mme nom dans la classe de test [TestDaoFirebird] de la couche [dao]. La seule diffrence est que par configuration, les mthodes [saveOne] et [deleteOne] sexcutent dsormais dans une transaction. la mthode [test7] a pour but de tester les mthodes [saveMany] et [deleteMany]. On veut vrifier quelles sexcutent bien dans une transaction. Commentons le code de cette mthode : lignes 62-63 : on compte le nombre de personnes [nbPersonnes1] actuellement dans la liste lignes 67-72 : on cre trois personnes lignes 73-83 : ces trois personnes sont sauvegardes par la mthode [saveMany] ligne 77. Les deux premires personnes p1 et p2 ayant un id gal -1 vont tre ajoutes la table [PERSONNES]. La personne p3 a elle un id gal -2. Il ne sagit donc pas dune insertion mais dune mise jour. Celle-ci va chouer car il ny a aucune personne avec un id gal 2 dans la table [PERSONNES]. La couche [dao] va donc lancer une exception qui va remonter jusqu la couche [service]. Lexistence de cette exception est teste ligne 83. cause de lexception prcdente, la couche [service] devrait faire un [rollback] de lensemble des ordres SQL mis pendant lexcution de la mthode [saveMany], ceci parce que cette mthode sexcute dans une transaction. Lignes 86-87, on vrifie que le nombre de personnes de la liste na pas boug et que donc les insertions de p1 et p2 nont pas eu lieu. lignes 88-103 : on ajoute les seules personnes p1 et p2 et on vrifie quensuite on a deux personnes de plus dans la liste. lignes 106-114 : on supprime un groupe de personnes constitu des personnes p1 et p2 quon vient dajouter et dune personne inexistante (id= -1). La mthode [deleteMany] est utilise pour cela, ligne 108. Cette mthode va chouer car il ny a aucune personne avec un id gal 1 dans la table [PERSONNES]. La couche [dao] va donc lancer une exception qui va remonter jusqu la couche [service]. Lexistence de cette exception est teste ligne 114. cause de lexception prcdente, la couche [service] devrait faire un [rollback] de lensemble des ordres SQL mis pendant lexcution de la mthode [deleteMany], ceci parce que cette mthode sexcute dans une transaction. Lignes 116-117, on vrifie que le nombre de personnes de la liste na pas boug et que donc les suppressions de p1 et p2 nont pas eu lieu. ligne 122 : on supprime un groupe constitu des seules personnes p1 et p2. Cela devrait russir. Le reste de la mthode vrifie que cest bien le cas.

Lexcution des tests donne les rsultats suivants :

Les sept tests ont t russis. Nous considrerons notre couche [service] comme oprationnelle.

17.7

La couche [web]

Rappelons larchitecture gnrale de lapplication web construire :

Les bases du dveloppement web MVC en Java, par l'exemple

227/264

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Spring IoC

Nous venons de construire les couches [dao] et [service] permettant de travailler avec une base de donnes Firebird. Nous avons crit une version 1 de cette application o les couches [dao] et [service] travaillaient avec une liste de personnes en mmoire. La couche [web] crite cette occasion reste valide. En effet, elle sadressait une couche [service] implmentant linterface [IService]. La nouvelle couche [service] implmentant cette mme interface, la couche [web] na pas tre modifie. Dans le prcdent article, la version 1 de lapplication avait t teste avec le projet Eclipse [mvc-personnes-02B] o les couches [web, service, dao, entites] avaient t mises dans des archives .jar :

Le dossier [src] tait vide. Les classes des couches taient dans les archives [personnes-*.jar ] :

Les bases du dveloppement web MVC en Java, par l'exemple

228/264

- couche [entites] - couche [dao]

- couche [service]

- couche [web]

Pour tester la version 2, sous Eclipse nous dupliquons le dossier Eclipse [mvc-personnes-02B] en [mvc-personnes-03B] (copy / paste) :

Dans le projet [mvc-personnes-03], nous exportons [File / Export / Jar file] les couches [dao] et [service] respectivement dans les archives [personnes-dao.jar] et [personnes-service.jar] du dossier [dist] du projet :

Nous copions ces deux fichiers, puis sous Eclipse nous les collons dans le dossier [WEB-INF/lib] du projet [mvc-personnes03B] o ils vont remplacer les archives de mme nom de la version prcdente.

Nous copions / collons galement les archives [commons-dbcp-*.jar, commons-pool-*.jar, firebirdsql-full.jar, ibatis-common2.jar, ibatis-sqlmap-2.jar] du dossier [lib] du projet [mvc-personnes-03] dans le dossier [WEB-INF/lib] du projet [mvcpersonnes-03B]. Ces archives sont ncessaires aux nouvelles couches [dao] et [service]. Ceci fait, nous incluons les nouvelles archives dans le Classpath du projet : [clic droit sur projet -> Properties -> Java Build Path -> Add Jars].
Les bases du dveloppement web MVC en Java, par l'exemple

229/264

Le dossier [src] contient les fichiers de configuration des couches [dao] et [service] :

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

ligne 12 : lurl de la base de donnes Firebird. Nous continuons utiliser la base qui a servi aux tests des couches [dao] et [service] Les bases du dveloppement web MVC en Java, par l'exemple 230/264

Nous dployons le projet web [mvc-personnes-03B] au sein de Tomcat :

Nous sommes prts pour les tests. Le SGBD Firebird est lanc. Le contenu de la table [PERSONNES] est alors le suivant :

Tomcat est lanc son tour. Avec un navigateur, nous demandons lurl [http://localhost:8080/mvc-personnes-03B] :

Nous ajoutons une nouvelle personne avec le lien [Ajout] :

Nous vrifions lajout dans la base de donnes :

Les bases du dveloppement web MVC en Java, par l'exemple

231/264

Le lecteur est invit faire dautres tests [modification, suppression]. Faisons maintenant le test de conflits de version qui avait t fait dans la version 1. [Firefox] sera le navigateur de lutilisateur U1. Celui-ci demande lurl [http://localhost:8080/mvc-personnes-03B] :

[IE] sera le navigateur de lutilisateur U2. Celui-ci demande la mme Url :

Lutilisateur U1 entre en modification de la personne [Perrichon] :

Les bases du dveloppement web MVC en Java, par l'exemple

232/264

Lutilisateur U2 fait de mme :

Lutilisateur U1 fait des modifications et valide :

rponse du serveur

validation Lutilisateur U2 fait de mme :

Les bases du dveloppement web MVC en Java, par l'exemple

233/264

validation

le conflit de version a t dtect Lutilisateur U2 revient la liste des personnes avec le lien [Annuler] du formulaire :

Il trouve la personne [Perrichon] telle que U1 la modifie (nom pass en majuscules). Et la base de donnes dans tout a ? Regardons :

La personne n 899 a bien son nom en majuscules suite la modification faite par U1.

17.8

Conclusion

Rappelons ce que nous voulions faire. Nous avions une application web avec larchitecture 3tier suivante : o les couches [dao] et [service] travaillaient avec une liste de donnes en mmoire qui tait donc perdue lorsque le serveur web tait arrt. Ctait la version 1. Dans la version 2, les couches [service] et [dao] ont t rcrites pour que la liste de personnes soit dans une table de base de donnes. Elle est donc dsormais persistante. On se propose maintenant de voir limpact qua sur notre application le changement de SGBD. Pour cela, nous allons construire trois nouvelles versions de notre application web :
Les bases du dveloppement web MVC en Java, par l'exemple

234/264

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

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] implmente des fonctionnalits de la couche [dao] lies au SGBD Firebird. Si ce besoin persiste, elle sera remplace respectivement par les classes [DaoImplPostgres], [DaoImplMySQL] et [DaoImplSqlExpress]. le fichier de mapping [personnes-firebird.xml] diBATIS pour le SGBD Firebird va tre remplac respectivement par les fichiers de mapping [personnes-postgres.xml], [personnes-mysql.xml] et [personnes-sqlexpress.xml]. la configuration de lobjet [DataSource] de la couche [dao] est spcifique un SGBD. Elle va donc changer chaque version. le pilote JDBC du SGBD change galement chaque version

En-dehors de ces points, tout reste lidentique. Dans la suite, nous dcrivons ces nouvelles versions en ne nous attachant quaux seules nouveauts amenes par chacune d'elles.

18 Application web MVC dans une architecture 3tier Exemple 4, Postgres


18.1 La base de donnes Postgres

Dans cette version, nous allons installer la liste des personnes dans une table de base de donnes Postgres 8.x [http://www.postgres.org]. Dans ce qui suit, les copies dcran proviennent du client EMS PostgreSQL Manager Lite [http://www.sqlmanager.net/fr/products/postgresql/manager], un client dadministration gratuit du SGBD Postgres. La base de donnes sappelle [dbpersonnes]. Elle contient une table [PERSONNES] :

Les bases du dveloppement web MVC en Java, par l'exemple

235/264

La table [PERSONNES] contiendra la liste des personnes gre par lapplication web. Elle a t construite avec les ordres SQL suivants :
1. CREATE TABLE "public"."PERSONNES" ( 2. "ID" INTEGER NOT NULL, 3. "VERSION" INTEGER NOT NULL, 4. "NOM" VARCHAR(30) NOT NULL, 5. "PRENOM" VARCHAR(30) NOT NULL, 6. "DATENAISSANCE" DATE NOT NULL, 7. "MARIE" BOOLEAN NOT NULL, 8. "NBENFANTS" INTEGER NOT NULL, 9. CONSTRAINT "PERSONNES_pkey" PRIMARY KEY("ID"), 10. CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK ("NBENFANTS" >= 0), 11. CONSTRAINT "PERSONNES_chk_NOM" CHECK (("NOM")::text <> ''::text), 12. CONSTRAINT "PERSONNES_chk_PRENOM" CHECK (("PRENOM")::text <> ''::text) 13. ) WITH OIDS;

Nous ninsistons pas sur cette table qui est analogue la table [PERSONNES] de type Firebird tudie prcdemment. On notera cependant que les noms des colonnes et des tables sont entre guillemets. Par ailleurs, ces noms sont sensibles la casse. Il est possible que ce mode de fonctionnement de Postgres 8.x soit configurable. Je nai pas creus la question. La table [PERSONNES] pourrait avoir le contenu suivant :

Outre la table [PERSONNES], la base [dbpersonnes] a, un objet appel squence et nomm [SEQ_ID]. Ce gnrateur dlivre des nombres entiers successifs que nous utiliserons pour donner sa valeur la cl primaire [ID] de la classe [PERSONNES]. Prenons un exemple pour illustrer son fonctionnement :

Les bases du dveloppement web MVC en Java, par l'exemple

236/264

- la prochaine valeur du gnrateur est 38

- double-cliquer sur [SEQ_ID]

- mettons lordre SQL ci-dessus (F12) ->

- la valeur obtenue est la valeur [Next value] de la squence [SEQ_ID]

On peut constater que la valeur [Next value] de la squence [SEQ_ID] a chang (double-clic dessus + F5 pour rafrachir) :

Lordre SQL
SELECT nextval('"SEQ_ID"')

permet donc davoir la valeur suivante de la squence [SEQ_ID]. Nous lutiliserons dans le fichier [personnes-postgres.xml] qui rassemble les ordres SQL mis sur le SGBD.

18.2

Le projet Eclipse des couches [dao] et [service]

Pour dvelopper les couches [dao] et [service] de notre application avec la base de donnes Postgres 8.x, nous utiliserons le projet Eclipse [spring-mvc-39] suivant :

Le projet est un simple projet Java, pas un projet web Tomcat. Dossier [src]
Les bases du dveloppement web MVC en Java, par l'exemple

237/264

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. Dans ce qui suit, nous dcrivons les fichiers modifis. Dossier [database] Ce dossier contient le script de cration de la base de donnes Postgres des personnes :

--- Definition for function plpgsql_call_handler (OID = 17230) : -... --- Structure for table PERSONNES (OID = 17254) : -CREATE TABLE "PERSONNES" ( "ID" integer NOT NULL, "VERSION" integer NOT NULL, "NOM" varchar(30) NOT NULL, "PRENOM" varchar(30) NOT NULL, "DATENAISSANCE" date NOT NULL, "MARIE" boolean NOT NULL, "NBENFANTS" integer NOT NULL, CONSTRAINT "PERSONNES_chk_NBENFANTS" CHECK (("NBENFANTS" >= 0)), CONSTRAINT "PERSONNES_chk_NOM" CHECK ((("NOM")::text <> ''::text)), CONSTRAINT "PERSONNES_chk_PRENOM" CHECK ((("PRENOM")::text <> ''::text)) ); --- Definition for sequence SEQ_ID (OID = 17261) : -CREATE SEQUENCE "SEQ_ID" INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; --- Data for blobs (OID = 17254) (LIMIT 0,3) -INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (1, 1, 'Major', 'Joachim', '1984-11-13', true, 2); 39. INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (2, 1, 'Humbort', 'Mlanie', '1985-01-12', false, 1); 40. INSERT INTO "PERSONNES" ("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES (3, 1, 'Lemarchand', 'Charles', '1986-01-01', false, 0); 41. -42. -- Definition for index PERSONNES_pkey (OID = 17256) : 43. -44. ALTER TABLE ONLY "PERSONNES" Les bases du dveloppement web MVC en Java, par l'exemple 238/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.

-----

EMS PostgreSQL Manager Lite 3.1.0.1 --------------------------------------Host : localhost Database : dbpersonnes

45. 46. 47. 48. 49. 50. 51. 52. 53.

ADD CONSTRAINT "PERSONNES_pkey" PRIMARY KEY ("ID"); --- Data for sequence public."SEQ_ID" (OID = 17261) -SELECT pg_catalog.setval('"SEQ_ID"', 37, true); --- Comments -COMMENT ON SCHEMA public IS 'Standard public schema';

Dossier [lib] Ce dossier contient les archives ncessaires lapplication :

On notera la prsence du pilote jdbc du SGBD Postgres 8.x. Toutes ces archives font partie du Classpath du projet Eclipse.

18.3

La couche [dao]

La couche [dao] est la suivante :

Nous ne prsentons que ce qui change vis vis de la version [Firebird]. Le fichier de mapping [personne-postgres.xml] est le suivant :
1. <?xml version="1.0" encoding="UTF-8" ?> 2. 3. <!DOCTYPE sqlMap 4. PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 5. "http://www.ibatis.com/dtd/sql-map-2.dtd"> 6. 7. <!-- attention - Postgresql 8 demande l'orthographe exacte des noms de colonnes 8. et des tables ainsi que des guillemets autour de ces noms --> 9. 10. <sqlMap> 11. <!-- alias classe [Personne] --> 12. <typeAlias alias="Personne.classe" 13. type="istia.st.mvc.personnes.entites.Personne"/> 14. <!-- mapping table [PERSONNES] - objet [Personne] --> Les bases du dveloppement web MVC en Java, par l'exemple

239/264

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.

<resultMap id="Personne.map" class="istia.st.mvc.personnes.entites.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> <!-- liste de toutes les personnes --> <select id="Personne.getAll" resultMap="Personne.map" > select "ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM "PERSONNES"</select> <!-- obtenir une personne en particulier --> <select id="Personne.getOne" resultMap="Personne.map" >select "ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS" FROM "PERSONNES" WHERE "ID"=#value#</select> <!-- ajouter une personne --> <insert id="Personne.insertOne" parameterClass="Personne.classe"> <selectKey keyProperty="id"> SELECT nextval('"SEQ_ID"') as value </selectKey> insert into "PERSONNES"("ID", "VERSION", "NOM", "PRENOM", "DATENAISSANCE", "MARIE", "NBENFANTS") VALUES(#id#, #version#, #nom#, #prenom#, #dateNaissance#, #marie#, #nbEnfants#) </insert> <!-- mettre jour une personne --> <update id="Personne.updateOne" parameterClass="Personne.classe"> update "PERSONNES" set "VERSION"=#version#+1, "NOM"=#nom#, "PRENOM"=#prenom#, "DATENAISSANCE"=#dateNaissance#, "MARIE"=#marie#, "NBENFANTS"=#nbEnfants# WHERE "ID"=#id# and "VERSION"=#version#</update> <!-- supprimer une personne --> <delete id="Personne.deleteOne" parameterClass="int"> delete FROM "PERSONNES" WHERE "ID"=#value# </delete> </sqlMap>

Cest le mme contenu que [personnes-firebird.xml] aux dtails prs suivants :


les noms des colonnes et des tables sont entre guillemets et ces noms sont sensibles la casse lordre SQL " Personne.insertOne " a chang lignes 34-41. La faon de gnrer la cl primaire avec Postgres est diffrente de celle utilise avec Firebird : ligne 36 : lordre SQL [SELECT nextval('"SEQ_ID"')] fournit la cl primaire. La syntaxe [as value] est obligatoire. [value] reprsente la cl obtenue. Cette valeur va tre affecte au champ de lobjet [Personne] dsign par lattribut [keyProperty] (line 35), ici le champ [id]. les ordres SQL de la balise <insert> sont excuts dans lordre o ils sont rencontrs. Donc le SELECT est fait avant lINSERT. Au moment de lopration dinsertion, le champ [id] de lobjet [Personne] aura donc t mis jour par lordre SQL SELECT. lignes 38-40 : insertion de lobjet [Personne]

La classe dimplmentation [DaoImplCommon] de la couche [dao] est celle tudie dans la version [Firebird]. La configuration de la couche [dao] a t adapte au SGBD [Postgres]. Ainsi, le fichier de configuration [spring-config-testdao-postgres.xml] est-il le suivant :
1. <?xml version="1.0" encoding="ISO_8859-1"?> 2. <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> 3. <beans> 4. <!-- la source de donnees DBCP --> 5. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 6. destroy-method="close"> 7. <property name="driverClassName"> 8. <value>org.postgresql.Driver</value> 9. </property> 10. <property name="url"> 11. <value>jdbc:postgresql:dbpersonnes</value> 12. </property> 13. <property name="username"> 14. <value>postgres</value> 15. </property> 16. <property name="password"> 17. <value>postgres</value> 18. </property> 19. </bean> 20. <!-- SqlMapCllient --> 21. <bean id="sqlMapClient" 22. class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 23. <property name="dataSource"> 24. <ref local="dataSource"/> 25. </property> Les bases du dveloppement web MVC en Java, par l'exemple

240/264

26. <property name="configLocation"> 27. <value>classpath:sql-map-config-postgres.xml</value> 28. </property> 29. </bean> 30. <!-- la classes d'acc la couche [dao] --> 31. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> 32. <property name="sqlMapClient"> 33. <ref local="sqlMapClient"/> 34. </property> 35. </bean> 36. </beans>

lignes 5-19 : le bean [dataSource] dsigne maintenant la base [Postgres] [dbpersonnes] dont ladministrateur est [postgres] avec le mot de passe [postgres]. Le lecteur modifiera cette configuration selon son propre environnement. ligne 31 : la classe [DaoImplCommon] est la classe d'implmentation de la couche [dao]

Ces modifications faites, on peut passer aux tests.

18.4

Les tests des couches [dao] et [service]

Les tests des couches [dao] et [service] sont les mmes que pour la version [Firebird]. Lanons le SGBD Postgres puis les tests Eclipse. Les rsultats obtenus sont les suivants :

- couche [dao]

- couche [service]

On constate que les tests ont t passs avec succs avec l'implmentation [DaoImplCommon]. Nous n'aurons pas driver cette classe comme il avait t ncessaire de le faire avec le SGBD [Firebird].

18.5

Tests de lapplication [web]

Pour tester lapplication web avec le SGBD [Postgres], nous construisons un projet Eclipse [mvc-personnes-04B] de faon analogue celle utilise pour construire le projet [mvc-personnes-03B] avec la base Firebird (cf page 227). Cependant, nous n'avons pas recrer les archives [personnes-dao.jar] et [personnes-service.jar]. En effet, nous n'avons modifi aucune classe vis vis du projet [mvc-personnes-03B]. Simplement l'archive [personnes-dao.jar] contient la classe [DaoImplFirebird] qui est devenue inutile.

Les bases du dveloppement web MVC en Java, par l'exemple

241/264

Nous dployons le projet web [mvc-personnes-04B] au sein de Tomcat :

Nous sommes prts pour les tests. Le contenu de la table [PERSONNES] est alors le suivant :

Tomcat est lanc. Avec un navigateur, nous demandons lurl [http://localhost:8080/mvc-personnes-04B] :

Nous ajoutons une nouvelle personne avec le lien [Ajout] :

Les bases du dveloppement web MVC en Java, par l'exemple

242/264

Nous vrifions lajout dans la base de donnes :

Le lecteur est invit faire dautres tests [modification, suppression].

19 Application web MVC dans une architecture 3tier Exemple 5, MySQL


19.1 La base de donnes MySQL

Dans cette version, nous allons installer la liste des personnes dans une table de base de donnes MySQL 4.x. Nous avons utilis le paquetage [Apache MySQL PHP] disponible l'url [http://www.easyphp.org]. Dans ce qui suit, les copies dcran proviennent du client EMS MySQL Manager Lite [http://www.sqlmanager.net/fr/products/mysql/manager], un client dadministration gratuit du SGBD MySQL. La base de donnes sappelle [dbpersonnes]. Elle contient une table [PERSONNES] :

La table [PERSONNES] contiendra la liste des personnes gre par lapplication web. Elle a t construite avec les ordres SQL suivants :
1. CREATE TABLE `personnes` ( 2. `ID` int(11) NOT NULL auto_increment, 3. `VERSION` int(11) NOT NULL default '0', 4. `NOM` varchar(30) NOT NULL default '', 5. `PRENOM` varchar(30) NOT NULL default '', 6. `DATENAISSANCE` date NOT NULL default '0000-00-00', 7. `MARIE` tinyint(4) NOT NULL default '0', 8. `NBENFANTS` int(11) NOT NULL default '0', Les bases du dveloppement web MVC en Java, par l'exemple

243/264

9. PRIMARY KEY (`ID`) 10. ) ENGINE=InnoDB DEFAULT CHARSET=latin1

MySQL 4.x semble plus pauvre que les deux SGBD prcdents. Je n'ai pas pu mettre de contraintes (checks) la table.

ligne 10 : la table doit avoir le type [InnoDB] et non le type [MyISAM] qui ne supporte pas les transactions. ligne 2 : la cl primaire est de type auto_increment. Si on insre une ligne sans valeur pour la colonne ID de la table, MySQL gnrera automatiquement un nombre entier pour cette colonne. Cela va nous viter de gnrer les cls primaires nous-mmes.

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 diffrente de -1 ensuite, cette valeur tant la cl primaire affecte la nouvelle ligne insre dans la table [PERSONNES]. Voyons sur un exemple comment nous allons pouvoir connatre cette valeur.

- on excute l'ordre SELECT ci- on excute l'ordre d'insertion ci-dessus (F12). On notera qu'on ne donne pas de valeur dessus pour connatre la dernire au champ ID -> valeur insre dans le champ ID de la table ->

- le rsultat - vrification Lordre SQL


SELECT LAST_INSERT_ID()

permet de connatre la dernire valeur insre dans le champ ID de la table. Elle est mettre aprs l'insertion. C'est une diffrence avec les SGBD [Firebird] et [Postgres] o on demandait la valeur de la cl primaire de la personne ajoute avant l'insertion. Nous lutiliserons dans le fichier [personnes-mysql.xml] qui rassemble les ordres SQL mis sur la base de donnes.

19.2

Le projet Eclipse des couches [dao] et [service]

Pour dvelopper les couches [dao] et [service] de notre application avec la base de donnes MySQL, nous utiliserons le projet Eclipse [mvc-personnes-05] suivant :

Les bases du dveloppement web MVC en Java, par l'exemple

244/264

Le projet est un simple projet Java, pas un projet web Tomcat. 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. Dans ce qui suit, nous dcrivons ceux qui ont t modifis. Dossier [database] Ce dossier contient le script de cration de la base de donnes MySQL des personnes :

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.

# # # # #

EMS MySQL Manager Lite 3.2.0.1 --------------------------------------Host : localhost Port : 3306 Database : dbpersonnes

SET FOREIGN_KEY_CHECKS=0; CREATE DATABASE `dbpersonnes` CHARACTER SET 'latin1' COLLATE 'latin1_swedish_ci'; USE `dbpersonnes`; # # Structure for the `personnes` table : # CREATE TABLE `personnes` ( `ID` int(11) NOT NULL auto_increment, `VERSION` int(11) NOT NULL default '0', `NOM` varchar(30) NOT NULL default '', `PRENOM` varchar(30) NOT NULL default '', `DATENAISSANCE` date NOT NULL default '0000-00-00', `MARIE` tinyint(4) NOT NULL default '0', `NBENFANTS` int(11) NOT NULL default '0', PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; # # Data for the `personnes` table # (LIMIT 0,500)

Les bases du dveloppement web MVC en Java, par l'exemple

245/264

35. INSERT INTO `personnes` (`ID`, `VERSION`, `NOM`, `PRENOM`, `DATENAISSANCE`, `MARIE`, `NBENFANTS`) VALUES 36. (1,1,'Major','Joachim','1984-01-13',1,2), 37. (2,1,'Humbort','Mlanie','1985-01-12',0,1), 38. (3,1,'Lemarchand','Charles','1986-01-01',0,0); 39. 40. COMMIT;

Dossier [lib] Ce dossier contient les archives ncessaires lapplication :

On notera la prsence du pilote jdbc du SGBD MySQL. Toutes ces archives font partie du Classpath du projet Eclipse.

19.3

La couche [dao]

La couche [dao] est la suivante :

Nous ne prsentons que ce qui change vis vis de la version [Firebird]. Le fichier de mapping [personne-mysql.xml] est le suivant :
1. <?xml version="1.0" encoding="UTF-8" ?> 2. 3. <!DOCTYPE sqlMap 4. PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 5. "http://www.ibatis.com/dtd/sql-map-2.dtd"> 6. 7. <sqlMap> 8. <!-- alias classe [Personne] --> 9. <typeAlias alias="Personne.classe" 10. type="istia.st.mvc.personnes.entites.Personne"/> 11. <!-- mapping table [PERSONNES] - objet [Personne] --> 12. <resultMap id="Personne.map" Les bases du dveloppement web MVC en Java, par l'exemple

246/264

13. class="istia.st.mvc.personnes.entites.Personne"> 14. <result property="id" column="ID" /> 15. <result property="version" column="VERSION" /> 16. <result property="nom" column="NOM"/> 17. <result property="prenom" column="PRENOM"/> 18. <result property="dateNaissance" column="DATENAISSANCE"/> 19. <result property="marie" column="MARIE"/> 20. <result property="nbEnfants" column="NBENFANTS"/> 21. </resultMap> 22. <!-- liste de toutes les personnes --> 23. <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, 24. PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select> 25. <!-- obtenir une personne en particulier --> 26. <select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM, 27. PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#value#</select> 28. <!-- ajouter une personne --> 29. <insert id="Personne.insertOne" parameterClass="Personne.classe"> 30. insert into 31. PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 32. VALUES(#version#, #nom#, #prenom#, #dateNaissance#, #marie#, 33. #nbEnfants#) 34. <selectKey keyProperty="id"> 35. select LAST_INSERT_ID() as value 36. </selectKey> 37. </insert> 38. <!-- mettre jour une personne --> 39. <update id="Personne.updateOne" parameterClass="Personne.classe"> update 40. PERSONNES set VERSION=#version#+1, NOM=#nom#, PRENOM=#prenom#, DATENAISSANCE=#dateNaissance#, 41. MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# and 42. VERSION=#version#</update> 43. <!-- supprimer une personne --> 44. <delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 45. ID=#value# </delete> 46. <!-- obtenir la valeur de la cl primaire [id] de la dernire personne insre --> 47. <select id="Personne.getNextId" resultClass="int">select 48. LAST_INSERT_ID()</select> 49. </sqlMap>

Cest le mme contenu que [personnes-firebird.xml] aux dtails prs suivants :

lordre SQL " Personne.insertOne " a chang lignes 29-37 : l'ordre SQL d'insertion est excut avant l'ordre SELECT qui va permettre de rcuprer la valeur de la cl primaire de la ligne insre l'ordre SQL d'insertion n'a pas de valeur pour la colonne ID de la table [PERSONNES] Cela reflte l'exemple d'insertion que nous avons comment page 244. On notera qu'il y a peut-tre l une source possible de problmes entre threads concurrents. Imaginons deux threads Th1 et Th2 qui font une insertion en mme temps. Il y a au total quatre ordres SQL mettre. Supposons qu'ils soient faits dans l'ordre suivant : 1. insertion I1 de Th1 2. insertion I2 de Th2 3. select S1 de Th1 4. select S2 de Th2 En 3, Th1 rcupre la cl primaire gnre lors de la dernire insertion, donc celle de Th2 et non la sienne. Je ne sais pas si la mthode [insert] d'iBATIS est protge pour ce cas de figure. Nous allons supposer qu'elle le gre proprement. Si ce n'tait pas le cas, il nous faudrait driver la classe dimplmentation [DaoImplCommon] de la couche [dao] en une classe [DaoImplMySQL] o la mthode [insertPersonne] serait synchronise. Ceci ne rsoudrait le problme que pour les threads de notre application. Si ci-dessus, Th1 et Th2 sont des threads de deux applications diffrentes, il faudrait alors rsoudre le problme la fois avec des transactions et un niveau d'tanchit adquat (isolation level) entre transactions. Le niveau [serializable] dans lequel les transactions sont excutes comme si elles s'excutaient squentiellement serait appropri. On notera que ce problme n'existe pas avec les SGBD Firebird et Postgres qui eux font le SELECT avant l'INSERT. Si on a par exemple la squence : 1. 2. 3. 4. select S1 de Th1 select S2 de Th2 insertion I1 de Th1 insertion I2 de Th2

Les bases du dveloppement web MVC en Java, par l'exemple

247/264

Aux tapes 1 et 2, Th1 et Th2 rcuprent des valeurs de cl primaire auprs du mme gnrateur. Cette opration est normalement atomique, et Th1 et Th2 vont rcuprer deux valeurs diffrentes. Si l'opration n'tait pas atomique, et que Th1 et Th2 rcupraient deux valeurs identiques, l'insertion faite en 4 par Th2 chouerait pour cause de doublon de cl primaire. C'est une erreur tout fait rcuprable et Th2 peut retenter l'insertion. On va laisser l'opration " Personne.insertOne " telle qu'elle est actuellement dans le fichier [personnes-mysql.xml] mais le lecteur doit avoir conscience qu'il y a l potentiellement un problme. La classe dimplmentation [DaoImplCommon] de la couche [dao] est celle des deux versions prcdentes. La configuration de la couche [dao] a t adapte au SGBD [MySQL]. Ainsi, le fichier de configuration [spring-config-testdao-mysql.xml] est-il le suivant :
1. <?xml version="1.0" encoding="ISO_8859-1"?> 2. <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> 3. <beans> 4. <!-- la source de donnees DBCP --> 5. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 6. destroy-method="close"> 7. <property name="driverClassName"> 8. <value>com.mysql.jdbc.Driver</value> 9. </property> 10. <property name="url"> 11. <value>jdbc:mysql://localhost/dbpersonnes</value> 12. </property> 13. <property name="username"> 14. <value>root</value> 15. </property> 16. <property name="password"> 17. <value></value> 18. </property> 19. </bean> 20. <!-- SqlMapCllient --> 21. <bean id="sqlMapClient" 22. class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> 23. <property name="dataSource"> 24. <ref local="dataSource"/> 25. </property> 26. <property name="configLocation"> 27. <value>classpath:sql-map-config-mysql.xml</value> 28. </property> 29. </bean> 30. <!-- la classes d'acc la couche [dao] --> 31. <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> 32. <property name="sqlMapClient"> 33. <ref local="sqlMapClient"/> 34. </property> 35. </bean>

36.</beans>

lignes 5-19 : le bean [dataSource] dsigne maintenant la base [MySQL] [dbpersonnes] dont ladministrateur est [root] sans mot de passe. Le lecteur modifiera cette configuration selon son propre environnement. ligne 31 : la classe [DaoImplCommon] est la classe d'implmentation de la couche [dao]

Ces modifications faites, on peut passer aux tests.

19.4

Les tests des couches [dao] et [service]

Les tests des couches [dao] et [service] sont les mmes que pour la version [Firebird]. Les rsultats obtenus sont les suivants :

Les bases du dveloppement web MVC en Java, par l'exemple

248/264

- couche [dao]

- couche [service]

On constate que les tests ont t passs avec succs avec l'implmentation [DaoImplCommon]. Nous n'aurons pas driver cette classe comme il avait t ncessaire de le faire avec le SGBD [Firebird].

19.5

Tests de lapplication [web]

Pour tester lapplication web avec le SGBD [MySQL], nous construisons un projet Eclipse [mvc-personnes-05B] de faon analogue celle utilise pour construire le projet [mvc-personnes-03B] avec la base Firebird (cf page 227). Cependant, comme avec Postgres, nous n'avons pas recrer les archives [personnes-dao.jar] et [personnes-service.jar] puisque nous n'avons modifi aucune classe. Nous dployons le projet web [mvc-personnes-05B] au sein de Tomcat :

Le SGBD MySQL est lanc. Le contenu de la table [PERSONNES] est alors le suivant :

Tomcat est lanc son tour. Avec un navigateur, nous demandons lurl [http://localhost:8080/mvc-personnes-05B] :

Nous ajoutons une nouvelle personne avec le lien [Ajout] :


Les bases du dveloppement web MVC en Java, par l'exemple

249/264

Nous vrifions lajout dans la base de donnes :

Le lecteur est invit faire dautres tests [modification, suppression].

20 Application web MVC dans une architecture 3tier Exemple 6, SQL Server Express
20.1 La base de donnes SQL Server Express

Dans cette version, nous allons installer la liste des personnes dans une table de base de donnes SQL Server Express 2005 disponible l'url [http://msdn.microsoft.com/vstudio/express/sql/]. Dans ce qui suit, les copies dcran proviennent du client EMS Manager Lite pour SQL Server Express [http://www.sqlmanager.net/fr/products/mssql/manager], un client dadministration gratuit du SGBD SQL Server Express. La base de donnes sappelle [dbpersonnes]. Elle contient une table [PERSONNES] :

Les bases du dveloppement web MVC en Java, par l'exemple

250/264

La table [PERSONNES] contiendra la liste des personnes gre par lapplication web. Elle a t construite avec les ordres SQL suivants :
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. CREATE TABLE [dbo].[PERSONNES] ( [ID] int IDENTITY(1, 1) NOT NULL, [VERSION] int NOT NULL, [NOM] varchar(30) COLLATE French_CI_AS NOT NULL, [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL, [DATENAISSANCE] datetime NOT NULL, [MARIE] tinyint NOT NULL, [NBENFANTS] tinyint NOT NULL, PRIMARY KEY CLUSTERED ([ID]), CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''), CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>''), CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0)) ) ON [PRIMARY] GO

ligne 2 : la cl primaire [ID] est de type entier. L'attribut IDENTITY indique que si on insre une ligne sans valeur pour la colonne ID de la table, SQL Express gnrera lui-mme un nombre entier pour cette colonne. Dans IDENTITY(1, 1), le premier paramtre est la premire valeur possible pour la cl primaire, le second, l'incrment utilis dans la gnration des nombres.

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 diffrente de -1 ensuite, cette valeur tant la cl primaire affecte la nouvelle ligne insre dans la table [PERSONNES]. Voyons sur un exemple comment nous allons pouvoir connatre cette valeur.

Les bases du dveloppement web MVC en Java, par l'exemple

251/264

- on excute l'ordre SELECT cidessus pour connatre la dernire valeur insre dans le champ ID de la - on excute l'ordre d'insertion ci-dessus (F12). On notera qu'on ne donne pas de valeur table -> au champ ID ->

- le rsultat - vrification Lordre SQL


SELECT @@IDENTITY

permet de connatre la dernire valeur insre dans le champ ID de la table. Elle est mettre aprs l'insertion. C'est une diffrence avec les SGBD [Firebird] et [Postgres] o on demandait la valeur de la cl primaire de la personne ajoute avant l'insertion, mais c'est analogue la gnration de cl primaire du SGBD MySQL. Nous lutiliserons dans le fichier [personnessqlexpress.xml] qui rassemble les ordres SQL mis sur la base de donnes.

20.2

Le projet Eclipse des couches [dao] et [service]

Pour dvelopper les couches [dao] et [service] de notre application avec la base de donnes[SQL Server Express], nous utiliserons le projet Eclipse [mvc-personnes-06] suivant :

Le projet est un simple projet Java, pas un projet web Tomcat. Dossier [src] Ce dossier contient les codes source des couches [dao] et [service] :

Les bases du dveloppement web MVC en Java, par l'exemple

252/264

Tous les fichiers ayant [sqlexpress] dans leur nom ont pu subir ou non une modification vis vis des versions Firebird, Postgres et MySQL. Dans ce qui suit, nous ne dcrivons que ceux qui ont t modifis. Dossier [database] Ce dossier contient le script de cration de la base de donnes SQL Express des personnes :

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.

-----

SQL Manager 2005 Lite for SQL Server (2.2.0.1) --------------------------------------Host : (local)\SQLEXPRESS Database : dbpersonnes

--- Structure for table PERSONNES : -CREATE TABLE [dbo].[PERSONNES] ( [ID] int IDENTITY(1, 1) NOT NULL, [VERSION] int NOT NULL, [NOM] varchar(30) COLLATE French_CI_AS NOT NULL, [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL, [DATENAISSANCE] datetime NOT NULL, [MARIE] tinyint NOT NULL, [NBENFANTS] tinyint NOT NULL, CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0)), CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''), CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>'') ) ON [PRIMARY] GO --- Data for table PERSONNES -(LIMIT 0,500)

SET IDENTITY_INSERT [dbo].[PERSONNES] ON GO

INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS]) 34. VALUES 35. (1, 1, 'Major', 'Joachim', '19541113', 1, 2) 36. GO 37. 38. INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS]) 39. VALUES 40. (2, 1, 'Humbort', 'Mlanie', '19850212', 0, 1) 41. GO 42. 43. INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS]) 44. VALUES 45. (3, 1, 'Lemarchand', 'Charles', '19860301', 0, 0) 46. GO 47. 48. SET IDENTITY_INSERT [dbo].[PERSONNES] OFF 49. GO 50. 51. -52. -- Definition for indices : 53. -54. 55. ALTER TABLE [dbo].[PERSONNES] 56. ADD PRIMARY KEY CLUSTERED ([ID]) 57. WITH ( 58. PAD_INDEX = OFF, 59. IGNORE_DUP_KEY = OFF, 60. STATISTICS_NORECOMPUTE = OFF, 61. ALLOW_ROW_LOCKS = ON, 62. ALLOW_PAGE_LOCKS = ON) 63. ON [PRIMARY] 64. GO

Dossier [lib] Ce dossier contient les archives ncessaires lapplication :


Les bases du dveloppement web MVC en Java, par l'exemple

253/264

On notera la prsence du pilote jdbc [sqljdbc.jar] du SGBD [Sql Server Express]. Toutes ces archives font partie du Classpath du projet Eclipse.

20.3

La couche [dao]

La couche [dao] est la suivante :

Nous ne prsentons que ce qui change vis vis de la version [Firebird]. Le fichier de mapping [personne-sqlexpress.xml] est le suivant :
1. <?xml version="1.0" encoding="UTF-8" ?> 2. 3. <!DOCTYPE sqlMap 4. PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 5. "http://www.ibatis.com/dtd/sql-map-2.dtd"> 6. 7. <sqlMap> 8. <!-- alias classe [Personne] --> 9. <typeAlias alias="Personne.classe" 10. type="istia.st.mvc.personnes.entites.Personne"/> 11. <!-- mapping table [PERSONNES] - objet [Personne] --> 12. <resultMap id="Personne.map" 13. class="istia.st.mvc.personnes.entites.Personne"> 14. <result property="id" column="ID" /> 15. <result property="version" column="VERSION" /> 16. <result property="nom" column="NOM"/> 17. <result property="prenom" column="PRENOM"/> 18. <result property="dateNaissance" column="DATENAISSANCE"/> 19. <result property="marie" column="MARIE"/> 20. <result property="nbEnfants" column="NBENFANTS"/> 21. </resultMap> 22. <!-- liste de toutes les personnes --> 23. <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, Les bases du dveloppement web MVC en Java, par l'exemple

254/264

24. PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select> 25. <!-- obtenir une personne en particulier --> 26. <select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM, 27. PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#value#</select> 28. <!-- ajouter une personne --> 29. <insert id="Personne.insertOne" parameterClass="Personne.classe"> 30. insert into 31. PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 32. VALUES(#version#, #nom#, #prenom#, #dateNaissance#, #marie#, 33. #nbEnfants#) 34. <selectKey keyProperty="id"> 35. select @@IDENTITY as value 36. </selectKey> 37. </insert> 38. <!-- mettre jour une personne --> 39. <update id="Personne.updateOne" parameterClass="Personne.classe"> update 40. PERSONNES set VERSION=#version#+1, NOM=#nom#, PRENOM=#prenom#, DATENAISSANCE=#dateNaissance#, 41. MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# and 42. VERSION=#version#</update> 43. <!-- supprimer une personne --> 44. <delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 45. ID=#value# </delete> 46. <!-- obtenir la valeur de la cl primaire [id] de la dernire personne insre --> 47. <select id="Personne.getNextId" resultClass="int">select 48. LAST_INSERT_ID()</select> 49. </sqlMap>

Cest le mme contenu que [personnes-firebird.xml] aux dtails prs suivants :

lordre SQL " Personne.insertOne " a chang lignes 29-37 : l'ordre SQL d'insertion est excut avant l'ordre SELECT qui va permettre de rcuprer la valeur de la cl primaire de la ligne insre l'ordre SQL d'insertion n'a pas de valeur pour la colonne ID de la table [PERSONNES] Cela reflte l'exemple d'insertion que nous avons comment page 251. A noter, qu'on retrouve ici le problme d'insertions simultanes par des threads diffrents dcrit pour MySQL au paragraphe 19.3, page 247.

La classe dimplmentation [DaoImplCommon] de la couche [dao] est celle des trois versions prcdentes. La configuration de la couche [dao] a t adapte au SGBD [SQL Express]. Ainsi, le fichier de configuration [spring-configtest-dao-sqlexpress.xml] est-il 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. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. <?xml version="1.0" encoding="ISO_8859-1"?> <!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- la source de donnees DBCP --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName"> <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value> </property> <property name="url"> <value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000;databaseName=dbpersonnes</value> </property> <property name="username"> <value>sa</value> </property> <property name="password"> <value>msde</value> </property> </bean> <!-- SqlMapCllient --> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource"> <ref local="dataSource"/> </property> <property name="configLocation"> <value>classpath:sql-map-config-sqlexpress.xml</value> </property> </bean> <!-- la classes d'acc la couche [dao] --> <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon"> <property name="sqlMapClient"> <ref local="sqlMapClient"/> </property> </bean> </beans>

Les bases du dveloppement web MVC en Java, par l'exemple

255/264

lignes 5-19 : le bean [dataSource] dsigne maintenant la base [SQL Express] [dbpersonnes] dont ladministrateur est [sa] avec le mot de passe [msde]. Le lecteur modifiera cette configuration selon son propre environnement. ligne 31 : la classe [DaoImplCommon] est la classe d'implmentation de la couche [dao]

La ligne 11 mrite des explications :


<value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000;databaseName=dbpersonnes</value>

//localhost : indique que le serveur SQL Express est sur la mme machine que notre application Java \\SQLEXPRESS : est le nom d'une instance de SQL Server. Il semble que plusieurs instances peuvent s'excuter en mme temps. Il semble donc logique de nommer l'instance laquelle on s'adresse. Ce nom peut tre obtenu grce [SQL Server Configuration Manager] install normalement en mme temps que SQL Express :

4000 : port d'coute de SQL Express. Cela est dpendant de la configuration du serveur. Par dfaut, il travaille avec des ports dynamiques, donc pas connus l'avance. On ne prcise alors pas de port dans l'url JDBC. Ici, nous avons travaill avec un port fixe, le port 4000. Cela s'obtient par configuration :

- [clic droit sur TCP/IP -> Proprits] ->

- on laisse les champs [TCP Dynamic Ports] vides - on met les champs [TCP Port] 4000

l'attribut dataBaseName fixe la base de donnes avec laquelle on veut travailler. C'est celle qui a t cre avec le client EMS :

Les bases du dveloppement web MVC en Java, par l'exemple

256/264

Ces modifications faites, on peut passer aux tests.

20.4

Les tests des couches [dao] et [service]

Les tests des couches [dao] et [service] sont les mmes que pour la version [Firebird]. Les rsultats obtenus sont les suivants :

- couche [dao]

- couche [service]

On constate que les tests ont t passs avec succs avec l'implmentation [DaoImplCommon]. Nous n'aurons pas driver cette classe comme il avait t ncessaire de le faire avec le SGBD [Firebird].

20.5

Tests de lapplication [web]

Pour tester lapplication web avec le SGBD [SQL Server Express], nous construisons un projet Eclipse [mvc-personnes-06B] de faon analogue celle utilise pour construire les projets web prcdents. Nous dployons le projet web [mvc-personnes-05B] au sein de Tomcat :

Le SGBD SQL server Express est lanc. Le contenu de la table [PERSONNES] est alors le suivant :

Tomcat est lanc son tour. Avec un navigateur, nous demandons lurl [http://localhost:8080/mvc-personnes-06B] :

Les bases du dveloppement web MVC en Java, par l'exemple

257/264

Nous ajoutons une nouvelle personne avec le lien [Ajout] :

Nous vrifions lajout dans la base de donnes :

Le lecteur est invit faire dautres tests [modification, suppression].

21 Conclusion
Rappelons ce qui a t prsent 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 prt pour dvelopper lui-mme ses propres applications web en Java. Il est galement prt aborder d'autres mthodes de dveloppement proches de celles tudies dans ce document. Rappelons l'architecture des applications web dveloppes ici :
Les bases du dveloppement web MVC en Java, par l'exemple

258/264

Application web
couche [web]

Servlet Utilisateur JSP1 JSP2 JSPn


Pour des applications simples, cette architecture est suffisante. Lorsqu'on a crit plusieurs applications de ce type, on s'aperoit que les servlets de deux applications diffrentes : 1. ont le mme mcanisme pour dterminer quelle mthode [doAction] il faut excuter pour traiter l'action demande par l'utilisateur 2. ne diffrent en fait que par le contenu de ces mthodes [doAction] La tentation est alors grande de : factoriser le traitement (1) dans une servlet gnrique ignorante de l'application qui l'utilise dlguer le traitement (2) des classes externes puisque la servlet gnrique ne sait pas dans quelle application elle est utilise faire le lien entre l'action demande par l'utilisateur et la classe qui doit la traiter l'aide d'un fichier de configuration Des outils, souvent appels " frameworks ", sont apparus pour apporter les facilits prcdentes aux dveloppeurs. Le plus ancien et probablement le plus connu d'entre-eux est Struts (http://struts.apache.org/). Jakarta Struts est un projet de l'Apache Software Foundation (www.apache.org). Ce framework est dcrit dans (http://tahe.developpez.com/java/struts/). Apparu plus rcemment, le framework Spring (http://www.springframework.org/) offre des facilits analogues celles de Struts. C'est en ralit son module Spring MVC. Son utilisation a t dcrite dans plusieurs articles (http://tahe.developpez.com/java/springmvc-part1/). Spring ne s'arrte pas au seul concept MVC de la couche [web] d'une application 3tier. Il est utile mme dans des applications situes en-dehors du web. Ainsi, nous avons termin notre cours en mettant en oeuvre une architecture MVC dans une architecture 3tier [web, metier, dao] sur un exemple basique de gestion dune liste de personnes.

couche [metier]
Modles

couche [dao]

Donnes

Application web
couche [web]

Application Utilisateur
LIST EDIT ERREURS Exception Modles

couche [service]

couche [dao]

Donnes

Spring IoC

Dans la version 1 de l'application, la liste des personnes tait maintenue en mmoire et disparaissait au dchargement de lapplication web. Dans les autres versions, la liste des personnes est maintenue dans une table de base de donnes. Nous avons utilis quatre SGBD diffrents : Firebird, Postgres, MySQL et SQL Server Express. Grce Spring IoC, la couche [web] de la version 1 a pu tre garde intgralement dans les versions suivantes. Nous avons ainsi montr qu'on pouvait construire des architectures ntier avec des couches indpendantes.
Les bases du dveloppement web MVC en Java, par l'exemple

259/264

Avec les versions utilisant une base de donnes, nous avons montr l'apport de Spring pour la construction des couches [dao] et [service]. Grce l'intgration de Spring avec iBATIS, nous avons pu construire quatre versions qui ne diffrent que par leurs fichiers de configuration. La mme classe [DaoImplCommon] a t utilise pour implmenter la couche [dao] dans les quatre versions. Pour grer un problme spcifique au SGBD Firebird, nous avons t amens driver cette classe mais pas la modifier. Enfin, nous avons montr comment Spring nous permettait de grer les transactions de faon dclarative au niveau de la couche [service]. Le lecteur est encourag dcouvrir la totalit des fonctionnalits offertes par ce produit.

22 Le code de l'article
Le lecteur trouvera le code des exemples de ce document, sur le site de l'article, sous la forme d'un fichier zipp.

- contenu du zip

- contenu du dossier [lib]

Le dossier [lib] rassemble les archives utilises par les diffrents projets tudis. Les dossiers [lib] des diffrents projets ont t eux vids afin de diminuer la taille du zip. Il faudra donc aller chercher les archives ncessaires un projet, dans le dossier [lib] ci-dessus. Dans les dossiers [lib] des projets web [mvc-personnes-0XB], ont t laisses les archives des couches [dao], [service] et [web] de l'application construite par les projets associs [mvc-personnes-0X] :

Pour rejouer les exemples avec Base de donnes, le lecteur devra adapter la configuration du [DataSource] trouv dans les diffrents fichiers de configuration son propre environnement. Cet objet renseigne : 1. le nom de classe du pilote JDBC du SGBD utilis 2. l'url de la base de donnes exploiter 3. le propritaire des connexions qui sont ouvertes sur la base 4. le mot de passe de ce dernier. Les informations 2 4 sont dpendantes de l'environnement utilis pour les tests.

Les bases du dveloppement web MVC en Java, par l'exemple

260/264

Table des matires


1INTRODUCTION........................................................................................................................................................................ 2 2LES OUTILS UTILISS DANS LE DOCUMENT.................................................................................................................. 3 2.1JAVA 1.5.......................................................................................................................................................................................3 2.2LE CONTENEUR DE SERVLETS TOMCAT 5..........................................................................................................................................4 2.3DPLOIEMENT D'UNE APPLICATION WEB AU SEIN DU SERVEUR TOMCAT...............................................................................................9 2.3.1DPLOIEMENT.............................................................................................................................................................................. 9 2.3.2ADMINISTRATION DE TOMCAT...................................................................................................................................................... 10 2.3.3GESTION DES APPLICATIONS WEB DPLOYES...................................................................................................................................14 2.3.4APPLICATION WEB AVEC PAGE D'ACCUEIL....................................................................................................................................... 17 2.4INSTALLATION D'ECLIPSE............................................................................................................................................................. 19 2.5INTGRATION TOMCAT - ECLIPSE................................................................................................................................................. 24 3LES BASES DU DVELOPPEMENT WEB EN JAVA........................................................................................................ 29 3.1CRATION D'UN PROJET WEB SOUS ECLIPSE................................................................................................................................... 29 3.2CRATION D'UNE PAGE D'ACCUEIL................................................................................................................................................. 33 3.3TEST DE LA PAGE D'ACCUEIL.........................................................................................................................................................34 3.4CRATION D'UN FORMULAIRE HTML...........................................................................................................................................36 3.5CRATION D'UNE PAGE JSP..........................................................................................................................................................38 3.6CRATION D'UNE SERVLET............................................................................................................................................................ 41 3.6.1CRATION DE LA SERVLET............................................................................................................................................................42 3.6.2CLASSPATH D'UN PROJET ECLIPSE..................................................................................................................................................44 3.6.3CONFIGURATION DE LA SERVLET................................................................................................................................................... 46 3.6.4LE CODE DE LA SERVLET [SERVLETFORMULAIRE]............................................................................................................................47 3.6.5TEST DE LA SERVLET...................................................................................................................................................................49 3.6.6RECHARGEMENT AUTOMATIQUE DU CONTEXTE DE L'APPLICATION WEB.................................................................................................50 3.7COOPRATION SERVLET ET PAGES JSP..........................................................................................................................................53 3.7.1LA SERVLET [SERVLETFORMULAIRE2]...........................................................................................................................................54 3.7.2LA PAGE JSP [FORMULAIRE2.JSP]................................................................................................................................................ 56 3.7.3CONFIGURATION DE L'APPLICATION................................................................................................................................................ 57 4DVELOPPEMENT MVC (MODLE VUE CONTRLEUR).................................................................................... 58 5APPLICATION WEB MVC [PERSONNE] VERSION 1.................................................................................................. 60 5.1LES VUES DE L'APPLICATION......................................................................................................................................................... 60 5.2ARCHITECTURE DE L'APPLICATION.................................................................................................................................................61 5.3LE PROJET ECLIPSE..................................................................................................................................................................... 63 5.4CONFIGURATION DE L'APPLICATION WEB [PERSONNE1]....................................................................................................................65 5.5LE CODE DES VUES....................................................................................................................................................................... 66 5.5.1LA VUE [FORMULAIRE]................................................................................................................................................................66 5.5.2LA VUE [REPONSE]..................................................................................................................................................................... 68 5.5.3LA VUE [ERREURS]..................................................................................................................................................................... 68 5.6TESTS DES VUES........................................................................................................................................................................... 69 5.7LE CONTRLEUR [SERVLETPERSONNE]..........................................................................................................................................71 5.7.1SQUELETTE DU CONTRLEUR........................................................................................................................................................ 71 5.7.2INITIALISATION DU CONTRLEUR....................................................................................................................................................72 5.7.3LA MTHODE [DOGET]................................................................................................................................................................73 5.7.4LA MTHODE [DOINIT]................................................................................................................................................................ 74 5.7.5LA MTHODE [DOVALIDATIONFORMULAIRE]...................................................................................................................................75 5.8TESTS......................................................................................................................................................................................... 75 6APPLICATION WEB MVC [PERSONNE] VERSION 2.................................................................................................. 76 6.1INTRODUCTION.............................................................................................................................................................................76 6.2LE PROJET ECLIPSE..................................................................................................................................................................... 78 6.3CONFIGURATION DE L'APPLICATION WEB [PERSONNE2]....................................................................................................................80 6.4LE CODE DES VUES....................................................................................................................................................................... 81 6.4.1LA VUE [FORMULAIRE]................................................................................................................................................................81 6.4.2LA VUE [REPONSE]..................................................................................................................................................................... 82 6.4.3LA VUE [ERREURS]..................................................................................................................................................................... 83 6.5TESTS DES VUES........................................................................................................................................................................... 84 6.6LE CONTRLEUR [SERVLETPERSONNE]..........................................................................................................................................86
Les bases du dveloppement web MVC en Java, par l'exemple

261/264

6.6.1SQUELETTE DU CONTRLEUR........................................................................................................................................................ 86 6.6.2INITIALISATION DU CONTRLEUR [INIT]...........................................................................................................................................86 6.6.3LA MTHODE [DOGET]................................................................................................................................................................87 6.6.4LA MTHODE [DOINIT]................................................................................................................................................................ 88 6.6.5LA MTHODE [DOVALIDATIONFORMULAIRE]...................................................................................................................................88 6.6.6LA MTHODE [DORETOURFORMULAIRE]........................................................................................................................................ 89 6.7TESTS......................................................................................................................................................................................... 90 7APPLICATION WEB MVC [PERSONNE] VERSION 3.................................................................................................. 90 7.1INTRODUCTION.............................................................................................................................................................................90 7.2LE PROJET ECLIPSE..................................................................................................................................................................... 91 7.3CONFIGURATION DE L'APPLICATION WEB [PERSONNE3]....................................................................................................................92 7.4LE CODE DES VUES....................................................................................................................................................................... 93 7.4.1LA VUE [FORMULAIRE]................................................................................................................................................................93 7.4.2LA VUE [REPONSE]..................................................................................................................................................................... 95 7.4.3LA VUE [ERREURS]..................................................................................................................................................................... 96 7.5TESTS DES VUES........................................................................................................................................................................... 97 7.6LE CONTRLEUR [SERVLETPERSONNE]..........................................................................................................................................98 7.6.1SQUELETTE DU CONTRLEUR........................................................................................................................................................ 99 7.6.2LA MTHODE [DOGET]..............................................................................................................................................................100 7.6.3LA MTHODE [DOINIT].............................................................................................................................................................. 100 7.6.4LA MTHODE [DORETOURFORMULAIRE]...................................................................................................................................... 100 7.7TESTS....................................................................................................................................................................................... 100 8LA BIBLIOTHQUE DE BALISES JSTL........................................................................................................................... 101 8.1.1INTRODUCTION......................................................................................................................................................................... 101 8.1.2INSTALLER ET DCOUVRIR LA BIBLIOTHQUE JSTL........................................................................................................................103 8.1.3UTILISER JSTL DANS UNE APPLICATION WEB................................................................................................................................106 9APPLICATION WEB MVC [PERSONNE] VERSION 4................................................................................................ 107 9.1LE PROJET ECLIPSE................................................................................................................................................................... 107 9.2CONFIGURATION DE L'APPLICATION WEB [PERSONNE4]..................................................................................................................107 9.3LE CODE DES VUES..................................................................................................................................................................... 108 9.3.1LA VUE [FORMULAIRE]..............................................................................................................................................................108 9.3.2LA VUE [REPONSE]................................................................................................................................................................... 109 9.3.3LA VUE [ERREURS]................................................................................................................................................................... 110 9.4TESTS DES VUES......................................................................................................................................................................... 111 9.5LE CONTRLEUR [SERVLETPERSONNE]........................................................................................................................................112 9.5.1LA MTHODE [DOINIT].............................................................................................................................................................. 113 9.5.2LA MTHODE [DOVALIDATIONFORMULAIRE].................................................................................................................................113 9.5.3LA MTHODE [DORETOURFORMULAIRE]...................................................................................................................................... 113 9.6TESTS....................................................................................................................................................................................... 114 10APPLICATION WEB MVC [PERSONNE] VERSION 5.............................................................................................. 114 10.1INTRODUCTION.........................................................................................................................................................................114 10.2LE PROJET ECLIPSE................................................................................................................................................................. 116 10.3CONFIGURATION DE L'APPLICATION WEB [PERSONNE5]................................................................................................................117 10.4LE CODE DES VUES................................................................................................................................................................... 118 10.5LE CONTRLEUR [SERVLETPERSONNE]......................................................................................................................................119 10.5.1LA MTHODE [DOGET]............................................................................................................................................................119 10.5.2LA MTHODE [DOVALIDATIONFORMULAIRE]...............................................................................................................................119 10.5.3LA MTHODE [DORETOURFORMULAIRE].................................................................................................................................... 120 10.6TESTS..................................................................................................................................................................................... 121 11APPLICATION WEB MVC [PERSONNE] VERSION 6.............................................................................................. 121 11.1INTRODUCTION.........................................................................................................................................................................121 11.2LE PROJET ECLIPSE................................................................................................................................................................. 121 11.3CONFIGURATION DE L'APPLICATION WEB [PERSONNE6]................................................................................................................121 11.4LE CODE DES VUES................................................................................................................................................................... 122 11.5LE CONTRLEUR [SERVLETPERSONNE]......................................................................................................................................123 11.5.1LA MTHODE [DOGET]............................................................................................................................................................123 11.5.2LA MTHODE [DOVALIDATIONFORMULAIRE]...............................................................................................................................123 11.6TESTS..................................................................................................................................................................................... 123 12APPLICATION WEB MVC [PERSONNE] VERSION 7.............................................................................................. 123 12.1INTRODUCTION.........................................................................................................................................................................123 Les bases du dveloppement web MVC en Java, par l'exemple 262/264

12.2LE PROJET ECLIPSE................................................................................................................................................................. 128 12.3CONFIGURATION DE L'APPLICATION WEB [PERSONNE7]................................................................................................................128 12.4LE CODE DES VUES................................................................................................................................................................... 129 12.4.1LA VUE [FORMULAIRE]............................................................................................................................................................129 12.4.2LA VUE [RPONSE]................................................................................................................................................................. 130 12.4.3LA VUE [ERREURS]................................................................................................................................................................. 130 12.5LE CONTRLEUR [SERVLETPERSONNE]......................................................................................................................................131 12.6TESTS..................................................................................................................................................................................... 133 13APPLICATION WEB MVC [PERSONNE] VERSION 8.............................................................................................. 133 14APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 1.................................................. 136 14.1PRSENTATION.........................................................................................................................................................................136 14.2LE PROJET ECLIPSE................................................................................................................................................................. 138 14.3LA REPRSENTATION DUNE PERSONNE.......................................................................................................................................139 14.4LA COUCHE [DAO]....................................................................................................................................................................140 14.5TESTS DE LA COUCHE [DAO]......................................................................................................................................................145 14.6LA COUCHE [SERVICE]..............................................................................................................................................................151 14.7TESTS DE LA COUCHE [SERVICE]................................................................................................................................................152 14.8LA COUCHE [WEB]................................................................................................................................................................... 154 14.8.1CONFIGURATION DE LAPPLICATION WEB.....................................................................................................................................155 14.8.2LES PAGES JSP / JSTL DE LAPPLICATION................................................................................................................................ 156 14.8.3LE CONTRLEUR DE LAPPLICATION........................................................................................................................................... 161 14.9LES TESTS DE LAPPLICATION WEB.............................................................................................................................................169 14.10CONCLUSION..........................................................................................................................................................................172 15SPRING IOC.......................................................................................................................................................................... 172 15.1INTRODUCTION.........................................................................................................................................................................172 15.2SPRING IOC PAR LA PRATIQUE.................................................................................................................................................. 174 15.2.1SPRING................................................................................................................................................................................. 174 15.2.2PROJETS ECLIPSE DES EXEMPLES...............................................................................................................................................175 15.2.3EXEMPLE 1........................................................................................................................................................................... 176 15.2.4EXEMPLE 2........................................................................................................................................................................... 178 15.2.5EXEMPLE 3........................................................................................................................................................................... 180 15.3CONFIGURATION D'UNE APPLICATION N TIER AVEC SPRING...........................................................................................................182 15.4CONCLUSION............................................................................................................................................................................186 16APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 2.................................................. 186 16.1INTRODUCTION.........................................................................................................................................................................186 16.2MISE EN ARCHIVES DE LAPPLICATION WEB................................................................................................................................ 188 17APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 3 SGBD FIREBIRD............... 190 17.1LA BASE DE DONNES FIREBIRD.................................................................................................................................................190 17.2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]............................................................................................................... 192 17.3LA COUCHE [DAO]....................................................................................................................................................................194 17.3.1LES COMPOSANTES DE LA COUCHE [DAO]................................................................................................................................... 194 17.3.2LA COUCHE DACCS AUX DONNES [IBATIS].......................................................................................................................... 196 17.3.3LA CLASSE [DAOIMPLCOMMON].............................................................................................................................................. 202 17.4TESTS DE LA COUCHE [DAO]......................................................................................................................................................208 17.4.1TESTS DE L'IMPLMENTATION [DAOIMPLCOMMON]..................................................................................................................... 208 17.4.2LA CLASSE [DAOIMPLFIREBIRD]...............................................................................................................................................217 17.4.3TESTS DE L'IMPLMENTATION [DAOIMPLFIREBIRD]......................................................................................................................217 17.5LA COUCHE [SERVICE]..............................................................................................................................................................219 17.5.1LES COMPOSANTES DE LA COUCHE [SERVICE].............................................................................................................................. 219 17.5.2CONFIGURATION DE LA COUCHE [SERVICE]................................................................................................................................. 220 17.6TESTS DE LA COUCHE [SERVICE]................................................................................................................................................225 17.7LA COUCHE [WEB]................................................................................................................................................................... 227 17.8CONCLUSION............................................................................................................................................................................234 18APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 4, POSTGRES........................... 235 18.1LA BASE DE DONNES POSTGRES............................................................................................................................................... 235 18.2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]............................................................................................................... 237 18.3LA COUCHE [DAO]....................................................................................................................................................................239 18.4LES TESTS DES COUCHES [DAO] ET [SERVICE]............................................................................................................................. 241 18.5TESTS DE LAPPLICATION [WEB]................................................................................................................................................ 241
Les bases du dveloppement web MVC en Java, par l'exemple

263/264

19APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 5, MYSQL.................................. 243 19.1LA BASE DE DONNES MYSQL.................................................................................................................................................243 19.2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]............................................................................................................... 244 19.3LA COUCHE [DAO]....................................................................................................................................................................246 19.4LES TESTS DES COUCHES [DAO] ET [SERVICE]............................................................................................................................. 248 19.5TESTS DE LAPPLICATION [WEB]................................................................................................................................................ 249 20APPLICATION WEB MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 6, SQL SERVER EXPRESS.... 250 20.1LA BASE DE DONNES SQL SERVER EXPRESS............................................................................................................................ 250 20.2LE PROJET ECLIPSE DES COUCHES [DAO] ET [SERVICE]............................................................................................................... 252 20.3LA COUCHE [DAO]....................................................................................................................................................................254 20.4LES TESTS DES COUCHES [DAO] ET [SERVICE]............................................................................................................................. 257 20.5TESTS DE LAPPLICATION [WEB]................................................................................................................................................ 257 21CONCLUSION...................................................................................................................................................................... 258 22LE CODE DE L'ARTICLE.................................................................................................................................................. 260

Les bases du dveloppement web MVC en Java, par l'exemple

264/264

Vous aimerez peut-être aussi