Vous êtes sur la page 1sur 43

Spring MVC par l'exemple

- Partie 4 -

serge.tahe@istia.univ-angers.fr, avril 2006

springmvc - partie4, ST universit dAngers

1/43

1 Rappels
Nous poursuivons dans cet article le travail fait dans les prcdents articles :

Spring MVC par l'exemple partie 1 : [http://tahe.developpez.com/java/springmvc-part1]


Spring MVC par l'exemple partie 2 : [http://tahe.developpez.com/java/springmvc-part2]
Spring MVC par l'exemple partie 3 : [http://tahe.developpez.com/java/springmvc-part3]

L'architecture d'une application Spring MVC est la suivante :


Couche Interface Utilisateur[ui]
utilisateur

DispatcherServlet
1

Couche mtier
[metier]

2
3

Controller
4

View

Couche d'accs
aux donnes
[dao]

Donnes

Modle Map
6

Dans ces trois articles, nous avons tudi les points suivants :
article 1 :

les diffrentes stratgies de rsolutions d'URL qui associent une URL demande par le client, un contrleur
implmentant l'interface [Controller] qui va traiter la demande du client (1,2)

les diffrentes faons qu'avait un contrleur [Controller] d'accder au contexte de l'application, par exemple pour
accder aux instances des couches [mtier] et [dao]

les diffrentes stratgies de rsolutions de noms de vue qui, un nom de vue rendu par le contrleur [Controller]
associe une classe implmentant l'interface [View] charge d'afficher le modle [Map] construit par le contrleur
[Controller] (4,5)
article 2 :

les classes [HandlerInterceptor] qui filtrent la requte avant de la passer au [Controller] qui doit la traiter (2)

les diffrentes faons de grer l'internationalisation (localisation) d'une application web

les gestionnaires des exceptions qui peuvent de produire dans une application web

la gestion des formulaires grce la classe [SimpleFormController]


article 3 :

des implmentations de l'interface [Controller] pas encore abordes dans les autres articles

des implmentations de l'interface [View] permettant de gnrer des documents PDF et Excel

des outils facilitant l'criture de formulaires destins tlcharger des documents du poste client vers le serveur

2 Spring MVC dans une architecture 3tier Exemple 1


2.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 :

springmvc - partie4, ST universit dAngers

2/43

Couche Interface Utilisateur[ui]


utilisateur

DispatcherServlet
1

Couche mtier
[metier]

2
3

Controller
4

Couche d'accs
aux donnes
[dao]

Donnes

Modle Map

View

Lapplication web correspondante va permettre de grer un groupe de personnes avec quatre oprations :

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
:

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 identique.
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 ->
springmvc - partie4, ST universit dAngers

3/43

Elle nest plus l.


On retouve la personne telle quelle a t modifie. On la
supprime maintenant ->

Les erreurs de saisie sont signales ->

2.2

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

Le projet Eclipse / Tomcat

Le projet de lapplication sappelle [spring-mvc-37] :

springmvc - partie4, ST universit dAngers

4/43

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


Couche Interface Utilisateur[ui-web]
utilisateur

DispatcherServlet
1

Couche service
[service]

2
3

Controller
4

View

Couche d'accs
aux donnes
[dao]

Donnes

Modle Map
6

la couche [dao] est contenue dans le paquetage [istia.st.springmvc.personnes.dao]


la couche [metier] ou [service] est contenue dans le paquetage [istia.st.springmvc.personnes.service]
la couche [web] ou [ui] est contenue dans le paquetage [istia.st.springmvc.personnes.web]
le paquetage [istia.st.springmvc.personnes.entites] contient les objets partags entre diffrentes couches
le paquetage [istia.st.springmvc.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.

2.3

La reprsentation dune personne

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

springmvc - partie4, ST universit dAngers

5/43

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.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.

package istia.st.springmvc.personnes.entites;

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 :

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
public Personne(Personne p) {
setId(p.getId());
setVersion(p.getVersion());
setNom(p.getNom());
setPrenom(p.getPrenom());
setDateNaissance(p.getDateNaissance());
setMarie(p.getMarie());
setNbEnfants(p.getNbEnfants());
}

// toString
public String toString() {
return "[" + id + "," + version + "," + prenom + "," + nom + ","
+ new SimpleDateFormat("dd/MM/yyyy").format(dateNaissance)
+ "," + marie + "," + nbEnfants + "]";
}

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.
springmvc - partie4, ST universit dAngers
6/43

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.

2.4

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

La couche [dao]

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

[IDao] est linterface prsente par la couche [dao]


[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;

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

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);
}

La couche [dao] est susceptible de lancer des exceptions. Celles-ci seront de type [DaoException] :
1.
2.
3.
4.
5.

package istia.st.springmvc.personnes.dao;
public class DaoException extends RuntimeException {
// code erreur

springmvc - partie4, ST universit dAngers

7/43

6.
private int code;
7.
8.
public int getCode() {
9.
return code;
10. }
11.
12. // constructeur
13. public DaoException(String message,int code) {
14.
super(message);
15.
this.code=code;
16. }
17. }

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

package istia.st.springmvc.personnes.dao;
import istia.st.springmvc.personnes.entites.Personne;
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;
}

springmvc - partie4, ST universit dAngers

8/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.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.

// 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());
original.setNbEnfants(personne.getNbEnfants());
}
// suppression d'une personne
public void deleteOne(int id) {
// on cherche la personne
int i = getPosition(id);
// a-t-on trouv ?
if (i == -1) {
throw new DaoException("Personne d'id [" + id + "] inconnue", 2);
} else {
// on supprime la personne
personnes.remove(i);
}
}
// gnrateur d'id
private int getNextId() {
id++;
return id;
}
// rechercher une personne
private int getPosition(int id) {
int i = 0;
boolean trouv = false;
// on parcourt la liste des personnes
while (i < personnes.size() && !trouv) {
if (id == ((Personne) personnes.get(i)).getId()) {
trouv = true;
} else {
i++;
}
}
// rsultat ?
return trouv ? i : -1;
}
// vrification d'une personne

springmvc - partie4, ST universit dAngers

9/43

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.

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

springmvc - partie4, ST universit dAngers

10/43

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)

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.

2.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.
springmvc - partie4, ST universit dAngers

11/43

