14. Applicazione web MVC in un’architettura a 3 livelli – Esempio 1
14.1. Présentation
Finora ci siamo limitati a esempi a scopo didattico. Per questo motivo, dovevano essere semplici. Presentiamo ora un’applicazione di base, ma comunque più ricca di tutte quelle presentate finora. Avrà la particolarità di utilizzare i tre livelli di un’architettura a tre livelli:

Il lettore è invitato a rileggere i principi di un’applicazione web MVC in un’architettura a tre livelli, se li ha dimenticati, al paragrafo 4.
L’applicazione web che scriveremo consentirà di gestire un gruppo di persone con quattro operazioni:
- elenco delle persone del gruppo
- aggiunta di una persona al gruppo
- modifica di una persona del gruppo
- eliminazione di una persona dal gruppo
Queste quattro operazioni corrispondono alle operazioni di base su una tabella di un database. Scriveremo due versioni di questa applicazione:
- nella versione 1, il livello [dao] non utilizzerà alcun database. Le persone del gruppo saranno memorizzate in un semplice oggetto [ArrayList] gestito internamente dal livello [dao]. Ciò consentirà al lettore di testare l’applicazione senza vincoli legati al database.
- Nella versione 2, inseriremo il gruppo di persone in una tabella del database. Dimostreremo che ciò avverrà senza alcun impatto sul livello web della versione 1, che rimarrà invariato.
e schermate che seguono mostrano le pagine che l’applicazione scambia con l’utente.



![]() |
![]() |
14.2. Il progetto Eclipse
Il progetto dell'applicazione si chiama [personnes-01]:

Questo progetto comprende i tre livelli dell’architettura a tre livelli dell’applicazione:
![]() |
- il livello [dao] è contenuto nel pacchetto [istia.st.mvc.personnes.dao]
- il livello [metier] o [service] è contenuto nel pacchetto [istia.st.mvc.personnes.service]
- il livello [web] o [ui] è contenuto nel pacchetto [istia.st.mvc.personnes.web]
- il pacchetto [istia.st.mvc.personnes.entites] contiene gli oggetti condivisi tra diversi livelli
- il pacchetto [istia.st.mvc.personnes.tests] contiene i test JUnit dei livelli [dao] e [service]
Esploreremo in successione i tre livelli [dao], [service] e [web]. Poiché sarebbe troppo lungo da scrivere e forse troppo noioso da leggere, a volte potremmo essere un po’ concisi nelle spiegazioni, tranne quando ciò che viene presentato è nuovo.
14.3. La rappresentazione di una persona
L’applicazione gestisce un gruppo di persone. Le schermate del paragrafo 14.1 hanno mostrato alcune delle caratteristiche di una persona. Formalmente, queste sono rappresentate da una classe [Personne]:
![]()
La classe [Personne] è la seguente:
- una persona è identificata dalle seguenti informazioni:
- id: un numero che identifica in modo univoco una persona
- cognome: il cognome della persona
- nome: il suo nome
- dateNaissance: la sua data di nascita
- stato civile: se è sposata o meno
- nbEnfants: il numero dei suoi figli
- L'attributo [version] è un attributo aggiunto artificialmente per le esigenze dell'applicazione. Da un punto di vista orientato agli oggetti, sarebbe stato senza dubbio preferibile aggiungere questo attributo in una classe derivata da [Personne]. La sua necessità emerge quando si analizzano i casi d’uso dell’applicazione web. Uno di questi è il seguente:
Al momento T1, un utente U1 accede alla modifica di una persona P. In quel momento, il numero di figli è 0. L'utente modifica tale numero portandolo a 1, ma prima che possa confermare la modifica, un altro utente U2 accede alla modifica della stessa persona P. Poiché U1 non ha ancora confermato la propria modifica, U2 vede il numero di figli pari a 0. U2 mette il nome della persona P in maiuscolo. Successivamente, U1 e U2 confermano le loro modifiche in quest’ordine. Sarà la modifica di U2 a prevalere: il nome verrà scritto in maiuscolo e il numero di figli rimarrà pari a zero, anche se U1 ritiene di averlo modificato in 1.
Il concetto di versione della persona ci aiuta a risolvere questo problema. Riprendiamo lo stesso caso d’uso:
All’ora T1, un utente U1 accede alla modifica di una persona P. In quel momento, il numero di figli è 0 e la versione è V1. Modifica il numero di figli portandolo a 1, ma prima che possa confermare la modifica, un utente U2 accede per modificare la stessa persona P. Poiché U1 non ha ancora confermato la modifica, U2 vede il numero di figli pari a 0 e la versione V1. U2 scrive il nome della persona P in maiuscolo. Successivamente, U1 e U2 convalidano le proprie modifiche in questo ordine. Prima di convalidare una modifica, si verifica che chi modifica una persona P possieda la stessa versione della persona P attualmente registrata. Questo sarà il caso dell’utente U1. La sua modifica viene quindi accettata e si cambia la versione della persona modificata da V1 a V2 per segnalare che la persona ha subito una modifica. Al momento della convalida della modifica di U2, si noterà che possiede una versione V1 della persona P, mentre attualmente la versione di quest’ultima è V2. A quel punto sarà possibile comunicare all’utente U2 che qualcuno lo ha preceduto e che deve ripartire dalla nuova versione della persona P. Egli lo farà, recupererà una persona P con versione V2 che ora ha un figlio, scriverà il nome in maiuscolo e confermerà. La sua modifica sarà accettata se la persona P registrata ha ancora la versione V2. Alla fine, le modifiche apportate da U1 e U2 saranno prese in considerazione, mentre nel caso d’uso senza versione, una delle modifiche andava persa.
- righe 32-40: un costruttore in grado di inizializzare i campi di una persona. Si omette il campo [version].
- righe 43-51: un costruttore che crea una copia della persona che gli viene passata come parametro. Si ottengono così due oggetti con contenuto identico ma referenziati da due puntatori diversi.
- riga 55: il metodo [toString] viene ridefinito per restituire una stringa di caratteri che rappresenta lo stato della persona
14.4. Il livello [dao]
Il livello [dao] è costituito dalle seguenti classi e interfacce:
![]()
- [IDao] è l’interfaccia presentata dal livello [dao]
- [DaoImpl] è un'implementazione di quest'ultima in cui il gruppo di persone è incapsulato in un oggetto [ArrayList]
- [DaoException] è un tipo di eccezioni non controllate (unchecked), generate dal livello [dao]
L’interfaccia [IDao] è la seguente:
- L’interfaccia dispone di quattro metodi per le quattro operazioni che si desidera eseguire sul gruppo di persone:
- getAll: per ottenere una raccolta di persone
- getOne: per ottenere una persona con un id specifico
- saveOne: per aggiungere una persona (id=-1) o modificare una persona esistente (id <> -1)
- deleteOne: per eliminare una persona con un id specifico
Il livello [dao] potrebbe generare delle eccezioni. Queste saranno di tipo [DaoException] :
- riga 3: la classe [DaoException], derivata da [RuntimeException], è un tipo di eccezione non controllata: il compilatore non ci obbliga a:
- gestire questo tipo di eccezioni con un try/catch quando si chiama un metodo che potrebbe generarla
- inserire l’indicatore "throws DaoException" nella firma di un metodo che potrebbe generare l’eccezione
Questa tecnica ci evita di dover firmare i metodi dell’interfaccia [IDao] con eccezioni di un tipo specifico. Qualsiasi implementazione che generi eccezioni non controllate sarà quindi accettabile, garantendo così flessibilità nell’architettura.
- riga 6: un codice di errore. Il livello [dao] genererà diverse eccezioni che saranno identificate da codici di errore distinti. Ciò consentirà al livello che deciderà di gestire l’eccezione di conoscere l’origine esatta dell’errore e di adottare così le misure appropriate. Esistono altri modi per ottenere lo stesso risultato. Uno di questi consiste nel creare un tipo di eccezione per ogni possibile tipo di errore, ad esempio NomManquantException, PrenomManquantException, AgeIncorrectException, ...
- righe 13-16: il costruttore che consentirà di creare un'eccezione identificata da un codice di errore e da un messaggio di errore.
- righe 8-10: il metodo che consentirà al codice di gestione di un'eccezione di recuperarne il codice di errore.
La classe [DaoImpl] implementa l’interfaccia [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 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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
Ci limiteremo a fornire solo le linee generali di questo codice. Ci soffermeremo tuttavia un po’ sulle parti più delicate.
- riga 13: l’oggetto [ArrayList] che conterrà il gruppo di persone
- riga 16: l’identificativo dell’ultima persona aggiunta. Ad ogni nuova aggiunta, questo identificativo verrà incrementato di 1.
La classe [DaoImpl] verrà istanziata in un unico esemplare. Si tratta di un cosiddetto singleton. Un’applicazione web serve i propri utenti in modo simultaneo. In un dato momento, il server web esegue diversi thread. Questi condividono i singleton:
- quello del livello [dao]
- quello del livello [service]
- quelli dei vari controller, validatori di dati, ... del livello web
Se un singleton presenta campi privati, occorre chiedersi immediatamente perché ne abbia. Sono giustificati? Infatti, saranno condivisi tra diversi thread. Se sono di sola lettura, ciò non pone alcun problema purché possano essere inizializzati in un momento in cui si è certi che vi sia un solo thread attivo. In genere si riesce a individuare quel momento. È quello dell’avvio dell’applicazione web, quando questa non ha ancora iniziato a servire i clienti. Se sono in lettura/scrittura, allora è necessario implementare una sincronizzazione dell’accesso ai campi, altrimenti si va incontro a un disastro. Illustreremo questo problema quando testeremo il livello [dao].
- La classe [DaoImpl] non ha un costruttore. Verrà quindi utilizzato il suo costruttore predefinito.
- righe 19-38: il metodo [init] verrà chiamato al momento dell’istanziazione del singleton del livello [dao]. Esso crea un elenco di tre persone.
- righe 41-43: implementa il metodo [getAll] dell’interfaccia [IDao]. Restituisce un riferimento all’elenco delle persone.
- righe 46-55: implementa il metodo [getOne] dell’interfaccia [IDao]. Il suo parametro è l’ID della persona ricercata.
Per recuperarla, si ricorre a un metodo privato [getPosition] presente nelle righe 113-126. Questo metodo restituisce la posizione nell’elenco della persona ricercata oppure -1 se la persona non è stata trovata.
Se la persona è stata trovata, il metodo [getOne] restituisce un riferimento (riga 51) a una copia di tale persona e non alla persona stessa. Infatti, quando un utente desidera modificare una persona, le informazioni relative a quest’ultima vengono richieste al livello [dao] e inoltrate al livello [web] per la modifica, sotto forma di un riferimento a un oggetto [Personne]. Questo riferimento fungerà da contenitore per i dati inseriti nel modulo di modifica. Quando, nel livello web, l’utente invierà le proprie modifiche, il contenuto del contenitore verrà modificato. Se il contenitore è un riferimento alla persona reale del [ArrayList] del livello [dao], allora quest’ultima viene modificata anche se le modifiche non sono state applicate ai livelli [service] e [dao]. Quest’ultimo è l’unico autorizzato a gestire l’elenco delle persone. Pertanto, il livello web deve operare su una copia della persona da modificare. In questo caso, il livello [dao] fornisce tale copia.
Se la persona ricercata non viene trovata, viene generata un’eccezione di tipo [DaoException] con il codice di errore 2 (riga 53).
- righe 94-104: implementa il metodo [deleteOne] dell’interfaccia [IDao]. Il suo parametro è l’ID della persona da eliminare. Se la persona da eliminare non esiste, viene generata un’eccezione di tipo [DaoException] con codice di errore 2.
- righe 58-91: implementa il metodo [saveOne] dell'interfaccia [IDao]. Il suo parametro è un oggetto [Personne]. Se questo oggetto ha un id=-1, si tratta dell’aggiunta di una persona. In caso contrario, si tratta di modificare la persona dell’elenco con tale id utilizzando i valori del parametro.
- riga 60: la validità del parametro [Personne] viene verificata da un metodo privato [check] definito alle righe 129-155. Questo metodo esegue verifiche di base sul valore dei diversi campi di [Personne]. Ogni volta che viene rilevata un’anomalia, viene generato un [DaoException] con un codice di errore specifico. Poiché il metodo [saveOne] non gestisce questa eccezione, la segnalerà al metodo chiamante.
- riga 62: se il parametro [Personne] ha un ID pari a -1, si tratta di un’aggiunta. L’oggetto [Personne] viene aggiunto all’elenco interno delle persone (riga 66), con il primo ID disponibile (riga 64) e un numero di versione pari a 1 (riga 65).
- Se il parametro [Personne] ha un [id] diverso da -1, si tratta di modificare la persona dell’elenco interno con quel [id]. Innanzitutto, si verifica (righe 70-75) che la persona da modificare esista. In caso contrario, viene generata un’eccezione di tipo [DaoException] con il codice di errore 2.
- Se la persona è effettivamente presente, si verifica che la sua versione attuale corrisponda a quella del parametro [Personne], che contiene le modifiche da apportare all’originale. In caso contrario, significa che chi intende modificare la persona non ne possiede l’ultima versione. Glielo si comunica generando un'eccezione di tipo [DaoException] con codice di errore 3 (righe 79-80).
- Se tutto va bene, le modifiche vengono apportate all’originale della persona (righe 85-90)
È evidente che questo metodo deve essere sincronizzato. Ad esempio, tra il momento in cui si verifica che la persona da modificare sia effettivamente presente e quello in cui la modifica verrà effettivamente apportata, la persona potrebbe essere stata eliminata dall’elenco da qualcun altro. Il metodo dovrebbe quindi essere dichiarato [synchronized] per garantire che venga eseguito da un solo thread alla volta. Lo stesso vale per gli altri metodi dell’interfaccia [IDao]. Non lo facciamo, preferendo spostare questa sincronizzazione nel livello [service]. Per evidenziare i problemi di sincronizzazione, durante i test del livello [dao] interromperemo l’esecuzione di [saveOne] per 10 ms (riga 83) tra il momento in cui sappiamo di poter apportare la modifica e quello in cui la apportiamo effettivamente. Il thread che esegue [saveOne] perderà quindi il controllo del processore a favore di un altro. In questo modo aumentiamo le probabilità che si verifichino conflitti di accesso all’elenco delle persone.
14.5. Test del livello [dao]
Viene scritto un test JUnit per il livello [dao]:
![]() | ![]() |
[TestDao] è il test JUnit. Per evidenziare i problemi di accesso concorrente all’elenco delle persone, vengono creati thread di tipo [ThreadDaoMajEnfants]. Il loro compito è aumentare di 1 il numero di figli di una determinata persona.
[TestDao] comprende cinque test, da [test1] a [test5]. Ne presentiamo solo due; il lettore è invitato a scoprire gli altri nel codice sorgente associato a questo articolo.
- riga 9: riferimento all’implementazione del livello [dao] sottoposto a test
- righe 12-15: il costruttore del test JUnit. Crea un'istanza di tipo [DaoImpl] del livello [dao] da testare e la inizializza.
Il metodo [test1] verifica i quattro metodi dell’interfaccia [IDao] nel modo seguente:
- riga 3: si richiede l’elenco delle persone
- riga 6: si visualizza l'elenco
[1,1,Joachim,Major,13/01/1984,true,2]
[2,1,Mélanie,Humbort,12/01/1985,false,1]
[3,1,Charles,Lemarchand,01/01/1986,false,0]
Il test aggiunge quindi una persona, la modifica e la elimina. In questo modo vengono utilizzati i quattro metodi dell'interfaccia [IDao].
- righe 8-10: si aggiunge una nuova persona (id=-1).
- riga 11: si recupera l’ID della persona aggiunta, poiché l’aggiunta le ha assegnato un ID. Prima non ne aveva uno.
- righe 13-14: si richiede al livello [dao] una copia della persona appena aggiunta. È importante ricordare che se la persona richiesta non viene trovata, il livello [dao] genera un'eccezione. Si verificherà quindi un arresto anomalo alla riga 13. Si sarebbe potuto gestire questo caso in modo più corretto. Alla riga 14, si verifica il nome della persona trovata.
- righe 16-17: si modifica tale nome e si richiede al livello [dao] di salvare le modifiche.
- righe 19-20: si richiede al livello [dao] una copia della persona appena aggiunta e si verifica il suo nuovo nome.
- riga 22: si elimina la persona aggiunta all’inizio del test.
- righe 23-34: si richiede al livello [dao] una copia della persona appena eliminata. Si dovrebbe ottenere un [DaoException] con codice 2.
- righe 36-37: si richiede nuovamente l’elenco delle persone. Si dovrebbe ottenere lo stesso elenco presente all’inizio del test.
Il metodo [test4] mira a evidenziare i problemi di accesso concorrente ai metodi del livello [dao]. Ricordiamo che questi ultimi non sono stati sincronizzati. Il codice del test è il seguente:
- righe 3-6: si aggiunge all’elenco una persona P senza figli. Si annota il suo [id] (riga 6).
- righe 7-13: si avviano N thread. Ciascuno di essi incrementerà di 1 unità il numero di figli della persona P. Alla fine, la persona P dovrà avere N figli.
- righe 15-17: il metodo [test4] che ha avviato i N thread attende che questi abbiano completato il loro lavoro prima di verificare il nuovo numero di figli della persona P.
- righe 18-21: si recupera la persona P e si verifica che il numero dei suoi figli sia pari a N.
- righe 22-35: la persona P viene rimossa, quindi si verifica che non sia più presente nell’elenco.
Alla riga 11 si vede che i thread sono di tipo [ThreadDaoMajEnfants]. Il costruttore di questo tipo ha tre parametri:
- il nome assegnato al thread, per poterlo monitorare tramite i log
- un riferimento al livello [dao] affinché il thread possa accedervi
- l'ID della persona su cui il thread deve lavorare
Il tipo [ThreadDaoMajEnfants] è il seguente:
- riga 9: [ThreadDaoMajEnfants] è effettivamente un thread
- righe 18-22: il costruttore che inizializza il thread con tre informazioni
- il nome [name] assegnato al thread
- un riferimento [dao] al livello [dao]. Si noti che, ancora una volta, stiamo lavorando con il tipo dell’interfaccia [IDao] e non con quello dell’implementazione [DaoImpl].
- l’identificativo [id] della persona su cui il thread deve operare
Quando [test4] avvia un thread [ThreadDaoMajEnfants] (riga 12 di test4), viene eseguito il metodo [run] (riga 25) di quest’ultimo:
- righe 78-81: il metodo privato [suivi] consente di generare log a schermo. Il metodo [run] lo utilizza per consentire il monitoraggio dell’esecuzione del thread.
- il thread cercherà di incrementare di 1 il numero di figli della persona P con identificativo [id]. Questo aggiornamento potrebbe richiedere diversi tentativi. Prendiamo due thread [TH1] e [TH2]. [TH1] richiede una copia della persona P al livello [dao]. La ottiene e constata che ha la versione V1. [TH1] viene interrotto. [TH2], che lo seguiva, fa la stessa cosa e ottiene la stessa versione V1 della persona P. [TH2] viene interrotto. [TH2] riprende il controllo, incrementa il numero di figli di P e salva le sue modifiche. Sappiamo che a questo punto queste sono state salvate e che la versione di P passerà a V2. [TH1] ha terminato il proprio lavoro. [TH2] riprende il controllo e fa lo stesso. Il suo aggiornamento di P verrà rifiutato poiché possiede una copia di P della versione V1, mentre l’originale P ha ora la versione V2. [TH2] deve quindi ripetere l’intero ciclo di [lecture -> mise à jour -> sauvegarde]. Ecco perché troviamo il ciclo nelle righe 32-72. In esso, il thread:
- richiede una copia della persona P da modificare (riga 34)
- attende 10 ms (riga 43). Si tratta di un'operazione artificiale volta a interrompere il thread tra la lettura della persona P e il suo effettivo aggiornamento nell'elenco delle persone, al fine di aumentare la probabilità di conflitti.
- incrementa il numero di figli di P (riga 54) e salva P (riga 56). Se il thread non dispone della versione corretta di P, verrà generata un’eccezione dal livello [dao]. Si recupera quindi il codice dell’eccezione (riga 61) per verificare che sia effettivamente il codice 3 (versione errata di P). Se così non fosse, si rilancia l’eccezione al metodo chiamante, ovvero il metodo di test [test4]. Se si verifica l’eccezione con codice 3, si ricomincia il ciclo [lecture -> mise à jour -> sauvegarde]. Se non si verifica alcuna eccezione, l’aggiornamento è stato effettuato e il lavoro del thread è terminato.
Quali sono i risultati dei test?
Nella prima configurazione testata:
- si commenta l’istruzione di attesa nel metodo [saveOne] di [DaoImpl] (riga 83, paragrafo 14.4).
- il metodo [test4] crea 100 thread (riga 8, paragrafo 14.5).
Si ottengono i seguenti risultati:

I cinque test sono stati superati con successo.
Nella seconda configurazione testata:
- si rimuove il commento dall'istruzione di attesa nel metodo [saveOne] di [DaoImpl] (riga 83, paragrafo 14.4).
- il metodo [test4] crea 2 thread (riga 8, paragrafo 14.5).
Si ottengono i seguenti risultati:
![]() | ![]() |
Il test [test4] non è andato a buon fine. Sono stati creati due thread, ciascuno incaricato di incrementare di 1 il numero di figli di una persona P che inizialmente ne aveva 0. Ci si aspettava quindi 2 figli dopo l'esecuzione dei due thread, ma ne abbiamo solo uno.
Esaminiamo i log di schermo di [test4] per capire cosa è successo:
- riga 1: il thread n. 0 inizia il suo lavoro
- riga 2: ha recuperato una copia della persona P e rileva che il numero dei suoi figli è pari a 0
- riga 3: incontra il [Thread.sleep(10)] del proprio metodo [run] e si ferma quindi al tempo [1145536368171] (ms)
- riga 4: il thread n. 1 riprende quindi il controllo del processore e inizia il proprio lavoro
- riga 5: ha recuperato una copia della persona P e rileva che il numero dei suoi figli è pari a 0
- riga 6: incontra il [Thread.sleep(10)] del proprio metodo [run] e quindi si interrompe
- riga 7: il thread n. 0 riprende il controllo del processore al tempo [1145536368187] (ms), c.a.d, 16 ms dopo averlo perso.
- riga 8: lo stesso vale per il thread n. 1
- riga 9: il thread n. 0 ha effettuato l’aggiornamento e portato il numero di figli a 1
- riga 10: lo stesso vale per il thread n. 1
La domanda è: perché il thread n. 1 è riuscito ad aggiornare i propri dati quando, normalmente, non possedeva più la versione corretta della persona P, che era appena stata aggiornata dal thread n. 0?
Innanzitutto, si nota un’anomalia tra le righe 7 e 8: sembrerebbe che il thread n. 0 abbia perso il controllo del processore tra queste due righe a favore del thread n. 1. Cosa stava facendo in quel momento? Stava eseguendo il metodo [saveOne] del livello [dao]. Quest’ultimo ha la seguente struttura (cfr. paragrafo 14.4):
- il thread n. 0 ha eseguito [saveOne] ed è arrivato fino alla riga 8, dove è stato costretto a cedere il processore. Nel frattempo, ha letto la versione della persona P, che era 1 perché la persona P non era ancora stata aggiornata.
- Poiché il processore si era liberato, è stato il thread n. 1 a rilevarlo. A sua volta, ha eseguito [saveOne] ed è arrivato alla riga 8, dove è stato costretto a rilasciare il processore. Nel frattempo, ha letto la versione della persona P, che era 1 poiché la persona P non era ancora stata aggiornata.
- Poiché il processore era diventato libero, è stato il thread n. 0 a ereditarlo. A partire dalla riga 9, ha effettuato l’aggiornamento e ha portato il numero di figli a 1. Successivamente, il metodo [run] del thread n. 0 si è concluso e il thread ha visualizzato il log che indicava di aver portato il numero di figli a 1 (riga 9).
- Poiché il processore era tornato libero, il thread n. 1 ne ha preso il controllo. A partire dalla riga 9, ha effettuato l’aggiornamento e ha impostato il numero di figli a 1. Perché 1? Perché possiede una copia di P con un numero di figli pari a 0. È quanto indica il log (riga 5). Successivamente, il metodo [run] del thread n. 1 si è concluso e il thread ha visualizzato il log che indicava di aver impostato il numero di figli a 1 (riga 10).
Da dove deriva il problema? Deriva dal fatto che il thread n. 0 non ha avuto il tempo di confermare la propria modifica e quindi di aggiornare la versione della persona P prima che il thread n. 1 tentasse di leggere tale versione per verificare se la persona P fosse cambiata. Questo scenario è improbabile ma non impossibile. È stato necessario forzare il thread n. 0 a cedere il processore per farlo emergere con soli due thread. Senza questo espediente, la configurazione precedente non era riuscita a far emergere lo stesso caso con 100 thread. Il test [test4] era stato superato.
Qual è la soluzione? Ce ne sono senza dubbio diverse. Una di queste, semplice da implementare, consiste nel sincronizzare il metodo [saveOne]:
public synchronized void saveOne(Personne personne)
La parola chiave [synchronized] garantisce che un solo thread alla volta possa eseguire il metodo. In questo modo, il thread n. 1 sarà autorizzato a eseguire [saveOne] solo quando il thread n. 0 ne sarà uscito. Si ha quindi la certezza che la versione della persona P sarà stata modificata quando il thread n. 1 entrerà in [saveOne]. Il suo aggiornamento verrà quindi rifiutato poiché non disporrà della versione corretta di P.
Sono i quattro metodi del livello [dao] che andrebbero sincronizzati. Decidiamo tuttavia di mantenere questo livello così come è stato descritto e di spostare la sincronizzazione sul livello [service]. Ciò per diversi motivi:
- partiamo dal presupposto che l’accesso al livello [dao] avvenga sempre tramite un livello [service]. È quanto avviene nella nostra applicazione web.
- potrebbe essere necessario sincronizzare anche l’accesso ai metodi del livello [service] per motivi diversi da quelli che ci porterebbero a sincronizzare quelli del livello [dao]. In questo caso, non è necessario sincronizzare i metodi del livello [dao]. Se si è certi che:
- ogni accesso al livello [dao] passa attraverso il livello [service]
- che il livello [service] venga utilizzato da un solo thread alla volta
allora si ha la certezza che i metodi del livello [dao] non saranno eseguiti da due thread contemporaneamente.
Ora esaminiamo il livello [service].
14.6. Il livello [service]
Il livello [service] è costituito dalle seguenti classi e interfacce:
![]()
- [IService] è l’interfaccia presentata dal livello [dao]
- [ServiceImpl] ne costituisce un’implementazione
L’interfaccia [IService] è la seguente:
È identica all'interfaccia [IDao].
L'implementazione [ServiceImpl] dell'interfaccia [IService] è la seguente:
- righe 10-19: l’attributo [IDao dao] è un riferimento al livello [dao]. Verrà inizializzato da Spring IoC.
- righe 22-24: implementazione del metodo [getAll] dell’interfaccia [IService]. Il metodo si limita a delegare la richiesta al livello [dao].
- righe 27-29: implementazione del metodo [getOne] dell’interfaccia [IService]. Il metodo si limita a delegare la richiesta al livello [dao].
- righe 32-34: implementazione del metodo [saveOne] dell’interfaccia [IService]. Il metodo si limita a delegare la richiesta al livello [dao].
- righe 37-39: implementazione del metodo [deleteOne] dell’interfaccia [IService]. Il metodo si limita a delegare la richiesta al livello [dao].
- Tutti i metodi sono sincronizzati (parola chiave `synchronized`), garantendo che un solo thread alla volta possa utilizzare il livello [service] e, di conseguenza, il livello [dao].
14.7. Test del livello [service]
È stato scritto un test JUnit per il livello [service]:
![]() | ![]() |
[TestService] è il test JUnit. I test effettuati sono rigorosamente identici a quelli effettuati per il livello [dao]. Lo scheletro di [TestService] è il seguente:
- riga 9: il livello [service] testato è di tipo [ServiceImpl].
- righe 11-15: il generatore di test JUnit crea un'istanza del livello [service] da testare (riga 12), crea un'istanza del livello [dao] (riga 13) e indica al livello [service] di utilizzare tale livello [dao] (riga 14).
Il metodo [test1] verifica i quattro metodi dell’interfaccia [IService] in modo identico al metodo di verifica del livello [dao] con lo stesso nome. Semplicemente, si accede al livello [service] (righe 25, 32, 35) anziché al livello [dao].
Il metodo [test4] mira a evidenziare i problemi di accesso concorrente ai metodi del livello [service]. Anche in questo caso è identico al metodo di test [test4] del livello [dao]. Vi sono tuttavia alcuni dettagli che cambiano:
- ci si rivolge al livello [service] anziché al livello [dao] (riga 55)
- ai thread viene passato un riferimento al livello [service] anziché al livello [dao] (riga 61)
Anche il tipo [ThreadServiceMajEnfants] è pressoché identico al tipo [ThreadDaoMajEnfants], con la sola differenza che opera con il livello [service] anziché con il livello [dao]:
- riga 12: il thread opera con il livello [service]
Eseguiamo i test con la configurazione che ha causato il problema al livello [dao]:
- si rimuove il commento dall’istruzione di attesa nel metodo [saveOne] di [DaoImpl] (riga 83, paragrafo 14.4).
- il metodo [test4] crea 100 thread (riga 65, paragrafo 14.7).
I risultati ottenuti sono i seguenti:
![]() |
È stata la sincronizzazione dei metodi del livello [service] a consentire il successo del test [test4].
14.8. Il livello [web]
Ricordiamo l'architettura a tre livelli della nostra applicazione:
![]() |
Il livello [web] fornirà all’utente delle schermate che gli consentiranno di gestire il gruppo di persone:
- elenco delle persone del gruppo
- aggiunta di una persona al gruppo
- modifica di una persona del gruppo
- eliminazione di una persona dal gruppo
A tal fine, si avvarrà del livello [service], che a sua volta farà ricorso al livello [dao]. Abbiamo già presentato le schermate gestite dal livello [web] (paragrafo 14.1). Per descrivere il livello web, presenteremo in successione:
- la sua configurazione
- le sue viste
- il suo controller
- alcuni test
14.8.1. Configurazione dell’applicazione web
Il progetto Eclipse dell'applicazione è il seguente:

- nel pacchetto [istia.st.mvc.personnes.web] si trova il controller [Application].
- Le pagine JSP / JSTL si trovano in [WEB-INF/vues].
- La cartella [lib] contiene gli archivi di terze parti necessari all’applicazione. Sono visibili nella cartella [Web App Libraries].
[web.xml]
Il file [web.xml] è il file utilizzato dal server web per caricare l’applicazione. Il suo contenuto è il seguente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>mvc-personnes-01</display-name>
<!-- ServletPersonne -->
<servlet>
<servlet-name>personnes</servlet-name>
<servlet-class>
istia.st.mvc.personnes.web.Application
</servlet-class>
<init-param>
<param-name>urlEdit</param-name>
<param-value>/WEB-INF/vues/edit.jsp</param-value>
</init-param>
<init-param>
<param-name>urlErreurs</param-name>
<param-value>/WEB-INF/vues/erreurs.jsp</param-value>
</init-param>
<init-param>
<param-name>urlList</param-name>
<param-value>/WEB-INF/vues/list.jsp</param-value>
</init-param>
</servlet>
<!-- Mappatura ServletPersonne-->
<servlet-mapping>
<servlet-name>personnes</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- file di benvenuto -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Pagina di errore imprevisto -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/vues/exception.jsp</location>
</error-page>
</web-app>
- righe 27-30: gli URL [/do/*] saranno gestiti dal servlet [personnes]
- righe 9-12: il servlet [personnes] è un'istanza della classe [Application], una classe che creeremo.
- righe 13-24: definiscono tre parametri [urlList, urlEdit, urlErreurs] che identificano gli URL delle pagine JSP delle viste [list, edit, erreurs].
- righe 32-34: l’applicazione dispone di una pagina iniziale predefinita [index.jsp] che si trova nella directory principale dell’applicazione web.
- righe 36-39: l’applicazione dispone di una pagina di errore predefinita che viene visualizzata quando il server web rileva un’eccezione non gestita dall’applicazione.
- riga 37: il tag <exception-type> indica il tipo di eccezione gestita dalla direttiva <error-page>, in questo caso il tipo [java.lang.Exception] e i suoi derivati, ovvero tutte le eccezioni.
- riga 38: il tag <location> indica la pagina JSP da visualizzare quando si verifica un'eccezione del tipo definito da <exception-type>. L'eccezione verificatasi è disponibile in questa pagina in un oggetto denominato exception se la pagina contiene la direttiva:
<%@ page isErrorPage="true" %>
- (continua)
- se <exception-type> specifica un tipo T1 e un'eccezione di tipo T2 non derivata da T1 viene segnalata al server web, quest'ultimo invia al client una pagina di eccezione proprietaria, solitamente poco intuitiva. Da qui l’utilità del tag <error-page> nel file [web.xml].
[index.jsp]
Questa pagina viene visualizzata se un utente richiede direttamente il contesto dell’applicazione senza specificare un URL, c.a.d. qui [/personnes-01]. Il suo contenuto è il seguente:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<c:redirect url="/do/list"/>
[index.jsp] reindirizza il client all’URL [/do/list]. Questo URL mostra l’elenco delle persone del gruppo.
14.8.2. Le pagine JSP / JSTL dell’applicazione
La vista [list.jsp]
Serve a visualizzare l’elenco delle persone:

Il suo codice è il seguente:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>Liste des personnes</h2>
<table border="1">
<tr>
<th>Id</th>
<th>Version</th>
<th>Prénom</th>
<th>Nom</th>
<th>Date de naissance</th>
<th>Marié</th>
<th>Nombre d'enfants</th>
<th></th>
</tr>
<c:forEach var="personne" items="${personnes}">
<tr>
<td><c:out value="${personne.id}"/></td>
<td><c:out value="${personne.version}"/></td>
<td><c:out value="${personne.prenom}"/></td>
<td><c:out value="${personne.nom}"/></td>
<td><dt:format pattern="dd/MM/yyyy">${personne.dateNaissance.time}</dt:format></td>
<td><c:out value="${personne.marie}"/></td>
<td><c:out value="${personne.nbEnfants}"/></td>
<td><a href="<c:url value="/do/edit?id=${personne.id}"/>">Modifier</a></td>
<td><a href="<c:url value="/do/delete?id=${personne.id}"/>">Supprimer</a></td>
</tr>
</c:forEach>
</table>
<br>
<a href="<c:url value="/do/edit?id=-1"/>">Ajout</a>
</body>
</html>
- questa vista riceve un elemento nel proprio modello:
- l'elemento [personnes] associato a un oggetto di tipo [ArrayList] contenente oggetti di tipo [Personne]
- righe 22-34: si scorre l'elenco ${personnes} per visualizzare una tabella HTML contenente le persone del gruppo.
- riga 31: l'URL a cui punta il link [Modifier] viene impostato dal campo [id] della persona corrente, in modo che il controller associato all'URL [/do/edit] sappia quale persona deve essere modificata.
- riga 32: lo stesso vale per il link [Supprimer].
- riga 28: per visualizzare la data di nascita della persona nel formato JJ/MM/AAAA, si utilizza il tag <dt> della libreria di tag [DateTime] del progetto Apache [Jakarta Taglibs]:

Il file di descrizione di questa libreria di tag è definito alla riga 3.
- riga 37: il link [Ajout] per l'aggiunta di una nuova persona ha come destinazione l'URL [/do/edit], proprio come il link [Modifier] della riga 31. È il valore -1 del parametro [id] che indica che si tratta di un'aggiunta piuttosto che di una modifica.
La vista [edit.jsp]
Serve a visualizzare il modulo per l’aggiunta di una nuova persona o la modifica di una persona esistente:
![]() |
Il codice della vista [edit.jsp] è il seguente:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ taglib uri="/WEB-INF/taglibs-datetime.tld" prefix="dt" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="../ressources/standard.jpg">
<h2>Ajout/Modification d'une personne</h2>
<c:if test="${erreurEdit != ''}">
<h3>Echec de la mise à jour :</h3>
L'erreur suivante s'est produite : ${erreurEdit}
<hr>
</c:if>
<form method="post" action="<c:url value="/do/validate"/>">
<table border="1">
<tr>
<td>Id</td>
<td>${id}</td>
</tr>
<tr>
<td>Version</td>
<td>${version}</td>
</tr>
<tr>
<td>Prénom</td>
<td>
<input type="text" value="${prenom}" name="prenom" size="20">
</td>
<td>${erreurPrenom}</td>
</tr>
<tr>
<td>Nom</td>
<td>
<input type="text" value="${nom}" name="nom" size="20">
</td>
<td>${erreurNom}</td>
</tr>
<tr>
<td>Date de naissance (JJ/MM/AAAA)</td>
<td>
<input type="text" value="${dateNaissance}" name="dateNaissance">
</td>
<td>${erreurDateNaissance}</td>
</tr>
<tr>
<td>Marié</td>
<td>
<c:choose>
<c:when test="${marie}">
<input type="radio" name="marie" value="true" checked>Oui
<input type="radio" name="marie" value="false">Non
</c:when>
<c:otherwise>
<input type="radio" name="marie" value="true">Oui
<input type="radio" name="marie" value="false" checked>Non
</c:otherwise>
</c:choose>
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td>
<input type="text" value="${nbEnfants}" name="nbEnfants">
</td>
<td>${erreurNbEnfants}</td>
</tr>
</table>
<br>
<input type="hidden" value="${id}" name="id">
<input type="hidden" value="${version}" name="version">
<input type="submit" value="Valider">
<a href="<c:url value="/do/list"/>">Annuler</a>
</form>
</body>
</html>
Questa vista presenta un modulo per l’aggiunta di una nuova persona o l’aggiornamento di una persona esistente. D’ora in poi, per semplificare la scrittura, useremo il termine unico [mise à jour]. Il pulsante [Valider] (riga 73) attiva il POST del modulo all’URL [/do/validate] (riga 16). Se il POST fallisce, viene nuovamente visualizzata la vista [edit.jsp] con l’errore o gli errori verificatisi; in caso contrario, viene visualizzata la vista [list.jsp].
- La vista [edit.jsp], visualizzata sia su un GET che su un POST che non supera il controllo, riceve i seguenti elementi nel proprio modello:
attributo | GET | POST |
ID della persona aggiornata | idem | |
la sua versione | idem | |
il suo nome | nome inserito | |
il suo cognome | cognome inserito | |
Data di nascita | data di nascita inserita | |
stato civile | stato civile inserito | |
numero di figli | numero di figli inserito | |
vuoto | un messaggio di errore che segnala un errore nell'aggiunta o nella modifica al momento dell'esecuzione di POST, causata dal pulsante [Envoyer]. Vuoto se non ci sono errori. | |
vuoto | segnala un nome errato – vuoto in caso contrario | |
vuoto | segnala un cognome errato – vuoto in caso contrario | |
vuoto | segnala una data di nascita errata – vuoto in caso contrario | |
vuoto | segnala un numero di figli errato – vuoto in caso contrario |
- righe 11-15: se il codice POST del modulo non viene elaborato correttamente, si otterrà [erreurEdit!=''] e verrà visualizzato un messaggio di errore.
- riga 16: il modulo verrà inviato all’URL [/do/validate]
- riga 20: viene visualizzato l'elemento [id] del modello
- riga 24: viene visualizzato l'elemento [version] del modello
- righe 26-32: inserimento del nome della persona:
- alla visualizzazione iniziale del modulo (GET), ${prenom} visualizza il valore attuale del campo [prenom] dell’oggetto [Personne] aggiornato e ${erreurPrenom} è vuoto.
- in caso di errore dopo il campo POST, viene visualizzato nuovamente il valore inserito ${prenom} insieme all’eventuale messaggio di errore ${erreurPrenom}
- righe 33-39: inserimento del cognome della persona
- righe 40-46: inserimento della data di nascita della persona
- righe 47-61: inserimento dello stato civile della persona tramite un pulsante di opzione. Si utilizza il valore del campo [marie] dell’oggetto [Personne] per determinare quale dei due pulsanti di opzione debba essere selezionato.
- righe 62-68: inserimento del numero di figli della persona
- riga 71: un campo nascosto HTML denominato [id], il cui valore corrisponde al campo [id] della persona oggetto dell’aggiornamento; -1 in caso di aggiunta, un altro valore in caso di modifica.
- riga 72: un campo nascosto HTML denominato [version], il cui valore corrisponde al campo [id] della persona oggetto dell’aggiornamento.
- riga 73: il pulsante [Valider] di tipo [Submit] del modulo
- riga 74: un link che consente di tornare all’elenco delle persone. È stato denominato [Annuler] perché permette di uscire dal modulo senza convalidarlo.
La vista [exception.jsp]
Serve a visualizzare una pagina che segnala che si è verificata un'eccezione non gestita dall'applicazione e che è stata segnalata al server web.
Ad esempio, eliminiamo una persona che non esiste nel gruppo:
![]() |
Il codice della vista [exception.jsp] è il seguente:
<%@ page language="java" pageEncoding="ISO-8859-1" contentType="text/html;charset=ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<%@ page isErrorPage="true" %>
<%
response.setStatus(200);
%>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body background="<c:url value="/ressources/standard.jpg"/>">
<h2>MVC - personnes</h2>
L'exception suivante s'est produite :
<%= exception.getMessage()%>
<br><br>
<a href="<c:url value="/do/list"/>">Retour à la liste</a>
</body>
</html>
- questa vista riceve nel proprio modello l’elemento [exception], che rappresenta l’eccezione intercettata dal server web. Affinché questo elemento venga incluso nel modello della pagina JSP dal server web, è necessario che la pagina abbia definito il tag alla riga 3.
- riga 6: si imposta a 200 il codice di stato HTTP della risposta. Si tratta della prima intestazione HTTP della risposta. Il codice 200 indica al client che la sua richiesta è stata soddisfatta. Generalmente, nella risposta del server è stato incluso un documento HTML. È proprio questo il caso. Se il codice di stato HTTP della risposta non viene impostato a 200, in questo caso assumerà il valore 500, il che significa che si è verificato un errore. Infatti, il server web, avendo intercettato un'eccezione non gestita, rileva questa situazione anomala e la segnala con il codice 500. La reazione al codice HTTP 500 varia a seconda dei browser: Firefox visualizza il documento HTML che può accompagnare questa risposta, mentre IE ignora tale documento e visualizza la propria pagina. Per questo motivo abbiamo sostituito il codice 500 con il codice 200.
- riga 16: viene visualizzato il testo dell’eccezione
- riga 18: all’utente viene proposto un link per tornare all’elenco delle persone
La vista [erreurs.jsp]
Serve a visualizzare una pagina che segnala gli errori di inizializzazione dell’applicazione, c.a.d, e gli errori rilevati durante l’esecuzione del metodo [init] del servlet del controller. Può trattarsi, ad esempio, della mancanza di un parametro nel file [web.xml], come illustrato nell’esempio riportato di seguito:

Il codice della pagina [erreurs.jsp] è il seguente:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
<html>
<head>
<title>MVC - Personnes</title>
</head>
<body>
<h2>Les erreurs suivantes se sont produites</h2>
<ul>
<c:forEach var="erreur" items="${erreurs}">
<li>${erreur}</li>
</c:forEach>
</ul>
</body>
</html>
La pagina riceve nel proprio modello un elemento [erreurs], che è un oggetto di tipo [ArrayList] contenente oggetti [String], i quali a loro volta sono messaggi di errore. Questi vengono visualizzati dal ciclo delle righe 13-15.
14.8.3. Il controller dell’applicazione
Il controller [Application] è definito nel pacchetto [istia.st.mvc.personnes.web]:
![]()
Struc tura e inizializzazione del controller
La struttura del controller [Application] è la seguente:
- righe 20-36: si recuperano i parametri previsti nel file [web.xml].
- righe 39-41: il parametro [urlErreurs] deve essere obbligatoriamente presente poiché indica l'URL della vista [erreurs] in grado di visualizzare eventuali errori di inizializzazione. Se non esiste, l’applicazione viene interrotta lanciando una [ServletException] (riga 40). Questa eccezione verrà segnalata al server web e gestita dal tag <error-page> del file [web.xml]. Viene quindi visualizzata la vista [exception.jsp]:

Il link [Retour à la liste] sopra riportato non è funzionante. Utilizzandolo si ottiene la stessa risposta finché l’applicazione non viene modificata e ricaricata. È utile per altri tipi di eccezioni, come abbiamo già visto.
- riga 43: crea un'istanza [DaoImpl] che implementa il livello [dao]
- riga 44: inizializza questa istanza (creazione di un elenco iniziale di tre persone)
- riga 46: crea un'istanza [ServiceImpl] che implementa il livello [service]
- riga 47: inizializza il livello [service] fornendogli un riferimento al livello [dao]
Dopo l’inizializzazione del controller, i suoi metodi dispongono di un riferimento [service] al livello [service] (riga 15) che utilizzeranno per eseguire le azioni richieste dall’utente. Queste azioni verranno intercettate dal metodo [doGet], che le farà elaborare da un metodo specifico del controller:
Url | Metodo HTTP | metodo del controller |
GET | doListPersonnes | |
GET | doEditPersonne | |
POST | doValidatePersonne | |
GET | doDeletePersonne |
Il metodo [doGet]
Questo metodo ha lo scopo di indirizzare l'elaborazione delle azioni richieste dall'utente verso il metodo corretto. Il suo codice è il seguente:
- righe 7-13: si verifica che l'elenco degli errori di inizializzazione sia vuoto. In caso contrario, viene visualizzata la vista [erreurs(erreurs)] che segnalerà l'errore o gli errori.
- riga 15: si recupera il metodo [get] o [post] che il cliente ha utilizzato per effettuare la richiesta.
- riga 17: si recupera il valore del parametro [action] della richiesta.
- righe 23-27: elaborazione della richiesta [GET /do/list] che richiede l'elenco delle persone.
- righe 28-32: elaborazione della richiesta [GET /do/delete] che richiede la cancellazione di una persona.
- righe 33-37: elaborazione della richiesta [GET /do/edit] che richiede il modulo di aggiornamento di una persona.
- righe 38-42: elaborazione della richiesta [POST /do/validate] che richiede la convalida della persona aggiornata.
- riga 44: se l'azione richiesta non è una delle cinque precedenti, allora si procede come se fosse [GET /do/list].
Il metodo [doListPersonnes]
Questo metodo elabora la richiesta [GET /do/list] che richiede l'elenco delle persone:

Il suo codice è il seguente:
- riga 5: si richiede al livello [service] l'elenco delle persone del gruppo e lo si inserisce nel modello sotto la chiave "persone".
- riga 7: si visualizza la vista [list.jsp] descritta al paragrafo 14.8.2.
Il metodo [doDeletePersonne]
Questo metodo gestisce la richiesta [GET /do/delete?id=XX] che richiede l’eliminazione della persona con id=XX. L'URL [/do/delete?id=XX] è quello dei link [Supprimer] della vista [list.jsp]:

il cui codice è il seguente:
Alla riga 12 si vede l'URL [/do/delete?id=XX] del link [Supprimer]. Il metodo [doDeletePersonne], che deve elaborare questo URL, deve rimuovere la persona con id=XX e quindi visualizzare il nuovo elenco delle persone del gruppo. Il suo codice è il seguente:
- riga 5: l’URL elaborato è della forma [/do/delete?id=XX]. Si recupera il valore [XX] dal parametro [id].
- riga 7: si richiede al livello [service] l’eliminazione della persona con l’ID ottenuto. Non effettuiamo alcuna verifica. Se la persona che si intende eliminare non esiste, il livello [dao] genera un’eccezione che viene propagata dal livello [service]. Non la gestiamo nemmeno qui, nel controller. Verrà quindi inoltrata al server web che, in base alla configurazione, visualizzerà la pagina [exception.jsp], descritta al paragrafo 14.8.2:

- riga 9: se l’eliminazione è avvenuta (nessuna eccezione), si richiede al client di reindirizzarsi all’URL relativo [list]. Poiché quella appena elaborata è [/do/delete], l’URL di reindirizzamento sarà [/do/list]. Il browser verrà quindi indirizzato a [GET /do/list], che provocherà la visualizzazione dell’elenco delle persone.
Il metodo [doEditPersonne]
Questo metodo gestisce la richiesta [GET /do/edit?id=XX] che richiede il modulo di aggiornamento della persona con id=XX. L'URL [/do/edit?id=XX] è quello dei link [Modifier] e del link [Ajout] della vista [list.jsp]:

il cui codice è il seguente:
Alla riga 11 si vede l'URL [/do/edit?id=XX] del link [Modifier] e alla riga 17 l'URL [/do/edit?id=-1] del link [Ajout]. Il metodo [doEditPersonne] deve visualizzare il modulo di modifica della persona con id=XX oppure, se si tratta di un'aggiunta, presentare un modulo vuoto.
![]() | ![]() |
Il codice del metodo [doEditPersonne] è il seguente:
- il GET ha come destinazione un URL del tipo [/do/edit?id=XX]. Alla riga 5, recuperiamo il valore di [id]. Successivamente si presentano due casi:
- se id è diverso da -1, si tratta di una modifica e occorre visualizzare un modulo precompilato con le informazioni della persona da modificare. Alla riga 10, questa persona viene richiesta al livello [service].
- id è uguale a -1. In questo caso si tratta di un’aggiunta ed è necessario visualizzare un modulo vuoto. A tal fine, alle righe 13-14 viene creata una persona vuota.
- L’oggetto [Personne] ottenuto viene inserito nel modello della pagina [edit.jsp] descritto al paragrafo 14.8.2. Questo modello comprende i seguenti elementi [erreurEdit, id, version, prenom, erreurPrenom, nom, erreurNom, dateNaissance, erreurDateNaissance, marie, nbEnfants, erreurNbEnfants]. Questi elementi vengono inizializzati alle righe 17-30, ad eccezione di quelli il cui valore è la stringa vuota [erreurPrenom, erreurNom, erreurDateNaissance, erreurNbEnfants]. È noto che, in caso di loro assenza nel modello, la libreria JSTL visualizzerà una stringa vuota come loro valore. Sebbene anche l’elemento [erreurEdit] abbia come valore una stringa vuota, viene comunque inizializzato poiché nella pagina [edit.jsp] viene eseguito un test sul suo valore.
- Una volta pronto il modello, il controllo passa alla pagina [edit.jsp], righe 32-33, che genererà la vista [edit].
Il metodo [doValidatePersonne]
Questo metodo gestisce la richiesta [POST /do/validate] che convalida il modulo di aggiornamento. Questa richiesta POST viene attivata dal pulsante [Valider]:

Ricordiamo gli elementi di immissione dati del modulo HTML della vista sopra riportata:
La richiesta POST contiene i parametri [prenom, nom, dateNaissance, marie, nbEnfants, id, version] e viene inviata all'URL [/do/validate] (riga 1). Viene elaborata dal seguente metodo [doValidatePersonne]:
- righe 8-14: viene recuperato il parametro [prenom] della richiesta POST e ne viene verificata la validità. Se risulta errato, l’elemento [erreurPrenom] viene inizializzato con un messaggio di errore e inserito negli attributi della richiesta.
- righe 16-22: si procede in modo analogo per il parametro [nom]
- righe 24-32: si procede in modo analogo per il parametro [dateNaissance]
- riga 34: si recupera il parametro [marie]. Non si verifica la sua validità perché, a priori, deriva dal valore di un pulsante di opzione. Detto questo, nulla impedisce a un programma di generare un [POST /personnes-01/do/validate] accompagnato da un parametro [marie] inventato. Dovremmo quindi verificare la validità di questo parametro. In questo caso, ci affidiamo alla nostra gestione delle eccezioni, che provoca la visualizzazione della pagina [exception.jsp] se il controller non le gestisce autonomamente. Se quindi la conversione del parametro [marie] in valore booleano fallisce alla riga 34, verrà generata un’eccezione che porterà all’invio della pagina [exception.jsp] al cliente. Questo funzionamento ci soddisfa.
- righe 34-54: si recupera il parametro [nbEnfants] e se ne verifica il valore.
- riga 56: si recupera il parametro [id] senza verificarne il valore
- riga 58: si procede allo stesso modo per il parametro [version]
- righe 60-65: se il modulo contiene errori, viene visualizzato nuovamente con i messaggi di errore generati in precedenza
- righe 67-69: se è valido, si crea un nuovo oggetto [Personne] con gli elementi del modulo
- righe 70-78: il profilo viene salvato. Il salvataggio potrebbe non andare a buon fine. In un ambiente multiutente, il profilo da modificare potrebbe essere stato eliminato o già modificato da qualcun altro. In questo caso, il livello [dao] genererà un'eccezione che viene gestita qui.
- riga 80: se non si è verificata alcuna eccezione, si reindirizza il cliente all’URL [/do/list] per mostrargli il nuovo stato del gruppo.
- riga 75: se si è verificata un’eccezione durante il salvataggio, si richiede nuovamente la visualizzazione del modulo iniziale passando il messaggio di errore dell’eccezione (terzo parametro).
Il metodo [showFormulaire] (righe 84-101) costruisce il modello necessario per la pagina [edit.jsp] con i valori inseriti (request.getParameter(" ... ")). Si ricorda che i messaggi di errore sono già stati inseriti nel modello dal metodo [doValidatePersonne]. La pagina [edit.jsp] viene visualizzata alle righe 99-100.
14.9. I test dell’applicazione web
Nel paragrafo 14.1 sono stati presentati alcuni test. Invitiamo il lettore a ripeterli. Mostriamo qui altre schermate che illustrano i casi di conflitti di accesso ai dati in un contesto multiutente:
[Firefox] sarà il browser dell’utente U1. Quest’ultimo richiede l’URL [http://localhost:8080/personnes-01]:

[IE] sarà il browser dell’utente U2. Quest’ultimo richiede lo stesso URL:

L’utente U1 accede alla modifica della persona [Lemarchand]:

L’utente U2 fa lo stesso:

L'utente U1 apporta delle modifiche e conferma:
![]() |
Anche l'utente U2 fa lo stesso:
![]() |
L'utente U2 torna all'elenco delle persone tramite il link [Annuler] del modulo:

Trova la persona [Lemarchand] così come è stata modificata da U1. Ora U2 elimina [Lemarchand]:
![]() |
U1 ha ancora il proprio elenco e vuole modificare nuovamente [Lemarchand]:
![]() |
U1 utilizza il link [Retour à la liste] per vedere di cosa si tratta:

Scopre che, effettivamente, [Lemarchand] non fa più parte dell’elenco...
14.10. Conclusion
Abbiamo implementato l’architettura MVC in un’architettura a tre livelli [web, metier, dao] su un esempio di base di gestione di un elenco di persone. Questo ci ha permesso di utilizzare i concetti presentati nelle sezioni precedenti. Nella versione esaminata, l’elenco delle persone era mantenuto in memoria. Presto esamineremo versioni in cui tale elenco sarà conservato in una tabella del database.
Ma prima presenteremo uno strumento chiamato Spring IoC, che facilita l'integrazione dei diversi livelli di un'applicazione ntier.

