[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.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.

package istia.st.springmvc.personnes.tests;

lignes 18-26 : la classe de test dtient une rfrence de type [IDao] (ligne 18) sur la couche [dao]. Cest donc bien une
interface qui est teste. La rfrence dune implmentation concrte de celle-ci est obtenue dans le constructeur.
lignes 29-32 : le constructeur du test JUnit. Il rcupre la rfrence de limplmentation de la couche [dao] tester
dans le fichier de configuration Spring [spring-config-test-dao.xml] :

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;

import
import
import
import

istia.st.springmvc.personnes.dao.DaoException;
istia.st.springmvc.personnes.dao.IDao;
istia.st.springmvc.personnes.entites.Personne;
junit.framework.TestCase;

public class TestDao extends TestCase {


// couche [dao]
private IDao dao;
public IDao getDao() {
return dao;
}
public void setDao(IDao dao) {
this.dao = dao;
}
// constructeur
public TestDao() {
dao = (IDao) (new XmlBeanFactory(new ClassPathResource(
"spring-config-test-dao.xml"))).getBean("dao");
}
// 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


public void test5() throws ParseException {
...

springmvc - partie4, ST universit dAngers

12/43

Le fichier [spring-config-test-dao.xml] est dans [WEB-INF/src] parce que Spring va le chercher dans le ClassPath du
projet Eclipse. Son contenu est le suivant :
1.
2.
3.
4.
5.
6.

<?xml version="1.0" encoding="ISO_8859-1"?>


<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- implmentation de la couche [dao] -->
<bean id="dao" class="istia.st.springmvc.personnes.dao.DaoImpl" init-method="init"/>
</beans>

On voit que le bean [dao] dfini ligne 5 ci-dessus est celui utilis par le constructeur de linstance de test (ligne 31 du
constructeur). La classe dimplmentation de linterface [IDao] teste sera de type [DaoImpl]. Une fois cette instance
construite, sa mthode [init] sera excute. On rappelle quelle met trois personnes arbitraires dans la liste des
personnes.
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.

springmvc - partie4, ST universit dAngers

13/43

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 {
2.
// ajout d'une personne
3.
Personne p1 = new Personne(-1, "X", "X", new SimpleDateFormat(
4.
"dd/MM/yyyy").parse("01/02/2006"), true, 0);
5.
dao.saveOne(p1);
6.
int id1 = p1.getId();
7.
// cration de N threads de mise jour du nombre d'enfants
8.
final int N = 10;
9.
Thread[] taches = new Thread[N];
10. for (int i = 0; i < taches.length; i++) {
11.
taches[i] = new ThreadDaoMajEnfants("thread n " + i, dao, id1);
12.
taches[i].start();
13. }
14. // on attend la fin des threads
15. for (int i = 0; i < taches.length; i++) {
16.
taches[i].join();
17. }
18. // on rcupre la personne
19. p1 = dao.getOne(id1);
20. // elle doit avoir N enfants
21. assertEquals(N, p1.getNbEnfants());
22. // suppression personne p1
23. dao.deleteOne(p1.getId());
24. // vrification
25. boolean erreur = false;
26. int codeErreur = 0;
27. try {
28.
p1 = dao.getOne(p1.getId());
29. } catch (DaoException ex) {
30.
erreur = true;
31.
codeErreur = ex.getCode();
32. }
33. // on doit avoir une erreur de code 2
34. assertTrue(erreur);
35. assertEquals(2, codeErreur);
36. }

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.

package istia.st.springmvc.personnes.tests;
import istia.st.springmvc.personnes.dao.DaoException;
import istia.st.springmvc.personnes.dao.IDao;
import istia.st.springmvc.personnes.entites.Personne;
public class ThreadDaoMajEnfants extends Thread {
// nom du thread
private String name;
// rfrence sur la couche [dao]

springmvc - partie4, ST universit dAngers

14/43

11. private IDao dao;


12. // l'id de la personne sur qui on va travailler
13. private int idPersonne;
14.
15. // constructeur
16. public ThreadDaoMajEnfants(String name, IDao dao, int idPersonne) {
17.
this.name = name;
18.
this.dao = dao;
19.
this.idPersonne = idPersonne;
20. }
21.
22. // coeur du thread
23. public void run() {
24.
// suivi
25.
suivi("lanc");
26.
// on boucle tant qu'on n'a pas russi incrmenter de 1
27.
// le nbre d'enfants de la personne idPersonne
28.
boolean fini = false;
29.
int nbEnfants = 0;
30.
while (!fini) {
31.
// on rcupre une copie de la personne d'idPersonne
32.
Personne personne = dao.getOne(idPersonne);
33.
nbEnfants = personne.getNbEnfants();
34.
// suivi
35.
suivi("" + nbEnfants + " -> " + (nbEnfants + 1) + " pour la version "+personne.getVersion());
36.
// attente de 10 ms pour abandonner le processeur
37.
try {
38.
// suivi
39.
suivi("dbut attente");
40.
// on s'interrompt pour laisser le processeur
41.
Thread.sleep(10);
42.
// suivi
43.
suivi("fin attente");
44.
} catch (Exception ex) {
45.
throw new RuntimeException(ex.toString());
46.
}
47.
// attente termine - on essaie de valider la copie
48.
// entre-temps d'autres threads ont pu modifier l'original
49.
int codeErreur = 0;
50.
try {
51.
// incrmente de 1 le nbre d'enfants de cette copie
52.
personne.setNbEnfants(nbEnfants + 1);
53.
// on essaie de modifier l'original
54.
dao.saveOne(personne);
55.
// on est pass - l'original a t modifi
56.
fini = true;
57.
} catch (DaoException ex) {
58.
// on rcupre le code erreur
59.
codeErreur = ex.getCode();
60.
// doit tre une erreur de version 3 - sinon on relance
61.
// l'exception
62.
if (codeErreur != 3) {
63.
throw ex;
64.
} else {
65.
// suivi
66.
suivi(ex.getMessage());
67.
}
68.
// l'original a chang - on recommence tout
69.
}
70.
}
71.
// suivi
72.
suivi("a termin et pass le nombre d'enfants " + (nbEnfants + 1));
73. }
74.
75. // suivi
76. private void suivi(String message) {
77.
System.out.println(name + " [" + new Date().getTime()+ "] : " + message); }
78. }

ligne 7 : [ThreadDaoMajEnfants] est bien un thread


lignes 16-20 : 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 23) de celui-ci est excute
:

lignes 76-78 : la mthode prive [suivi] permet de faire des logs cran. La mthode [run] en use pour permettre le
suivi du thread dans son excution.

springmvc - partie4, ST universit dAngers

15/43

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 3070. Dans celle-ci, le thread :
demande une copie de la personne P modifier (ligne 32)
attend 10 ms (ligne 41). Ceci est artificiel et vise interrompre le thread entre la lecture de la personne P et sa mise
jour effective dans la liste des personnes afin daugmenter la probabilit de conflits.
incrmente le nombre denfants de P (ligne 52) et sauvegarde P (ligne 54). 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 59) 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 9).
// on attend 10 ms
//wait(10);

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


// 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 9).
// on attend 10 ms
wait(10);

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


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

On obtient les rsultats suivants :

springmvc - partie4, ST universit dAngers

16/43

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.
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 9) :
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

springmvc - partie4, ST universit dAngers

17/43

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)

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

2.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;
springmvc - partie4, ST universit dAngers

18/43

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.

import istia.st.springmvc.personnes.entites.Personne;

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

2.7

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 synchronized Collection getAll() {
return dao.getAll();
}
// obtenir une personne en particulier
public synchronized Personne getOne(int id) {
return dao.getOne(id);
}
// ajouter ou modifier une personne
public synchronized void saveOne(Personne personne) {
dao.saveOne(personne);
}

// suppression d'une personne


public synchronized void deleteOne(int id) {
dao.deleteOne(id);
}

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. package istia.st.springmvc.personnes.tests;
2.
springmvc - partie4, ST universit dAngers

19/43

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.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.

...

lignes 8-16 : la classe de test dtient une rfrence de type [IService] (ligne 8) sur la couche [service]. Cest donc bien
une interface qui est teste. La rfrence dune implmentation concrte de celle-ci est obtenue dans le constructeur.
lignes 19-22 : le constructeur du test JUnit. Il rcupre la rfrence de limplmentation de la couche [service] tester
dans le fichier de configuration Spring [spring-config-test-service.xml] :

public class TestService extends TestCase {


// couche [service]
private IService service;
public IService getService() {
return service;
}
public void setService(IService service) {
this.service = service;
}
// constructeur
public TestService() {
service = (IService) (new XmlBeanFactory(new ClassPathResource(
"spring-config-test-service.xml"))).getBean("service");
}
// 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);
// 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 {
...
}

springmvc - partie4, ST universit dAngers

20/43

Le fichier [spring-config-test-service.xml] est dans [WEB-INF/src] parce que Spring va le chercher dans le ClassPath
du projet Eclipse. Son contenu est le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

<?xml version="1.0" encoding="ISO_8859-1"?>


<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- implmentation de la couche [dao] -->
<bean id="dao" class="istia.st.springmvc.personnes.dao.DaoImpl" init-method="init"/>
<!-- implmentation de la couche service -->
<bean id="service" class="istia.st.springmvc.personnes.service.ServiceImpl">
<property name="dao">
<ref local="dao"/>
</property>
</bean>
</beans>

Linstance de la couche [dao] est dfinie ligne 5. Elle est de type [DaoImpl]. Linstance de la couche [service] est elle
dfinie lignes 7-11. Elle est de type [ServiceImpl]. Ce type a un attribut [dao] dont la valeur doit tre une rfrence de
la couche [dao]. Cet attribut est initialis lignes 8-10 avec la rfrence de la couche [dao] cre ligne 5. Au final, la
couche [service] est implmente avec une instance [ServiceImpl] dtenant une rfrence sur la couche [dao] ellemme implmente par une instance [DaoImpl].
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 39, 42) 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 62)

on passe aux threads une rfrence la couche [service] plutt qu la couche [dao] (ligne 68)
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.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.

package istia.st.springmvc.personnes.tests;

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

import istia.st.springmvc.personnes.dao.DaoException;
import istia.st.springmvc.personnes.entites.Personne;
import istia.st.springmvc.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;
}
public void run() {
...
}
// suivi
private void suivi(String message) {
System.out.println(name + " : " + message);
}
}

springmvc - partie4, ST universit dAngers

21/43

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 9).
// on attend 10 ms
wait(10);

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


// 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].

2.8

La couche [web]

Rappelons larchitecture 3tier de notre application :

Couche Interface Utilisateur[web]


utilisateur

DispatcherServlet
1

Couche service
[service]

2
3

Controller
4

Couche d'accs
aux donnes
[dao]

Donnes

Modle Map

View

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

liste des personnes du groupe


ajout dune personne au groupe
modification dune personne du groupe
suppression dune personne du groupe

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 3). Pour dcrire la couche web, nous allons prsenter successivement :

sa configuration
ses contrleurs
ses vues
quelques tests

springmvc - partie4, ST universit dAngers

22/43

2.8.1

Configuration de lapplication web

Le projet Eclipse / Tomcat de lapplication est le suivant :

dans le paquetage [istia.st.springmvc.personnes.web], on trouve les contrleurs et le validateur du formulaire dajout /


modification dune personne
le rsolveur de vues sera une instance de [ResourceBundleViewResolver]. Les vues seront dfinies dans le fichier
[vues.properties] dans [WEB-INF/src]. Les pages JSP / JSTL associes sont dans [WEB-INF/vues].
les messages derreurs sont dfinis dans [messages.properties] dans [WEB-INF/src].
le dossier [lib] contient les archives tierces ncessaires lapplication. Elles sont visibles sur la copie dcran cidessus.

[web.xml]
Le fichier [web.xml] est le fichier exploit par le serveur web pour charger lapplication. Son contenu est le suivant :
1. <?xml version="1.0" encoding="ISO-8859-1"?>
2.
3. <!DOCTYPE web-app PUBLIC
4.
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
5.
"http://java.sun.com/dtd/web-app_2_3.dtd">
6. <web-app>
7.
<!-- le chargeur du contexte spring de l'application -->
8.
<listener>
9.
<listener-class>
10.
org.springframework.web.context.ContextLoaderListener</listener-class>
11. </listener>
12. <!-- la servlet -->
13. <servlet>
14.
<servlet-name>personnes</servlet-name>
15.
<servlet-class>
16.
org.springframework.web.servlet.DispatcherServlet</servlet-class>
17. </servlet>
18. <!-- le mapping des url -->
19. <servlet-mapping>
20.
<servlet-name>personnes</servlet-name>
21.
<url-pattern>*.html</url-pattern>
22. </servlet-mapping>
23. <!-- le document d'entre -->
springmvc - partie4, ST universit dAngers

23/43

24. <welcome-file-list>
25.
<welcome-file>index.jsp</welcome-file>
26. </welcome-file-list>
27. </web-app>

lignes 19-22 : les url [*.html] seront diriges vers la servlet [personnes]
lignes 13-17 : la servlet [personnes] est une instance de la classe [DispatcherServlet] de Spring. Les url [*.html] seront
donc traites par [DispatcherServlet].
lignes 8-11 : le listener [ContextLoaderListener] est charg afin dexploiter le fichier [WEBINF/applicationContext.xml]
lignes 24-26 : lapplication a une page dentre par dfaut [index.jsp] qui se trouve la racine du dossier de
lapplication web.

[index.jsp]
Cette page est prsente si un utilisateur demande directement le contexte de lapplication sans prciser durl, c.a.d. ici
[/spring-mvc-37]. 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="/list.html"/>

[index.jsp] redirige le client vers lurl [/spring-mvc-37/list.html]. Cette url affiche la liste des personnes du groupe.
[applicationContext.xml]
Ce fichier est exploit par le listener [ContextLoaderListener] avant mme que [DispatcherServlet] ne soit instanci. Son
contenu est le suivant :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.

<?xml version="1.0" encoding="ISO_8859-1"?>


<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- implmentation de la couche [dao] -->
<bean id="dao" class="istia.st.springmvc.personnes.dao.DaoImpl" init-method="init"/>
<!-- implmentation de la couche service -->
<bean id="service" class="istia.st.springmvc.personnes.service.ServiceImpl">
<property name="dao">
<ref local="dao"/>
</property>
</bean>
</beans>

[ContextLoaderListener] va instancier les beans de ce fichier. Ils correspondent aux couches [dao] (ligne 5) et [service] (lignes
7-11). Notre application web va donc utiliser une couche [dao] de type [DaoImpl] et une couche [service] de type
[ServiceImpl]. Ce sont les deux couches que nous venons dtudier et de tester.
[personnes-servlet.xml]
Ce fichier est exploit par [DispatcherServlet] une fois celui-ci instanci, mais avant quil ne serve des requtes. Il configure la
couche [web] alors que le fichier [applicationContext.xml] configure les couches [dao] et [service]. Les beans dj instancis
dans [applicationContext.xml] sont disponibles lors de linstanciation des beans de [personnes-servlet.xml]. Le contenu de ce
fichier 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.
<!-- les mappings de l'application-->
5.
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
6.
<property name="mappings">
7.
<props>
8.
<prop key="/list.html">Personnes.ListController</prop>
9.
<prop key="/delete.html">Personnes.DeleteController</prop>
10.
<prop key="/edit.html">Personnes.EditController</prop>
11.
</props>
12.
</property>
13. </bean>
14. <!-- LES CONTROLEURS -->
15. <bean id="Personnes.ListController"
16.
class="istia.st.springmvc.personnes.web.ListPersonnes">
17.
<property name="service">
18.
<ref bean="service"/>
19.
</property>
20. </bean>
springmvc - partie4, ST universit dAngers

24/43

21. <bean id="Personnes.DeleteController"


22.
class="istia.st.springmvc.personnes.web.DeletePersonne">
23.
<property name="service">
24.
<ref bean="service"/>
25.
</property>
26. </bean>
27. <bean id="Personnes.EditController"
28.
class="istia.st.springmvc.personnes.web.EditPersonne">
29.
<property name="sessionForm">
30.
<value>true</value>
31.
</property>
32.
<property name="commandName">
33.
<value>personne</value>
34.
</property>
35.
<property name="validator">
36.
<ref bean="Personnes.Validator"/>
37.
</property>
38.
<property name="formView">
39.
<value>edit</value>
40.
</property>
41.
<property name="service">
42.
<ref bean="service"/>
43.
</property>
44. </bean>
45. <!-- le validateur -->
46. <bean id="Personnes.Validator"
47.
class="istia.st.springmvc.personnes.web.ValidatePersonne"/>
48. <!-- le rsolveur de vues -->
49. <bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
50.
<property name="basename">
51.
<value>vues</value>
52.
</property>
53. </bean>
54. <!-- le gestionnaire d'exceptions -->
55. <bean
56.
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
57.
<property name="exceptionAttribute">
58.
<value>exception</value>
59.
</property>
60.
<property name="defaultStatusCode">
61.
<value>200</value>
62.
</property>
63.
<property name="defaultErrorView">
64.
<value>exception</value>
65.
</property>
66. </bean>
67. <!-- le fichier des messages -->
68. <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
69.
<property name="basename">
70.
<value>messages</value>
71.
</property>
72. </bean>
73. </beans>

2.8.2

lignes 5-13 : lapplication naccepte que trois url [/list.html, /delete.html, /edit.html]
ligne 8 : lurl [/list.html] est traite par le contrleur [Personnes.ListController] dfini lignes 15-20. Ce contrleur gre
une demande de type GET.
ligne 9 : lurl [/delete.html] est traite par le contrleur [Personnes.DeleteController] dfini lignes 21-26. Ce
contrleur gre une demande de type GET.
ligne 10 : lurl [/edit.html] est traite par le contrleur [Personnes.EditController] dfini lignes 27-44. Ce contrleur
gre un formulaire et est driv de [SimpleFormController]. Le formulaire est valid par le validateur de donnes
dfini lignes 46-47.
les trois contrleurs ont besoin de la couche [service] pour remplir leur mission. Aussi leur injecte-t-on (lignes 17-19,
23-25, 41-43) le bean [service] dfini dans [applicationContext.xml].
lignes 49-53 : le rsolveur de vues est [ResourceBundleViewResolver]. Les vues seront dfinies dans
[vues.properties].
lignes 55-66 : le gestionnaire dexceptions. Il est l au cas o une exception non gre par lapplication remonterait
jusqu [DispatcherServlet]. Cest alors la vue par dfaut " exception " qui sera affiche (ligne 64).
lignes 68-72 : le gestionnaire des messages est [ResourceBundleMessageSource]. Les messages seront dfinis dans
[messages.properties].

Les vues de lapplication web

Les vues de lapplication sont dfinies dans [vues.properties] :


1.
2.

#list
list.class=org.springframework.web.servlet.view.JstlView

springmvc - partie4, ST universit dAngers

25/43

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

list.url=/WEB-INF/vues/list.jsp
#r-list
r-list.class=org.springframework.web.servlet.view.RedirectView
r-list.url=/list.html
r-list.contextRelative=true
r-list.http10Compatible=false
#edit
edit.class=org.springframework.web.servlet.view.JstlView
edit.url=/WEB-INF/vues/edit.jsp
#exception
exception.class=org.springframework.web.servlet.view.JstlView
exception.url=/WEB-INF/vues/exception.jsp

lignes 2-3 : la vue nomme "list" est associe la page JSP / JSTL [/WEB-INF/vues/list.jsp]
lignes 10-11 : la vue nomme "edit" est associe la page JSP / JSTL [/WEB-INF/vues/edit.jsp]
lignes 13-14 : la vue nomme "exception" est associe la page JSP / JSTL [/WEB-INF/vues/exception.jsp]
lignes 5-8 : la vue nomme "r-list" est associe une redirection vers lurl [/list.html] (ligne 6) relative au contexte
(ligne 7). Ce sera donc lurl [/spring-mvc-37/list.html].

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

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" %>
page isELIgnored="false" %>

cette vue reoit une cl dans son modle :

<html>
<head>
<title>Spring 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="/edit.html?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/delete.html?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/edit.html?id=-1"/>">Ajout</a>
</body>
</html>

springmvc - partie4, ST universit dAngers

26/43

la cl ${personnes} associe un objet de type [ArrayList] dobjets de type [Personne]

lignes 23-35 : on parcourt la liste ${personnes} pour afficher un tableau HTML contenant les personnes du groupe.
ligne 32 : lurl pointe par le lien [Modifier] est paramtre par le champ [id] de la personne courante afin que le
contrleur associ lurl [/edit.html] sache quelle est la personne modifier.
ligne 33 : il est fait de mme pour le lien [Supprimer].
ligne 29 : 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] :

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


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.

<%@
<%@
<%@
<%@

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/spring.tld" prefix="spring" %>
page isELIgnored="false" %>

<html>
<head>
<title>Spring-mvc : Personnes</title>
</head>
<body background="../ressources/standard.jpg">
<h2>Ajout/Modification d'une personne</h2>
<spring:bind path="personne">
<c:if test="${status.error}">
<h3>Les erreurs suivantes se sont produites :</h3>
<ul>
<c:forEach items="${status.errorMessages}" var="erreur">
<li><c:out value="${erreur}"/></li>
</c:forEach>

springmvc - partie4, ST universit dAngers

27/43

19.
</ul>
20.
<hr>
21.
</c:if>
22.
</spring:bind>
23.
<form method="post" action="<c:url value="/edit.html"/>">
24.
<table border="1">
25.
<tr>
26.
<td>Id</td>
27.
<td>${personne.id}</td>
28.
</tr>
29.
<tr>
30.
<td>Version</td>
31.
<td>${personne.version}</td>
32.
</tr>
33.
<tr>
34.
<td>Pr&eacute;nom</td>
35.
<spring:bind path="personne.prenom">
36.
<td>
37.
<input type="text" value="${status.value}" name="${status.expression}" size="20">
38.
</td>
39.
<td>${status.errorMessage}</td>
40.
</spring:bind>
41.
</tr>
42.
<tr>
43.
<td>Nom</td>
44.
<spring:bind path="personne.nom">
45.
<td>
46.
<input type="text" value="${status.value}" name="${status.expression}" size="20">
47.
</td>
48.
<td>${status.errorMessage}</td>
49.
</spring:bind>
50.
</tr>
51.
<tr>
52.
<td>Date de naissance (JJ/MM/AAAA)</td>
53.
<spring:bind path="personne.dateNaissance">
54.
<td>
55.
<input type="text" value="${status.value}" name="${status.expression}">
56.
</td>
57.
<td>${status.errorMessage}</td>
58.
</spring:bind>
59.
</tr>
60.
<tr>
61.
<td>Mari&eacute;</td>
62.
<td>
63.
<c:choose>
64.
<c:when test="${personne.marie}">
65.
<input type="radio" name="marie" value="true" checked>Oui
66.
<input type="radio" name="marie" value="false">Non
67.
</c:when>
68.
<c:otherwise>
69.
<input type="radio" name="marie" value="true">Oui
70.
<input type="radio" name="marie" value="false" checked>Non
71.
</c:otherwise>
72.
</c:choose>
73.
</td>
74.
</tr>
75.
<tr>
76.
<td>Nombre d'enfants</td>
77.
<spring:bind path="personne.nbEnfants">
78.
<td>
79.
<input type="text" value="${status.value}" name="${status.expression}">
80.
</td>
81.
<td>${status.errorMessage}</td>
82.
</spring:bind>
83.
</tr>
84.
</table>
85.
<br>
86.
<input type="hidden" value="${personne.id}" name="id">
87.
<input type="submit" value="Valider">
88.
<a href="<c:url value="/list.html"/>">Annuler</a>
89.
</form>
90. </body>
91. </html>

cette vue reoit une cl dans son modle :

la cl ${personne} qui est un objet de type [Personne] qui reprsente soit une personne ajouter (id= -1), soit
une personne modifier (id<> -1)
lignes 12-22 : on rencontre ici une balise <spring:bind> connue. Ce qui lest moins, cest la valeur de son attribut
path, ici [personne]. Nous avions rencontr des balises <spring:bind> o lattribut path avait pour valeur un champ
du conteneur de saisies mais jamais le conteneur lui-mme. Certaines erreurs sont lies non pas un champ du
conteneur, mais au traitement du conteneur lui-mme. La balise <spring:bind> avec un attribut path ayant pour valeur
le conteneur de saisies permet davoir accs ce type derreurs. Entre les balises douverture et de fermeture de la
balise, la variable ${status} nous donne les informations suivantes :

springmvc - partie4, ST universit dAngers

28/43

${status.error} : un boolen qui indique sil existe des erreurs lies au conteneur lui-mme
${status.errorMessages} : la liste de ces erreurs
lignes 16-18 : la boucle daffichage des erreurs de niveau conteneur
ligne 23 : le formulaire sera post lurl /edit.html
ligne 27 : le champ [id] de lobjet [Personne] est affich
ligne 31 : le champ [version]
lignes 35-40 : saisie du prnom de la personne au sein dune balise <spring:bind> :

lors de laffichage initial du formulaire (GET), ${status.value} affiche la valeur actuelle du champ [prenom] de
lobjet [Personne] et ${status.errorMessage} est vide.

en cas derreur aprs le POST, on raffiche la valeur saisie ${status.value} ainsi que le message derreur ventuel
${status.errorMessage}
lignes 44-49 : saisie du nom de la personne
lignes 53-58 : saisie de la date de naissance de la personne
lignes 63-72 : 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 77-82 : saisie du nombre denfants de la personne
ligne 86 : un champ HTML cach nomm [id] et ayant pour valeur le champ [id] de la personne en cours ddition, -1
pour un ajout, autre chose pour une modification.
ligne 87 : le bouton [Valider] de type [Submit] du formulaire
ligne 88 : 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 jusqu
[DispatcherServlet]. La vue est gnre par le gestionnaire dexceptions dclar dans [personnes-servlet.xml].
Par exemple, supprimons une personne qui nexiste pas dans le groupe :

la vue [list.jsp] - il ny a pas de personne did=7

la vue [exception.jsp] on a demand la suppression


de la personne did=7 en tapant la main lurl dans le
navigateur.

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.

<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>


<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ page isELIgnored="false" %>

cette vue reoit une cl dans son modle :

la cl ${exception} associe un objet de type [Exception] qui est lexception qui est remonte jusqu
[DispatcherServlet]

ligne 12 : le texte de lexception est affich


ligne 14 : on propose lutilisateur un lien pour revenir la liste des personnes

<html>
<head>
<title>Spring MVC - personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>Erreur</h2>
L'exception suivante s'est produite :
<c:out value="${exception.message}"/>
<br><br>
<a href="<c:url value="/list.html"/>">Retour la liste</a>
</body>
</html>

springmvc - partie4, ST universit dAngers

29/43

2.8.3

Les contrleurs de lapplication web

Les contrleurs sont dfinis dans le paquetage [istia.st.springmvc.personnes.web] :

Le contrleur [ListPersonnes]
La configuration de ce contrleur dans [personnes-servlet.xml] est la suivante :
1. <!-- les mappings de l'application-->
2. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
3.
<property name="mappings">
4.
<props>
5.
<prop key="/list.html">Personnes.ListController</prop>
6....
7.
</props>
8.
</property>
9. </bean>
10. <!-- LES CONTROLEURS -->
11. <bean id="Personnes.ListController"
12. class="istia.st.springmvc.personnes.web.ListPersonnes">
13. <property name="service">
14.
<ref bean="service"/>
15. </property>
16.</bean>

Ci-dessus, on voit que lurl [/list.html] va tre traite par le contrleur [ListPersonnes] (ligne 12). Ce contrleur doit afficher la
liste des personnes du groupe.

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.

package istia.st.springmvc.personnes.web;
import java.util.HashMap;
import java.util.Map;
import istia.st.springmvc.personnes.service.IService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class ListPersonnes implements Controller {
// service
IService service;
public IService getService() {
return service;
}
public void setService(IService service) {
this.service = service;
}
// gestion de la requte

springmvc - partie4, ST universit dAngers

30/43

28. public ModelAndView handleRequest(HttpServletRequest request,


29.
HttpServletResponse response) throws Exception {
30.
// le modle de la vue [list]
31.
Map model = new HashMap();
32.
model.put("personnes", service.getAll());
33.
// rsultat
34.
return new ModelAndView("list", model);
35. }
36.
37. }

lignes 17-25 : le contrleur a une rfrence sur la couche [service]. Le contrleur est instanci au moment de la
cration par [DispatcherServlet] des beans du fichier [personnes-servlet.xml]. Les lignes 11-15 de ce fichier ci-dessus
montrent que la proprit [service] du contrleur est initialise avec un bean nomm [service]. Celui-ci a t cr par
[ContextLoaderListener] lorsquil a exploit le fichier [applicationContext.xml] (cg paragraphe 2.8.1, page 24).
ligne 14 : la classe implmente linterface [Controller] qui na quune mthode [handleRequest].
lignes 28-35 : implmentation de la mthode [handleRequest].
ligne 31 : un modle vide est cr
ligne 32 : on demande la couche [service] la liste des personnes du groupe et on met celle-ci dans le modle sous la
cl " personnes ".
ligne 34 : on retourne un [ModelAndView] o " list " est le nom de la vue afficher et le modle de cette vue celui qui
vient dtre construit.

La vue " list " va tre affiche. Elle est associe la page [list.jsp] dcrite page 26.
Le contrleur [DeletePersonne]
La configuration de ce contrleur dans [personnes-servlet.xml] est la suivante :
1. <!-- les mappings de l'application-->
2.
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
3.
<property name="mappings">
4.
<props>
5.
<prop key="/delete.html">Personnes.DeleteController</prop>
6. ...
7.
</props>
8.
</property>
9.
</bean>
10. <!-- LES CONTROLEURS -->
11. ...
12. <bean id="Personnes.DeleteController"
13.
class="istia.st.springmvc.personnes.web.DeletePersonne">
14.
<property name="service">
15.
<ref bean="service"/>
16.
</property>
17. </bean>

Ci-dessus, on voit que lurl [/delete.html] doit tre traite par le contrleur [DeletePersonne] (ligne 13). Cette url est celle des
liens [Supprimer] de la vue [list.jsp] :

dont le code est le suivant :


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

...
<html>
<head>
<title>Spring 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="/edit.html?id=${personne.id}"/>">Modifier</a></td>

springmvc - partie4, ST universit dAngers

31/43

12.
<td><a href="<c:url value="/delete.html?id=${personne.id}"/>">Supprimer</a></td>
13.
</tr>
14.
</c:forEach>
15.
</table>
16.
<br>
17.
<a href="<c:url value="/edit.html?id=-1"/>">Ajout</a>
18. </body>
19. </html>

Ligne 12, on voit lurl [/delete.html?id=XX] du lien [Supprimer]. Le contrleur [DeletePersonne] 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.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.

package istia.st.springmvc.personnes.web;

ce qui a t dit pour [ListPersonnes] peut tre repris lidentique ici.


lignes 25-32 : implmentation de la mthode [handleRequest] de linterface [Controller]
ligne 28 : lurl traite est de la forme [/delete.html?id=XX]. On rcupre la valeur [XX] du paramtre [id].
ligne 30 : 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. Elle remontera donc jusqu [DispatcherServlet] qui
appellera alors le gestionnaire dexceptions. Celui-ci fera afficher sa page par dfaut. Nous avons montr ce cas dans
ltude de la vue [exception.jsp], page 29 :

ligne 32 : si la suppression a eu lieu (pas dexception), on retourne un [ModelAndView] o "r-list" est le nom de la
vue. Il ny a pas de modle. Dans [vues.properties], la vue [r-list] est dfinie comme suit :

1.
2.
3.
4.
5.

#r-list
r-list.class=org.springframework.web.servlet.view.RedirectView
r-list.url=/list.html
r-list.contextRelative=true
r-list.http10Compatible=false

import istia.st.springmvc.personnes.service.IService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.RequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
public class DeletePersonne implements Controller {
// service
IService service;
public IService getService() {
return service;
}
public void setService(IService service) {
this.service = service;
}
// on gre la requte
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
// on rcupre l'id de la personne supprimer
int id = RequestUtils.getIntParameter(request, "id", 0);
// on supprime la personne
service.deleteOne(id);
// on redirige vers la liste des personnes
return new ModelAndView("r-list");
}
}

Cest donc une redirection vers lurl [/list.html] qui est faite : la liste des personnes du groupe va tre raffiche sans
la personne qui vient dtre supprime.
springmvc - partie4, ST universit dAngers

32/43

Le contrleur [EditPersonne]
La configuration de ce contrleur dans [personnes-servlet.xml] est la suivante :
1.<!-- les mappings de l'application-->
2. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
3.
<property name="mappings">
4.
<props>
5...
6.
<prop key="/edit.html">Personnes.EditController</prop>
7.
</props>
8.
</property>
9. </bean>
10. <!-- LES CONTROLEURS -->
11.....
12. <bean id="Personnes.EditController"
13. class="istia.st.springmvc.personnes.web.EditPersonne">
14....
15. </bean>

Ci-dessus, on voit que lurl [/edit.html] doit tre traite par le contrleur [EditPersonne] (lignes 6 et 13). Cette url est celle des
liens [Modifier] et celui du lien [Ajout] de la vue [list.jsp] :

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>Spring 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="/edit.html?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/delete.html?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/edit.html?id=-1"/>">Ajout</a>
</body>
</html>

Ligne 11, on voit lurl [/edit.html?id=XX] du lien [Modifier]. Le contrleur [EditPersonne] qui doit traiter cette url doit grer
un formulaire ddition de la personne did=XX.
Si id= -1, il sagit dun ajout et le formulaire est prsent vide. Ce sera le cas si lutilisateur utilise le lien [Ajout] de la ligne 17
ci-dessus.

springmvc - partie4, ST universit dAngers

33/43

Si id<> -1, il sagit dune modification et le formulaire doit tre prsent pr-rempli avec les informations de la personne
modifier. Ce sera le cas si lutilisateur utilise le lien [Modifier] de la ligne 11 de la page [list.jsp] :

Le contrleur [EditPersonne] drive de [SimpleFormController]. Sa configuration dans [personnes-servlet.xml] est donc celle
de ce contrleur :
1.
<bean id="Personnes.EditController"
2.
class="istia.st.springmvc.personnes.web.EditPersonne">
3.
<property name="sessionForm">
4.
<value>true</value>
5.
</property>
6.
<property name="commandName">
7.
<value>personne</value>
8.
</property>
9.
<property name="validator">
10.
<ref bean="Personnes.Validator"/>
11.
</property>
12.
<property name="formView">
13.
<value>edit</value>
14.
</property>
15.
<property name="service">
16.
<ref bean="service"/>
17.
</property>
18. </bean>
19. <!-- le validateur -->
20. <bean id="Personnes.Validator"
21. class="istia.st.springmvc.personnes.web.ValidatePersonne"/>

lignes 12-14 : la vue associe au formulaire se nomme " edit ". Nous savons quelle correspond la page JSP [edit.jsp]
tudie page 27.
la configuration ne prcise pas le paramtre [commandClass] qui fixe le conteneur dont les champs servent :
1. alimenter le formulaire lorsque celui-ci est affich au moment du GET
2. recevoir les valeurs postes au moment du POST
Lorsque le paramtre [commandClass] est dfini, le conteneur des saisies est cr au moment du GET par appel du
constructeur par dfaut de la classe associe, ce qui fait que le formulaire est affich vide. Cette mthode conviendrait
ici pour lajout dune personne [/edit.html?id=-1]. Pour la modification dune personne existante [/edit.html?id=XX],
au moment du GET, on doit afficher un formulaire pr-rempli avec les informations de la personne que lutilisateur
veut modifier. La mthode prcdente ne convient alors pas. En labsence du paramtre [commandClass], le conteneur
doit tre fourni par la mthode [formBackingObject] du contrleur. Cest ce qui sera fait ici. La mthode
[formBackingObject] fournira un objet [Personne] vide si lurl traiter est [/edit.html?id=-1], o lobjet [Personne]
did=XX si lurl traiter est [/edit.html?id=XX]. Dans ce dernier cas, lobjet [Personne] sera demand la couche
[service].

lignes 6-8 : le conteneur des saisies sera dans le modle de la vue " edit " sous le nom " personne "

springmvc - partie4, ST universit dAngers

34/43

lignes 3-5 : lobjet [Personne] cr au moment du GET par la mthode [formBackingObject] du contrleur restera en
session pour tre rutilis au moment du POST.
lignes 9-11 : lorsque le formulaire sera post, il sera vrifi par le validateur [ValidatePersonne] dfini lignes 20-21. Il
sagira de vrifier la validit des diffrents champs de lobjet [Personne] post.
le contrleur [EditPersonne] a besoin davoir accs la couche [service] (lecture puis sauvegarde dune personne
modifier par exemple). Le contrleur dtient une rfrence sur la couche [service] qui est initialise par les lignes 1518.

Le code du contrleur 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.
68.
69.
70.

package istia.st.springmvc.personnes.web;
import
import
import
import

java.text.SimpleDateFormat;
istia.st.springmvc.personnes.dao.DaoException;
istia.st.springmvc.personnes.entites.Personne;
istia.st.springmvc.personnes.service.IService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import
import
import
import
import
import
import

org.springframework.beans.propertyeditors.CustomDateEditor;
org.springframework.validation.BindException;
org.springframework.web.bind.RequestUtils;
org.springframework.web.bind.ServletRequestDataBinder;
org.springframework.web.servlet.ModelAndView;
org.springframework.web.servlet.mvc.SimpleFormController;
org.springframework.web.servlet.view.RedirectView;

public class EditPersonne extends SimpleFormController {


// service
IService service;
public IService getService() {
return service;
}
public void setService(IService service) {
this.service = service;
}
// enregistrement d'diteurs de proprits
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
// format attendu pour la date e naissance
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
// format strict
dateFormat.setLenient(false);
// on enregistre un diteur de proprits String (dd/MM/yyyy) -> Date
// CustomDateEditor est fourni par Spring - il sera utilis par Spring
// pour transformer
// la chane saisie dans le formulaire en type java.util.Date
// la date ne pourra tre vide (2ime paramtre de CustomDateEditor)
binder.registerCustomEditor(java.util.Date.class, null,
new CustomDateEditor(dateFormat, false));
}
// prparation [Personne] afficher
protected Object formBackingObject(HttpServletRequest request) {
// on rcupre l'id de la personne
int id = RequestUtils.getIntParameter(request, "id", -1);
// 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 rend l'objet [Personne]
return personne;
}
// excution de la commande
protected ModelAndView onSubmit(HttpServletRequest request, HttpServletResponse response, Object
command, BindException errors) throws Exception {
// sauvegarde la personne ajoute ou modifie
Personne personne = (Personne) command;
int idPersonne = personne.getId();
try {

springmvc - partie4, ST universit dAngers

35/43

71.
// sauvegarde de la personne
72.
service.saveOne(personne);
73.
// on redirige vers la liste des personnes
74.
return new ModelAndView("r-list");
75.
} catch (DaoException ex) {
76.
// on note l'erreur
77.
String message=idPersonne==-1 ? "personne.ajout.echec" : "personne.modification.echec";
78.
errors.reject(message,new Object[]{ex.getMessage()},"Echec de la mise jour: {0}");
79.
// on raffiche le formulaire
80.
return showForm(request,response, errors);
81.
}
82. }
83.
84. }

ligne 19 : la classe [EditPersonne] drive de [SimpleFormController]


lignes 21-29 : lattribut [service] initialis lors de linstanciation du contrleur par [DispatcherServlet]. Nous en avons
parl un peu plus haut. Cet attribut est une rfrence sur la couche [service] de larchitecture 3tier de lapplication.
lignes 32-45 : la mthode [initBinder] est utilise ici pour la date de naissance de la personne. Nous voulons tre
capables de faire la conversion [String] (valeur saisie au format JJ/MM/AAAA) vers le type [java.util.Date]. Pour
cela, il nous faut enregistrer un diteur de proprit particulier. Nous avons rencontr ce cas dans un autre exemple de
larticle 2 (paragraphe 5.11). Nous ny revenons pas.
lignes 48-64 : la mthode [formBackingObject] qui, au moment du GET initial sur le formulaire, rend une rfrence
sur le conteneur de saisies, c.a.d. sur un objet [Personne].
le GET a pour cible une url du type [/edit.html?id=XX]. Ligne 50, 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 55, 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 58-59.
La mthode [formBackingObject] rend lobjet [Personne] cr. Ce sera le conteneur de saisies associ au formulaire
[edit.jsp].

lignes 66-82 : lune des mthodes [submit] que lon peut redfinir pour traiter le POST du formulaire. Rappelons
quentre-temps, celui a t vrifi par le validateur de donnes et quil a t dclar correct sinon on ne serait pas
arriv l. Nous avons choisi ici, une mthode [submit] (il en existe plusieurs avec diffrentes signatures) permettant de
signaler des erreurs. Cest le paramtre [BindException errors] qui va enregistrer celles-ci.

ligne 68 : on rcupre le conteneur de saisies de type [Personne]. Il contient la personne modifie ou la


personne ajoute.

ligne 69 : on rcupre lid de la personne

ligne 72 : 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 74 : sil ny a pas eu dexception, on redirige le client vers lurl [/list.html] pour lui prsenter le nouvel tat
du groupe. Pour cela, on renvoie un [ModelAndView] demandant laffichage de la vue " r-list " qui savre tre
une redirection (cf vues.properties).

lignes 77-78 : sil y a eu exception, on cre le message derreur adquat et on lajoute la liste des erreurs
[BindException errors] qui nous a t passe en paramtre (ligne 78). On utilise pour cela la mthode [reject] de
linterface [BindException] qui lie lerreur au conteneur de saisies plutt qu un champ particulier de celui-ci.

ligne 80 : on redemande le raffichage du formulaire initial. La mthode [showForm] renvoie un


[ModelAndView] o la vue est celle nomme [formView] dans la configuration du contrleur et o le modle
contient le conteneur de saisies qui a t refus et quelle trouve dans la session (sessionForm=true) ainsi que la
liste des erreurs [BindException errors] quon lui passe en paramtre. Nous savons qualors la page [edit.jsp] va
tre utilise pour afficher ce modle. Rappelons le code qui affiche la liste des erreurs de niveau conteneur :
<spring:bind path="personne">
<c:if test="${status.error}">
<h3>Les erreurs suivantes se sont produites :</h3>
<ul>
<c:forEach items="${status.errorMessages}" var="erreur">
<li><c:out value="${erreur}"/></li>
</c:forEach>
</ul>
<hr>
</c:if>
</spring:bind>

Nous avons dit quau moment du POST, les saisies taient contrles pas un validateur de donnes avant dtre passes la
mthode [onSubmit] ci-dessus. Nous voyons maintenant ce validateur de donnes.
springmvc - partie4, ST universit dAngers

36/43

Le validateur [ValidatePersonne]
Celui-ci est li par configuration au contrleur [EditPersonne] :
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.

<bean id="Personnes.EditController"
class="istia.st.springmvc.personnes.web.EditPersonne">
...
<property name="validator">
<ref bean="Personnes.Validator"/>
</property>
...
</bean>
<!-- le validateur -->
<bean id="Personnes.Validator"
class="istia.st.springmvc.personnes.web.ValidatePersonne"/>

[ValidatePersonne] doit sassurer que lobjet [Personne] qui est post par le formulaire [edit.jsp] est correct. 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.

package istia.st.springmvc.personnes.web;

lignes 7-8 : [ValidatePersonne] implmente linterface [Validator] et doit donc, ce titre, implmenter les mthodes
[supports] et [validate].
lignes 10-12 : la mthode [supports] : [ValidatePersonne] sert contrler la validit des objets [Personne] ou drivs.
lignes 14-35 : la mthode [validate]. Des vrifications basiques sont faites sur le prnom saisi (lignes 18-22), le nom
(lignes 24-28), le nombre denfants (lignes 30-34).

import istia.st.springmvc.personnes.entites.Personne;
import org.springframework.validation.Errors;
public class ValidatePersonne implements
org.springframework.validation.Validator {
public boolean supports(Class classe) {
return classe.isAssignableFrom(Personne.class);
}

public void validate(Object obj, Errors erreurs) {


// on rcupre la personne poste
Personne personne = (Personne) obj;
// on vrifie le prnom
String prnom = personne.getPrenom();
if (prnom==null || prnom.trim().length() == 0) {
erreurs.rejectValue("prenom", ,
"Le prnom est ncessaire !");
}
// on vrifie le nom
String nom = personne.getNom();
if (nom==null || nom.trim().length() == 0) {
erreurs.rejectValue("nom", "personne.nom.necessaire",
"Le nom est ncessaire !");
}
// on vrifie le nombre d'enfants
int nbEnfants = personne.getNbEnfants();
if (nbEnfants < 0) {
erreurs.rejectValue("nbEnfants", "personne.nbEnfants.invalide",
"Donne incorrecte !");
}
}

Le fichier [messages.properties] des messages derreurs


Les messages derreur associs aux codes derreur tels que "personne.prenom.necessaire", "personne.nom.necessaire", ... quon
trouve dans la mthode [validate] du validateur ainsi que dans la mthode [onSubmit] du contrleur [EditPersonne] sont dfinis
dans le fichier [messages.properties]. Ceci est fix par configuration dans [personnes-servlet.xml] :
<!-- le fichier des messages -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>messages</value>
</property>
</bean>

Le contenu de [messages.properties] est le suivant :


springmvc - partie4, ST universit dAngers

37/43

personne.prenom.necessaire=Le nom est obligatoire !


personne.nom.necessaire=Le prnom est obligatoire !
personne.nbEnfants.invalide=Donne incorrecte !
typeMismatch=Donne incorrecte !
personne.ajout.echec=Echec de l'ajout : {0}
personne.modification.echec=Echec de la modification : {0}

2.9

Les tests de lapplication web

Un certain nombre de tests ont t prsents au paragraphe 2.1, page 3. 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 demande lurl [http://localhost:8080/spring-mvc-37/list.html] :

[IE] sera le navigateur de lutilisateur U2. Celui demande la mme Url :

Lutilisateur U1 entre en modification de la personne [Lemarchand] :

Lutilisateur U2 fait de mme :

springmvc - partie4, ST universit dAngers

38/43

Lutilisateur U1 fait des modifications et valide :

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 :

springmvc - partie4, ST universit dAngers

39/43

Il trouve la personne [Lemarchand] telle que U1 la modifie. Maintenant U2 supprime [Lemarchand] :

- Suppression

- rponse du serveur

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

2.10

Mise en archives de lapplication web

Nous avons dvelopp un projet Eclipse / Tomcat dune application 3tier :

springmvc - partie4, ST universit dAngers

40/43

Couche Interface Utilisateur[ui-web]


utilisateur

DispatcherServlet
1

Couche service
[service]

2
3

Controller
4

View

Couche d'accs
aux donnes
[dao]

Donnes

Modle Map
6

Dans une nouvelle 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 / Tomcat [spring-mvc-37B], copie du
projet prcdent [spring-mvc-37] mais o les couches [web, service, dao] on t mises dans des archives .jar :

Le dossier [WEB-INF/src] est dsormais vide. Il contenait le code source des classes Java ainsi que les fichiers [*.properties]
qui configuraient la couche [web]. Ces lments sont passs dans les trois archives [personnes-*.jar ] :

archive de la couche [service]


archive de la couche [dao]
archive de la couche [web]
springmvc - partie4, ST universit dAngers

41/43

Le projet Eclipse / Tomcat [spring-mvc-37B] a t configur pour inclure les trois archives [personnes-*.jar ] dans son
ClassPath. Pour le tester, nous demandons lurl [http://localhost:8080/spring-mvc-37B/list.html] :

Le lecteur est invit faire des tests complmentaires. On pourra noter que si on a eu cet affichage, cest que le fichier des vues
[vues.properties] a t correctement exploit. Or il est dans larchive [personnes-web.jar]. On sait que ce fichier est cherch
dans le ClassPath de lapplication web et que larchive [personnes-web.jar] fait partie de celui-ci.
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 larchive de
la couche [web].

3 Conclusion
Dans cet article nous avons mis en oeuvre Spring 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 prcdents
articles.
Dans la version tudie, la liste des personnes tait maintenue en mmoire. Le prochain article prsentera une version o cette
liste sera maintenue dans une table de base de donnes.

4 Le code de l'article
Comme pour les prcdents articles, le lecteur trouvera le code des exemples de ce document sous la forme d'un fichier zipp
sur le site de l'article. Les rgles de dploiement du fichier zipp sont relire dans l'article 1. Une fois un projet import dans
[Eclipse] :

copier le contenu du dossier [lib] du zip dans le dossier [WEB-INF/lib] du projet

s'assurer que le dossier [work] existe sinon le crer : [clic droit sur projet / Projet Tomcat / Crer le dossier work]

nettoyer le projet [Project / clean / clean selected projects]

springmvc - partie4, ST universit dAngers

42/43

Table des matires


1RAPPELS..................................................................................................................................................................................... 2
2SPRING MVC DANS UNE ARCHITECTURE 3TIER EXEMPLE 1................................................................................ 2
2.1PRSENTATION...............................................................................................................................................................................2
2.2LE PROJET ECLIPSE / TOMCAT....................................................................................................................................................... 4
2.3LA REPRSENTATION DUNE PERSONNE.............................................................................................................................................5
2.4LA COUCHE [DAO]..........................................................................................................................................................................7
2.5TESTS DE LA COUCHE [DAO]..........................................................................................................................................................11
2.6LA COUCHE [SERVICE]..................................................................................................................................................................18
2.7TESTS DE LA COUCHE [SERVICE]....................................................................................................................................................19
2.8LA COUCHE [WEB]....................................................................................................................................................................... 22
2.8.1CONFIGURATION DE LAPPLICATION WEB.........................................................................................................................................23
2.8.2LES VUES DE LAPPLICATION WEB..................................................................................................................................................25
2.8.3LES CONTRLEURS DE LAPPLICATION WEB..................................................................................................................................... 30
2.9LES TESTS DE LAPPLICATION WEB.................................................................................................................................................38
2.10MISE EN ARCHIVES DE LAPPLICATION WEB.................................................................................................................................. 40
3CONCLUSION.......................................................................................................................................................................... 42
4LE CODE DE L'ARTICLE...................................................................................................................................................... 42

springmvc - partie4, ST universit dAngers

43/43

Vous aimerez peut-être aussi