14. Aplicativo web MVC em uma arquitetura de três camadas – Exemplo 1
14.1. Présentation
Até agora, limitamo-nos a exemplos com finalidade didática. Por isso, eles precisavam ser simples. Apresentamos agora uma aplicação básica, mas, ainda assim, mais rica do que todas as apresentadas até o momento. Ela terá a particularidade de utilizar as três camadas de uma arquitetura de três camadas:

Convidamos o leitor a reler os princípios de uma aplicação web MVC em uma arquitetura de três camadas, caso os tenha esquecido, no parágrafo 4.
A aplicação web que vamos desenvolver permitirá gerenciar um grupo de pessoas por meio de quatro operações:
- lista das pessoas do grupo
- adição de uma pessoa ao grupo
- alteração de uma pessoa do grupo
- exclusão de uma pessoa do grupo
Essas quatro operações básicas correspondem às operações de uma tabela de banco de dados. Vamos desenvolver duas versões dessa aplicação:
- na versão 1, a camada [dao] não utilizará um banco de dados. As pessoas do grupo serão armazenadas em um objeto simples [ArrayList], gerenciado internamente pela camada [dao]. Isso permitirá que o leitor teste a aplicação sem a restrição de um banco de dados.
- Na versão 2, colocaremos o grupo de pessoas em uma tabela de banco de dados. Mostraremos que isso será feito sem impacto na camada web da versão 1, que permanecerá inalterada.
As capturas de tela a seguir mostram as páginas que o aplicativo exibe ao usuário.



![]() |
![]() |
14.2. O projeto Eclipse
O projeto do aplicativo se chama [personnes-01]:

Este projeto abrange as três camadas da arquitetura de três camadas do aplicativo:
![]() |
- a camada [dao] está contida no pacote [istia.st.mvc.personnes.dao]
- a camada [metier] ou [service] está contida no pacote [istia.st.mvc.personnes.service]
- a camada [web] ou [ui] está contida no pacote [istia.st.mvc.personnes.web]
- o pacote [istia.st.mvc.personnes.entites] contém os objetos compartilhados entre diferentes camadas
- o pacote [istia.st.mvc.personnes.tests] contém os testes Junit das camadas [dao] e [service]
Vamos explorar sucessivamente as três camadas [dao], [service] e [web]. Como seria muito demorado escrever e talvez muito enfadonho de ler, talvez sejamos um pouco rápidos nas explicações em alguns momentos, exceto quando o que for apresentado for novo.
14.3. A representação de uma pessoa
O aplicativo gerencia um grupo de pessoas. As capturas de tela do parágrafo 14.1 mostraram algumas das características de uma pessoa. Formalmente, elas são representadas pela classe [Personne]:
![]()
A classe [Personne] é a seguinte:
- uma pessoa é identificada pelas seguintes informações:
- id: um número que identifica uma pessoa de forma exclusiva
- sobrenome: o sobrenome da pessoa
- nome próprio: seu nome próprio
- dateNaissance: sua data de nascimento
- casada: se a pessoa é casada ou não
- nbEnfants: o número de filhos
- O atributo [version] é um atributo adicionado artificialmente para atender às necessidades do aplicativo. Do ponto de vista orientada a objetos, sem dúvida teria sido preferível adicionar esse atributo em uma classe derivada de [Personne]. Sua necessidade surge quando se analisam os casos de uso do aplicativo web. Um deles é o seguinte:
No momento T1, um usuário U1 acessa a edição de uma pessoa P. Nesse momento, o número de filhos é 0. Ele altera esse número para 1, mas, antes de confirmar a alteração, um usuário U2 acessa a edição da mesma pessoa P. Como U1 ainda não confirmou sua alteração, U2 vê o número de filhos como 0. U2 altera o nome da pessoa P para letras maiúsculas. Em seguida, U1 e U2 confirmam suas alterações nessa ordem. É a modificação de U2 que prevalecerá: o nome passará a estar em maiúsculas e o número de filhos permanecerá em zero, mesmo que U1 acredite ter alterado esse valor para 1.
O conceito de versão de pessoa nos ajuda a resolver esse problema. Vamos retomar o mesmo caso de uso:
No momento T1, um usuário U1 inicia a edição de uma pessoa P. Nesse momento, o número de filhos é 0 e a versão é V1. Ele altera o número de filhos para 1, mas, antes de confirmar a alteração, um usuário U2 inicia a edição da mesma pessoa P. Como U1 ainda não confirmou sua alteração, U2 vê o número de filhos como 0 e a versão como V1. U2 altera o nome da pessoa P para letras maiúsculas. Em seguida, U1 e U2 validam suas alterações nessa ordem. Antes de validar uma alteração, verifica-se se quem está alterando a pessoa P possui a mesma versão que a pessoa P atualmente registrada. Esse será o caso do usuário U1. Sua alteração é, portanto, aceita e, então, altera-se a versão da pessoa modificada de V1 para V2 para indicar que a pessoa sofreu uma alteração. Ao validar a modificação de U2, perceberemos que ele possui uma versão V1 da pessoa P, enquanto que, atualmente, a versão desta é V2. Será então possível informar ao usuário U2 que alguém já agiu antes dele e que ele deve partir da nova versão da pessoa P. Ele fará isso, recuperará uma pessoa P da versão V2, que agora tem um filho, colocará o nome em maiúsculas e validará. Sua modificação será aceita se a pessoa P registrada ainda tiver a versão V2. No final, as modificações feitas por U1 e U2 serão consideradas, enquanto que, no caso de uso sem versão, uma das modificações seria perdida.
- linhas 32-40: um construtor capaz de inicializar os campos de uma pessoa. O campo [version] é omitido.
- linhas 43-51: um construtor que cria uma cópia da pessoa que lhe é passada como parâmetro. Temos, então, dois objetos com conteúdo idêntico, mas referenciados por dois ponteiros diferentes.
- linha 55: o método [toString] é redefinido para retornar uma sequência de caracteres representando o estado da pessoa
14.4. A camada [dao]
A camada [dao] é composta pelas seguintes classes e interfaces:
![]()
- [IDao] é a interface apresentada pela camada [dao]
- [DaoImpl] é uma implementação dessa interface, na qual o grupo de pessoas está encapsulado em um objeto [ArrayList]
- [DaoException] é um tipo de exceção não verificada (unchecked), lançada pela camada [dao]
A interface [IDao] é a seguinte:
- A interface possui quatro métodos para as quatro operações que se deseja realizar no grupo de pessoas:
- getAll: para obter uma coleção de pessoas
- getOne: para obter uma pessoa com um id específico
- saveOne: para adicionar uma pessoa (id=-1) ou modificar uma pessoa existente (id <> -1)
- deleteOne: para excluir uma pessoa com um id específico
A camada [dao] pode gerar exceções. Essas exceções serão do tipo [DaoException] :
- linha 3: a classe [DaoException], derivada de [RuntimeException], é um tipo de exceção não controlada: o compilador não nos obriga a:
- tratar esse tipo de exceção com um try/catch ao chamar um método que possa lançá-la
- incluir o marcador “throws DaoException” na assinatura de um método suscetível de lançar a exceção
Essa técnica evita que tenhamos que assinar os métodos da interface [IDao] com exceções de um tipo específico. Qualquer implementação que lance exceções não controladas será, portanto, aceitável, proporcionando assim flexibilidade à arquitetura.
- linha 6: um código de erro. A camada [dao] lançará diversas exceções que serão identificadas por códigos de erro diferentes. Isso permitirá que a camada responsável por gerenciar a exceção conheça a origem exata do erro e, assim, tome as medidas adequadas. Existem outras maneiras de chegar ao mesmo resultado. Uma delas é criar um tipo de exceção para cada tipo de erro possível, por exemplo, NomManquantException, PrenomManquantException, AgeIncorrectException, ...
- linhas 13-16: o construtor que permitirá criar uma exceção identificada por um código de erro e uma mensagem de erro.
- linhas 8-10: o método que permitirá que o código de gerenciamento de uma exceção recupere o código de erro.
A classe [DaoImpl] implementa a interface [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 | |
Apresentaremos apenas as linhas gerais desse código. No entanto, dedicaremos um pouco de tempo às partes mais delicadas.
- linha 13: o objeto [ArrayList], que conterá o grupo de pessoas
- linha 16: o identificador da última pessoa adicionada. A cada nova adição, esse identificador será incrementado em 1.
A classe [DaoImpl] será instanciada em uma única instância. Isso é o que chamamos de singleton. Um aplicativo web atende seus usuários simultaneamente. Em um determinado momento, há várias threads sendo executadas pelo servidor web. Elas compartilham os singletons:
- o da camada [dao]
- da camada [service]
- os das diferentes controladoras, validadores de dados, etc., da camada web
Se um singleton tiver campos privados, é preciso questionar imediatamente por que ele os possui. Eles são justificados? Afinal, eles serão compartilhados entre diferentes threads. Se forem somente de leitura, isso não representa problema, desde que possam ser inicializados em um momento em que se tenha certeza de que há apenas um thread ativo. Geralmente, sabemos identificar esse momento. É o momento do início da aplicação web, quando ela ainda não começou a atender clientes. Se forem de leitura/gravação, é preciso implementar uma sincronização de acesso aos campos; caso contrário, corremos para o desastre. Ilustraremos esse problema quando testarmos a camada [dao].
- A classe [DaoImpl] não possui construtor. Portanto, será utilizado seu construtor padrão.
- linhas 19-38: o método [init] será chamado no momento da instanciação do singleton da camada [dao]. Ele cria uma lista com três pessoas.
- linhas 41-43: implementam o método [getAll] da interface [IDao]. Ele retorna uma referência à lista de pessoas.
- linhas 46-55: implementa o método [getOne] da interface [IDao]. Seu parâmetro é o ID da pessoa procurada.
Para recuperá-la, recorre-se a um método privado [getPosition], nas linhas 113-126. Esse método retorna a posição na lista da pessoa procurada ou -1, caso a pessoa não tenha sido encontrada.
Se a pessoa tiver sido encontrada, o método [getOne] retorna uma referência (linha 51) a uma cópia dessa pessoa e não à própria pessoa. De fato, quando um usuário quiser modificar uma pessoa, as informações sobre ela serão solicitadas à camada [dao] e encaminhadas até a camada [web] para modificação, na forma de uma referência a um objeto [Personne]. Essa referência servirá como contêiner de entradas no formulário de modificação. Quando, na camada web, o usuário enviar suas modificações, o conteúdo do contêiner de entradas será alterado. Se o contêiner for uma referência à pessoa real do [ArrayList] da camada [dao], então essa pessoa é alterada mesmo que as alterações não tenham sido apresentadas às camadas [service] e [dao]. Esta última é a única habilitada a gerenciar a lista de pessoas. Portanto, é necessário que a camada web trabalhe com uma cópia da pessoa a ser alterada. Nesse caso, a camada [dao] fornece essa cópia.
Se a pessoa procurada não for encontrada, é lançada uma exceção do tipo [DaoException] com o código de erro 2 (linha 53).
- linhas 94-104: implementam o método [deleteOne] da interface [IDao]. Seu parâmetro é o ID da pessoa a ser excluída. Se a pessoa a ser excluída não existir, uma exceção do tipo [DaoException] é lançada com o código de erro 2.
- linhas 58-91: implementa o método [saveOne] da interface [IDao]. Seu parâmetro é um objeto [Personne]. Se esse objeto tiver um id=-1, trata-se de uma adição de pessoa. Caso contrário, trata-se de modificar a pessoa da lista com esse id com os valores do parâmetro.
- linha 60: a validade do parâmetro [Personne] é verificada por um método privado [check] definido nas linhas 129-155. Esse método realiza verificações básicas sobre o valor dos diferentes campos de [Personne]. Sempre que uma anomalia é detectada, é lançado um [DaoException] com um código de erro específico. Como o método [saveOne] não lida com essa exceção, ela será repassada ao método chamador.
- linha 62: se o parâmetro [Personne] tiver seu id igual a -1, trata-se de uma adição. O objeto [Personne] é adicionado à lista interna de pessoas (linha 66), com o primeiro ID disponível (linha 64) e um número de versão igual a 1 (linha 65).
- Se o parâmetro [Personne] tiver um [id] diferente de -1, trata-se de modificar a pessoa da lista interna que possui esse [id]. Primeiramente, verifica-se (linhas 70-75) se a pessoa a ser modificada existe. Caso contrário, lança-se uma exceção do tipo [DaoException] com o código de erro 2.
- Se a pessoa estiver presente, verifica-se se sua versão atual é a mesma que a do parâmetro [Personne], que contém as alterações a serem feitas no original. Caso contrário, isso significa que quem deseja fazer a alteração na pessoa não possui a versão mais recente. Isso é comunicado a ele por meio do lançamento de uma exceção do tipo [DaoException] com o código de erro 3 (linhas 79-80).
- Se tudo correr bem, as alterações são feitas no registro original da pessoa (linhas 85-90)
É evidente que esse método precisa ser sincronizado. Por exemplo, entre o momento em que se verifica se a pessoa a ser modificada realmente existe e o momento em que a modificação será feita, ela pode ter sido excluída da lista por outra pessoa. O método deveria, portanto, ser declarado como [synchronized] para garantir que apenas um thread por vez o execute. O mesmo se aplica aos outros métodos da interface [IDao]. Não fazemos isso, preferindo transferir essa sincronização para a camada [service]. Para destacar os problemas de sincronização, durante os testes da camada [dao], interromperemos a execução de [saveOne] por 10 ms (linha 83) entre o momento em que sabemos que podemos fazer a modificação e o momento em que a fazemos de fato. O thread que executa o [saveOne] perderá então o processador em favor de outro. Assim, aumentamos nossas chances de observar conflitos de acesso à lista de pessoas.
14.5. Testes da camada [dao]
Um teste JUnit é escrito para a camada [dao]:
![]() | ![]() |
[TestDao] é o teste JUnit. Para destacar os problemas de acesso simultâneo à lista de pessoas, são criadas threads do tipo [ThreadDaoMajEnfants]. Elas têm a função de aumentar em 1 o número de filhos de uma determinada pessoa.
[TestDao] possui cinco testes, de [test1] a [test5]. Apresentamos apenas dois deles; o leitor é convidado a descobrir os demais no código-fonte associado a este artigo.
- linha 9: referência à implementação da camada [dao] testada
- linhas 12-15: o construtor do teste JUnit. Ele cria uma instância do tipo [DaoImpl] da camada [dao] a ser testada e a inicializa.
O método [test1] testa os quatro métodos da interface [IDao] da seguinte maneira:
- linha 3: solicita-se a lista de pessoas
- linha 6: exibe-se essa lista
[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]
Em seguida, o teste adiciona uma pessoa, a modifica e a exclui. Assim, os quatro métodos da interface [IDao] são utilizados.
- linhas 8-10: adiciona-se uma nova pessoa (id=-1).
- linha 11: recupera-se o id da pessoa adicionada, pois a adição atribuiu um a ela. Antes, ela não tinha nenhum.
- linhas 13-14: solicita-se à camada [dao] uma cópia da pessoa que acabou de ser adicionada. É preciso lembrar que, se a pessoa solicitada não for encontrada, a camada [dao] lança uma exceção. Nesse caso, ocorrerá uma falha na linha 13. Esse caso poderia ter sido tratado de forma mais adequada. Na linha 14, verifica-se o nome da pessoa encontrada.
- linhas 16-17: alteramos esse nome e solicitamos à camada [dao] que salve as alterações.
- linhas 19-20: solicitamos à camada [dao] uma cópia da pessoa que acabou de ser adicionada e verificamos seu novo nome.
- linha 22: exclui-se a pessoa adicionada no início do teste.
- linhas 23-34: solicita-se à camada [dao] uma cópia da pessoa que acabou de ser excluída. Deve-se obter uma [DaoException] com código 2.
- linhas 36-37: a lista de pessoas é solicitada novamente. Deve-se obter a mesma lista do início do teste.
O método [test4] visa destacar os problemas de acesso simultâneo aos métodos da camada [dao]. Vale lembrar que esses métodos não foram sincronizados. O código do teste é o seguinte:
- linhas 3-6: adiciona-se à lista uma pessoa P sem filhos. Registra-se seu [id] (linha 6).
- linhas 7-13: iniciam-se N threads. Cada um deles incrementará o número de filhos da pessoa P em 1 unidade. No final, a pessoa P deverá ter N filhos.
- linhas 15-17: o método [test4], que iniciou as N threads, aguarda até que elas concluam seu trabalho antes de verificar o novo número de filhos da pessoa P.
- linhas 18-21: recupera-se a pessoa P e verifica-se se o número de filhos dela é N.
- linhas 22-35: a pessoa P é removida e, em seguida, verifica-se se ela não existe mais na lista.
Na linha 11, vemos que os threads são do tipo [ThreadDaoMajEnfants]. O construtor desse tipo possui três parâmetros:
- o nome atribuído ao thread, para que seja possível acompanhá-lo por meio de logs
- uma referência na camada [dao] para que o thread tenha acesso a ela
- o ID da pessoa sobre a qual o thread deve trabalhar
O tipo [ThreadDaoMajEnfants] é o seguinte:
- linha 9: [ThreadDaoMajEnfants] é, de fato, um thread
- linhas 18-22: o construtor que inicializa o thread com três informações
- o nome [name] atribuído ao thread
- uma referência [dao] à camada [dao]. Observe-se que, mais uma vez, estamos trabalhando com o tipo da interface [IDao] e não com o da implementação [DaoImpl].
- o identificador [id] da pessoa na qual o thread deve trabalhar
Quando [test4] inicia um thread [ThreadDaoMajEnfants] (linha 12 do test4), o método [run] (linha 25) deste último é executado:
- linhas 78-81: o método privado [suivi] permite gerar logs na tela. O método [run] utiliza-o para permitir o acompanhamento da execução da thread.
- o thread tentará incrementar em 1 o número de filhos da pessoa P com o identificador [id]. Essa atualização pode exigir várias tentativas. Consideremos dois threads: [TH1] e [TH2]. [TH1] solicita uma cópia da pessoa P à camada [dao]. Ele a obtém e constata que ela possui a versão V1. [TH1] é interrompido. [TH2], que vinha logo atrás, faz o mesmo e obtém a mesma versão V1 da pessoa P. [TH2] é interrompido. [TH2] retoma o controle, incrementa o número de filhos de P e salva suas alterações. Sabemos que, nesse momento, essas alterações são salvas e que a versão de P passará para V2. [TH1] concluiu seu trabalho. [TH2] retoma o controle e faz o mesmo. Sua atualização de P será recusada, pois ele possui uma cópia de P na versão V1, enquanto o P original agora está na versão V2. [TH2] precisa, então, repetir todo o ciclo de [lecture -> mise à jour -> sauvegarde]. É por isso que encontramos o loop nas linhas 32 a 72. Nele, o thread:
- solicita uma cópia da pessoa P a ser modificada (linha 34)
- aguarda 10 ms (linha 43). Isso é artificial e tem como objetivo interromper o thread entre a leitura da pessoa P e sua atualização efetiva na lista de pessoas, a fim de aumentar a probabilidade de conflitos.
- incrementa o número de filhos de P (linha 54) e salva P (linha 56). Se o thread não tiver a versão correta de P, uma exceção será acionada pela camada [dao]. Em seguida, recupera-se o código da exceção (linha 61) para verificar se é realmente o código 3 (versão incorreta de P). Se não for o caso, a exceção é reenviada ao método chamador, que, no final, é o método de teste [test4]. Se ocorrer a exceção com código 3, reiniciamos o ciclo [lecture -> mise à jour -> sauvegarde]. Se não houver exceção, a atualização foi realizada e o trabalho da thread está concluído.
Quais são os resultados dos testes?
Na primeira configuração testada:
- comentamos a instrução de espera no método [saveOne] de [DaoImpl] (linha 83, parágrafo 14.4).
- o método [test4] cria 100 threads (linha 8, parágrafo 14.5).
Obtêm-se os seguintes resultados:

Os cinco testes foram bem-sucedidos.
Na segunda configuração testada:
- descomenta-se a instrução de espera no método [saveOne] de [DaoImpl] (linha 83, parágrafo 14.4).
- o método [test4] cria 2 threads (linha 8, parágrafo 14.5).
Obtêm-se os seguintes resultados:
![]() | ![]() |
O teste [test4] falhou. Foram criadas duas threads, cada uma encarregada de incrementar em 1 o número de filhos de uma pessoa P, que inicialmente tinha 0. Esperávamos, portanto, 2 filhos após a execução das duas threads, mas temos apenas um.
Vamos acompanhar os logs de tela do [test4] para entender o que aconteceu:
- linha 1: o thread nº 0 inicia seu trabalho
- linha 2: ele recuperou uma cópia da pessoa P e constatou que o número de filhos dela era 0
- linha 3: ele encontra o [Thread.sleep(10)] do seu método [run] e, portanto, para no tempo [1145536368171] (ms)
- linha 4: a thread nº 1 recupera então o processador e inicia seu trabalho
- linha 5: ele recuperou uma cópia da pessoa P e constata que o número de filhos dela é 0
- linha 6: ele encontra o [Thread.sleep(10)] do seu método [run] e, portanto, é interrompido
- linha 7: o thread nº 0 recupera o processador no tempo [1145536368187] (ms), c.a.d. 16 ms após tê-lo perdido.
- linha 8: o mesmo ocorre com o thread nº 1
- linha 9: o thread nº 0 fez sua atualização e alterou o número de filhos para 1
- linha 10: o thread nº 1 fez o mesmo
A questão é saber por que o thread nº 1 conseguiu fazer sua atualização, embora, normalmente, ele já não possuísse a versão correta da pessoa P, que acabara de ser atualizada pelo thread nº 0.
Em primeiro lugar, é possível notar uma anomalia entre as linhas 7 e 8: parece que o thread nº 0 perdeu o controle do processador entre essas duas linhas para o thread nº 1. O que ele estava fazendo nesse momento? Ele estava executando o método [saveOne] da camada [dao]. Esse método tem a seguinte estrutura (ver parágrafo 14.4):
- o thread nº 0 executou [saveOne] e chegou até a linha 8, onde foi obrigado a liberar o processador. Nesse intervalo, ele leu a versão da pessoa P, que era 1, pois a pessoa P ainda não havia sido atualizada.
- Como o processador ficou livre, foi o thread nº 1 que o herdou. Ele, por sua vez, executou o [saveOne] e chegou até a linha 8, onde foi obrigado a liberar o processador. Enquanto isso, ele leu a versão da pessoa P, que era 1, pois a pessoa P ainda não havia sido atualizada.
- Como o processador ficou livre, o thread nº 0 assumiu o controle. A partir da linha 9, ele fez sua atualização e alterou o número de filhos para 1. Em seguida, o método [run] da thread nº 0 foi concluído e a thread exibiu o log informando que havia alterado o número de filhos para 1 (linha 9).
- Como o processador ficou livre, a thread nº 1 assumiu o controle. A partir da linha 9, ela fez sua atualização e alterou o número de filhos para 1. Por que 1? Porque ela possui uma cópia de P com o número de filhos igual a 0. É o que indica o log (linha 5). Em seguida, o método [run] da thread nº 1 foi concluído e a thread exibiu o log informando que havia alterado o número de filhos para 1 (linha 10).
De onde vem o problema? Ele decorre do fato de que a thread nº 0 não teve tempo de validar sua modificação e, portanto, de alterar a versão da pessoa P antes que a thread nº 1 tentasse ler essa versão para verificar se a pessoa P havia mudado. Esse cenário é improvável, mas não impossível. Foi necessário forçar a thread nº 0 a perder o controle do processador para que ele ocorresse com apenas duas threads. Sem esse artifício, a configuração anterior não havia conseguido reproduzir esse mesmo caso com 100 threads. O teste [test4] havia sido bem-sucedido.
Qual é a solução? Sem dúvida, há várias. Uma delas, simples de implementar, é sincronizar o método [saveOne]:
public synchronized void saveOne(Personne personne)
A palavra-chave [synchronized] garante que apenas um thread por vez possa executar o método. Assim, o thread nº 1 só terá permissão para executar [saveOne] quando o thread nº 0 tiver saído dele. Assim, temos a certeza de que a versão da pessoa P terá sido alterada quando o thread nº 1 entrar em [saveOne]. Sua atualização será então recusada, pois ele não terá a versão correta de P.
São esses os quatro métodos da camada [dao] que deveriam ser sincronizados. Decidimos, no entanto, manter essa camada tal como foi descrita e adiar a sincronização para a camada [service]. Há várias razões para isso:
- partimos do pressuposto de que o acesso à camada [dao] sempre ocorre por meio de uma camada [service]. Esse é o caso em nossa aplicação web.
- pode ser necessário sincronizar também o acesso aos métodos da camada [service] por motivos diferentes daqueles que nos levariam a sincronizar os da camada [dao]. Nesse caso, não há necessidade de sincronizar os métodos da camada [dao]. Se tivermos certeza de que:
- todo acesso à camada [dao] passa pela camada [service]
- que apenas um único thread por vez utiliza a camada [service]
então temos a garantia de que os métodos da camada [dao] não serão executados por dois threads ao mesmo tempo.
Agora, vamos conhecer a camada [service].
14.6. A camada [service]
A camada [service] é composta pelas seguintes classes e interfaces:
![]()
- [IService] é a interface apresentada pela camada [dao]
- [ServiceImpl] é uma implementação dessa interface
A interface [IService] é a seguinte:
Ela é idêntica à interface [IDao].
A implementação [ServiceImpl] da interface [IService] é a seguinte:
- linhas 10-19: o atributo [IDao dao] é uma referência à camada [dao]. Ele será inicializado pelo Spring IoC.
- linhas 22-24: implementação do método [getAll] da interface [IService]. O método se limita a delegar a solicitação à camada [dao].
- linhas 27-29: implementação do método [getOne] da interface [IService]. O método se limita a delegar a solicitação à camada [dao].
- linhas 32-34: implementação do método [saveOne] da interface [IService]. O método limita-se a delegar a solicitação à camada [dao].
- linhas 37-39: implementação do método [deleteOne] da interface [IService]. O método limita-se a delegar a solicitação à camada [dao].
- Todos os métodos são sincronizados (palavra-chave `synchronized`), garantindo que apenas um thread por vez possa utilizar a camada [service] e, consequentemente, a camada [dao].
14.7. Testes da camada [service]
Um teste JUnit é escrito para a camada [service]:
![]() | ![]() |
[TestService] é o teste JUnit. Os testes realizados são estritamente idênticos aos realizados para a camada [dao]. O esboço de [TestService] é o seguinte:
- linha 9: a camada [service] testada é do tipo [ServiceImpl].
- linhas 11-15: o construtor do teste JUnit cria uma instância da camada [service] a ser testada (linha 12), cria uma instância da camada [dao] (linha 13) e indica à camada [service] que ela deve utilizar essa camada [dao] (linha 14).
O método [test1] testa os quatro métodos da interface [IService] da mesma forma que o método de teste da camada [dao] com o mesmo nome. Simplesmente, acessa-se a camada [service] (linhas 25, 32, 35) em vez da camada [dao].
O método [test4] visa identificar problemas de acesso simultâneo aos métodos da camada [service]. Ele é, mais uma vez, idêntico ao método de teste [test4] da camada [dao]. No entanto, há alguns detalhes que mudam:
- recorre-se à camada [service] em vez da camada [dao] (linha 55)
- passamos aos threads uma referência à camada [service] em vez da camada [dao] (linha 61)
O tipo [ThreadServiceMajEnfants] também é praticamente idêntico ao tipo [ThreadDaoMajEnfants], com a única diferença de que ele opera com a camada [service] e não com a camada [dao]:
- linha 12: o thread opera com a camada [service]
Realizamos os testes com a configuração que apresentou problema na camada [dao]:
- descomentamos a instrução de espera no método [saveOne] de [DaoImpl] (linha 83, parágrafo 14.4).
- o método [test4] cria 100 threads (linha 65, parágrafo 14.7).
Os resultados obtidos são os seguintes:
![]() |
Foi a sincronização dos métodos da camada [service] que permitiu o sucesso do teste [test4].
14.8. A camada [web]
Vale lembrar a arquitetura de três camadas do nosso aplicativo:
![]() |
A camada [web] apresentará telas ao usuário para permitir que ele gerencie o grupo de pessoas:
- lista de pessoas do grupo
- adição de uma pessoa ao grupo
- alteração de uma pessoa do grupo
- exclusão de uma pessoa do grupo
Para isso, ela se baseará na camada [service], que, por sua vez, utilizará a camada [dao]. Já apresentamos as telas gerenciadas pela camada [web] (parágrafo 14.1). Para descrever a camada web, apresentaremos sucessivamente:
- sua configuração
- suas visualizações
- seu controlador
- alguns testes
14.8.1. Configuração do aplicativo web
O projeto Eclipse da aplicação é o seguinte:

- no pacote [istia.st.mvc.personnes.web], encontra-se o controlador [Application].
- As páginas JSP / JSTL estão no [WEB-INF/vues].
- A pasta [lib] contém os arquivos de terceiros necessários para o aplicativo. Eles podem ser visualizados na pasta [Web App Libraries].
[web.xml]
O arquivo [web.xml] é o arquivo utilizado pelo servidor web para carregar o aplicativo. Seu conteúdo é o seguinte:
<?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>
<!-- Mapeamento de ServletPersonne-->
<servlet-mapping>
<servlet-name>personnes</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- arquivos de página inicial -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Página de erro inesperado -->
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/vues/exception.jsp</location>
</error-page>
</web-app>
- linhas 27-30: as URLs [/do/*] serão processadas pelo servlet [personnes]
- linhas 9-12: o servlet [personnes] é uma instância da classe [Application], uma classe que iremos criar.
- linhas 13-24: definem três parâmetros [urlList, urlEdit, urlErreurs] que identificam as URLs das páginas JSP das visualizações [list, edit, erreurs].
- linhas 32-34: o aplicativo possui uma página inicial padrão [index.jsp], localizada na raiz da pasta do aplicativo web.
- linhas 36-39: o aplicativo possui uma página de erros padrão que é exibida quando o servidor web detecta uma exceção não tratada pelo aplicativo.
- linha 37: a tag <exception-type> indica o tipo de exceção tratada pela diretiva <error-page>, neste caso o tipo [java.lang.Exception] e seus derivados, ou seja, todas as exceções.
- linha 38: a tag <location> indica a página JSP a ser exibida quando ocorrer uma exceção do tipo definido por <exception-type>. A exceção ocorrida está disponível nessa página em um objeto chamado exception, caso a página contenha a diretiva:
<%@ page isErrorPage="true" %>
- (continuação)
- se <exception-type> especificar um tipo T1 e uma exceção do tipo T2, não derivada de T1, for reportada ao servidor web, este enviará ao cliente uma página de exceção proprietária, geralmente pouco intuitiva. Daí a importância da tag <error-page> no arquivo [web.xml].
[index.jsp]
Essa página é exibida se um usuário solicitar diretamente o contexto do aplicativo sem especificar uma URL, c.a.d. Aqui, [/personnes-01]. Seu conteúdo é o seguinte:
<%@ 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] redireciona o cliente para a URL [/do/list]. Essa URL exibe a lista de pessoas do grupo.
14.8.2. As páginas JSP / JSTL do aplicativo
A visualização [list.jsp]
Ela serve para exibir a lista de pessoas:

Seu código é o seguinte:
<%@ 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>
- essa visualização recebe um elemento em seu modelo:
- o elemento [personnes] associado a um objeto do tipo [ArrayList] de objetos do tipo [Personne]
- linhas 22-34: percorre-se a lista ${pessoas} para exibir uma tabela HTML contendo as pessoas do grupo.
- linha 31: a URL apontada pelo link [Modifier] é configurada pelo campo [id] da pessoa atual, para que o controlador associado à URL [/do/edit] saiba qual é a pessoa a ser modificada.
- linha 32: o mesmo procedimento é seguido para o link [Supprimer].
- linha 28: para exibir a data de nascimento da pessoa no formato JJ/MM/AAAA, usa-se a tag <dt> da biblioteca de tags [DateTime] do projeto Apache [Jakarta Taglibs]:

O arquivo de descrição dessa biblioteca de tags está definido na linha 3.
- linha 37: o link [Ajout] para adicionar uma nova pessoa tem como destino a URL [/do/edit], assim como o link [Modifier] da linha 31. É o valor -1 do parâmetro [id] que indica que se trata de uma adição, e não de uma modificação.
A visualização [edit.jsp]
Ela serve para exibir o formulário de adição de uma nova pessoa ou de modificação de uma pessoa existente:
![]() |
O código da visualização [edit.jsp] é o seguinte:
<%@ 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>
Essa visualização apresenta um formulário para adicionar uma nova pessoa ou atualizar uma pessoa existente. Daqui em diante, para simplificar a redação, utilizaremos apenas o termo [mise à jour]. O botão [Valider] (linha 73) aciona o POST do formulário na URL [/do/validate] (linha 16). Se o POST falhar, a visualização [edit.jsp] é exibida novamente com o(s) erro(s) ocorrido(s); caso contrário, a visualização [list.jsp] é exibida.
- A visualização [edit.jsp], exibida tanto em um GET quanto em um POST que falha, recebe os seguintes elementos em seu modelo:
atributo | GET | POST |
identificador da pessoa atualizada | idem | |
sua versão | idem | |
seu nome | nome inserido | |
sobrenome | sobrenome inserido | |
data de nascimento | data de nascimento inserida | |
estado civil | estado civil inserido | |
número de filhos | número de filhos inserido | |
em branco | uma mensagem de erro indicando que a adição ou modificação falhou no momento da execução do POST, acionada pelo botão [Envoyer]. Vazio se não houver erro. | |
vazio | indica um nome incorreto – vazio caso contrário | |
vazio | indica um sobrenome incorreto – vazio caso contrário | |
vazio | indica uma data de nascimento incorreta – vazio caso contrário | |
vazio | indica um número incorreto de filhos – vazio caso contrário |
- linhas 11-15: se o POST do formulário apresentar erro, teremos [erreurEdit!=''] e uma mensagem de erro será exibida.
- linha 16: o formulário será enviado para a URL [/do/validate]
- linha 20: o elemento [id] do modelo é exibido
- linha 24: o elemento [version] do modelo é exibido
- linhas 26-32: inserção do nome da pessoa:
- na exibição inicial do formulário (GET), ${prenom} exibe o valor atual do campo [prenom] do objeto [Personne] atualizado e ${erreurPrenom} está vazio.
- em caso de erro após o POST, exibe-se novamente o valor inserido ${prenom}, bem como a eventual mensagem de erro ${erreurPrenom}
- linhas 33-39: inserção do sobrenome da pessoa
- linhas 40-46: inserção da data de nascimento da pessoa
- linhas 47-61: inserção do estado civil da pessoa por meio de um botão de opção. Utiliza-se o valor do campo [marie] do objeto [Personne] para determinar qual dos dois botões de opção deve ser marcado.
- linhas 62-68: inserção do número de filhos da pessoa
- linha 71: um campo oculto HTML denominado [id], cujo valor corresponde ao campo [id] da pessoa que está sendo atualizada; -1 para uma adição, outro valor para uma modificação.
- linha 72: um campo oculto HTML, denominado [version], cujo valor corresponde ao campo [id] da pessoa que está sendo atualizada.
- linha 73: o botão [Valider] do tipo [Submit] do formulário
- linha 74: um link que permite retornar à lista de pessoas. Ele foi denominado [Annuler] porque permite sair do formulário sem validá-lo.
A visualização [exception.jsp]
Ela serve para exibir uma página informando que ocorreu uma exceção não tratada pelo aplicativo e que foi encaminhada ao servidor web.
Por exemplo, vamos excluir uma pessoa que não existe no grupo:
![]() |
O código da visualização [exception.jsp] é o seguinte:
<%@ 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>
- essa visualização recebe, em seu modelo, o elemento [exception], que corresponde à exceção interceptada pelo servidor web. Para que esse elemento seja incluído no modelo da página JSP pelo servidor web, é necessário que a página tenha definido a tag da linha 3.
- linha 6: define-se o código de status HTTP da resposta como 200. Esse é o primeiro cabeçalho HTTP da resposta. O código 200 indica ao cliente que sua solicitação foi atendida. Geralmente, um documento HTML foi incluído na resposta do servidor. É o que ocorre neste caso. Se o código de estado HTTP da resposta não for definido como 200, ele terá aqui o valor 500, o que significa que ocorreu um erro. De fato, o servidor web, ao interceptar uma exceção não tratada, considera essa situação anormal e a sinaliza com o código 500. A reação ao código 500 varia de acordo com os navegadores: o Firefox exibe o documento que pode acompanhar essa resposta, enquanto o Firefox ignora esse documento e exibe sua própria página. É por esse motivo que substituímos o código 500 pelo código 200.
- linha 16: o texto da exceção é exibido
- linha 18: é oferecido ao usuário um link para voltar à lista de pessoas
A visualização [erreurs.jsp]
Ela serve para exibir uma página que sinaliza os erros de inicialização do aplicativo, c.a.d, e os erros detectados durante a execução do método [init] do servlet do controlador. Pode ser, por exemplo, a ausência de um parâmetro no arquivo [web.xml], conforme mostra o exemplo abaixo:

O código da página [erreurs.jsp] é o seguinte:
<%@ 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>
A página recebe em seu modelo um elemento [erreurs], que é um objeto do tipo [ArrayList] contendo objetos [String]; estes últimos são mensagens de erro. Elas são exibidas pelo loop das linhas 13 a 15.
14.8.3. O controlador da aplicação
O controlador [Application] está definido no pacote [istia.st.mvc.personnes.web]:
![]()
Estrut tura e inicialização do controlador
A estrutura do controlador [Application] é a seguinte:
- linhas 20-36: recuperam-se os parâmetros esperados no arquivo [web.xml].
- linhas 39-41: o parâmetro [urlErreurs] deve estar obrigatoriamente presente, pois indica a URL da visualização [erreurs] capaz de exibir eventuais erros de inicialização. Se ele não existir, a aplicação é interrompida com a execução de um [ServletException] (linha 40). Essa exceção será encaminhada ao servidor web e tratada pela tag <error-page> do arquivo [web.xml]. A visualização [exception.jsp] é, portanto, exibida:

O link [Retour à la liste] acima não funciona. Ao utilizá-lo, obtém-se a mesma resposta enquanto a aplicação não for modificada e recarregada. Ele é útil para outros tipos de exceções, como já vimos.
- linha 43: cria uma instância [DaoImpl] que implementa a camada [dao]
- linha 44: inicializa essa instância (criação de uma lista inicial com três pessoas)
- linha 46: cria uma instância [ServiceImpl] que implementa a camada [service]
- linha 47: inicializa a camada [service], fornecendo-lhe uma referência à camada [dao]
Após a inicialização do controlador, seus métodos dispõem de uma referência [service] à camada [service] (linha 15), que serão utilizadas para executar as ações solicitadas pelo usuário. Essas ações serão interceptadas pelo método [doGet], que as encaminhará para um método específico do controlador:
Url | Método HTTP | método do controlador |
GET | doListPersonnes | |
GET | doEditPersonne | |
POST | doValidatePersonne | |
GET | doDeletePersonne |
O método [doGet]
Este método tem como objetivo direcionar o processamento das ações solicitadas pelo usuário para o método correto. Seu código é o seguinte:
- linhas 7-13: verifica-se se a lista de erros de inicialização está vazia. Caso contrário, exibe-se a visualização [erreurs(erreurs)], que indicará o(s) erro(s).
- linha 15: recupera-se o método [get] ou [post] que o cliente utilizou para fazer sua solicitação.
- linha 17: recupera-se o valor do parâmetro [action] da consulta.
- linhas 23-27: processamento da solicitação [GET /do/list], que solicita a lista de pessoas.
- linhas 28-32: processamento da solicitação [GET /do/delete], que solicita a exclusão de uma pessoa.
- linhas 33-37: processamento da solicitação [GET /do/edit], que solicita o formulário de atualização de uma pessoa.
- linhas 38-42: processamento da solicitação [POST /do/validate], que solicita a validação do dado atualizado.
- linha 44: se a ação solicitada não for uma das cinco anteriores, então procede-se como se fosse [GET /do/list].
O método [doListPersonnes]
Este método processa a solicitação [GET /do/list], que solicita a lista de pessoas:

Seu código é o seguinte:
- linha 5: solicita-se à camada [service] a lista de pessoas do grupo e insere-se essa lista no modelo sob a chave “pessoas”.
- linha 7: exibe-se a visualização [list.jsp] descrita no parágrafo 14.8.2.
O método [doDeletePersonne]
Este método processa a consulta [GET /do/delete?id=XX], que solicita a exclusão da pessoa com id=XX. A URL [/do/delete?id=XX] é a dos links [Supprimer] da visualização [list.jsp]:

cujo código é o seguinte:
Na linha 12, vemos a URL [/do/delete?id=XX] do link [Supprimer]. O método [doDeletePersonne], que deve processar essa URL, deve remover a pessoa com id=XX e, em seguida, exibir a nova lista de pessoas do grupo. Seu código é o seguinte:
- linha 5: a URL processada tem o formato [/do/delete?id=XX]. Recupera-se o valor [XX] do parâmetro [id].
- linha 7: solicita-se à camada [service] a exclusão da pessoa com o ID obtido. Não realizamos nenhuma verificação. Se a pessoa que se pretende excluir não existir, a camada [dao] lança uma exceção que é repassada pela camada [service]. Também não a tratamos aqui, no controlador. Portanto, ela será repassada até o servidor web, que, por configuração, exibirá a página [exception.jsp], descrita no parágrafo 14.8.2:

- linha 9: se a exclusão ocorreu (sem exceção), solicita-se ao cliente que seja redirecionado para a URL relativa [list]. Como a página que acabou de ser processada é [/do/delete], a URL de redirecionamento será [/do/list]. O navegador será, portanto, levado a acessar [GET /do/list], o que fará com que a lista de pessoas seja exibida.
O método [doEditPersonne]
Este método processa a solicitação [GET /do/edit?id=XX], que solicita o formulário de atualização da pessoa com id=XX. A URL [/do/edit?id=XX] é a dos links [Modifier] e a do link [Ajout] da visualização [list.jsp]:

cujo código é o seguinte:
Na linha 11, vemos a URL [/do/edit?id=XX] do link [Modifier] e, na linha 17, a URL [/do/edit?id=-1] do link [Ajout]. O método [doEditPersonne] deve exibir o formulário de edição da pessoa com id=XX ou, caso se trate de uma adição, apresentar um formulário em branco.
![]() | ![]() |
O código do método [doEditPersonne] é o seguinte:
- o GET tem como destino uma URL do tipo [/do/edit?id=XX]. Na linha 5, recuperamos o valor de [id]. Em seguida, há dois casos:
- se o id for diferente de -1, trata-se de uma modificação e é necessário exibir um formulário pré-preenchido com as informações da pessoa a ser modificada. Na linha 10, essa pessoa é solicitada à camada [service].
- Se o id for igual a -1, trata-se de uma adição e é necessário exibir um formulário vazio. Para isso, é criado um registro vazio nas linhas 13-14.
- O objeto [Personne] obtido é inserido no modelo da página [edit.jsp] descrito no parágrafo 14.8.2. Esse modelo inclui os seguintes elementos: [erreurEdit, id, version, prenom, erreurPrenom, nom, erreurNom, dateNaissance, erreurDateNaissance, marie, nbEnfants, erreurNbEnfants]. Esses elementos são inicializados nas linhas 17 a 30, com exceção daqueles cujo valor é a string vazia [erreurPrenom, erreurNom, erreurDateNaissance, erreurNbEnfants]. Sabe-se que, na ausência desses elementos no modelo, a biblioteca JSTL exibirá uma string vazia como valor para eles. Embora o elemento [erreurEdit] também tenha como valor uma string vazia, ele é, no entanto, inicializado, pois é realizada uma verificação de seu valor na página [edit.jsp].
- Assim que o modelo estiver pronto, o controle é passado para a página [edit.jsp], linhas 32-33, que irá gerar a visualização [edit].
O método [doValidatePersonne]
Este método processa a solicitação [POST /do/validate], que valida o formulário de atualização. Essa solicitação POST é acionada pelo botão [Valider]:

Recordemos os campos de preenchimento do formulário HTML da visualização acima:
A consulta POST contém os parâmetros [prenom, nom, dateNaissance, marie, nbEnfants, id, version] e é enviada para a URL [/do/validate] (linha 1). Ela é processada pelo seguinte método [doValidatePersonne]:
- linhas 8-14: o parâmetro [prenom] da solicitação POST é recuperado e sua validade é verificada. Caso esteja incorreto, o elemento [erreurPrenom] é inicializado com uma mensagem de erro e inserido nos atributos da consulta.
- linhas 16-22: procede-se de maneira semelhante para o parâmetro [nom]
- linhas 24-32: procede-se de maneira semelhante para o parâmetro [dateNaissance]
- linha 34: recupera-se o parâmetro [marie]. Não se verifica sua validade porque, a princípio, ele provém do valor de um botão de opção. Dito isso, nada impede que um programa gere um [POST /personnes-01/do/validate] acompanhado de um parâmetro [marie] inventado. Portanto, devemos testar a validade desse parâmetro. Aqui, contamos com nosso gerenciamento de exceções, que faz com que a página [exception.jsp] seja exibida caso o controlador não as gerencie por conta própria. Portanto, se a conversão do parâmetro [marie] em booleano falhar na linha 34, será gerada uma exceção que resultará no envio da página [exception.jsp] ao cliente. Esse funcionamento nos satisfaz.
- linhas 34-54: recuperamos o parâmetro [nbEnfants] e verificamos seu valor.
- linha 56: recuperamos o parâmetro [id] sem verificar seu valor
- linha 58: fazemos o mesmo com o parâmetro [version]
- linhas 60-65: se o formulário estiver com erros, ele é exibido novamente com as mensagens de erro geradas anteriormente
- linhas 67-69: se estiver válido, cria-se um novo objeto [Personne] com os elementos do formulário
- linhas 70-78: o usuário é salvo. O salvamento pode falhar. Em um ambiente com vários usuários, o usuário a ser alterado pode ter sido excluído ou já alterado por outra pessoa. Nesse caso, a camada [dao] lançará uma exceção que é tratada aqui.
- linha 80: se não houver exceção, redirecionamos o cliente para a URL [/do/list] para apresentar a ele o novo estado do grupo.
- linha 75: se ocorreu uma exceção durante o salvamento, solicitamos novamente a exibição do formulário inicial, passando a mensagem de erro da exceção (3º parâmetro).
O método [showFormulaire] (linhas 84-101) constrói o modelo necessário para a página [edit.jsp] com os valores inseridos (request.getParameter(" ... ")). Vale lembrar que as mensagens de erro já foram inseridas no modelo pelo método [doValidatePersonne]. A página [edit.jsp] é exibida nas linhas 99-100.
14.9. Testes da aplicação web
Vários testes foram apresentados no parágrafo 14.1. Convidamos o leitor a repeti-los. Mostramos aqui outras capturas de tela que ilustram casos de conflitos de acesso aos dados em um ambiente com múltiplos usuários:
[Firefox] será o navegador do usuário U1. Este solicita a URL [http://localhost:8080/personnes-01]:

[IE] será o navegador do usuário U2. Este solicita a mesma URL:

O usuário U1 acessa a edição do perfil da pessoa [Lemarchand]:

O usuário U2 faz o mesmo:

O usuário U1 faz alterações e confirma:
![]() |
O usuário U2 faz o mesmo:
![]() |
O usuário U2 retorna à lista de pessoas por meio do link [Annuler] do formulário:

Ele encontra a pessoa [Lemarchand] tal como U1 a modificou. Agora, U2 exclui [Lemarchand]:
![]() |
U1 ainda mantém sua própria lista e deseja alterar [Lemarchand] novamente:
![]() |
U1 usa o link [Retour à la liste] para ver do que se trata:

Ele descobre que, de fato, [Lemarchand] não faz mais parte da lista...
14.10. Conclusion
Implementamos a arquitetura MVC em uma arquitetura de três camadas [web, metier, dao], utilizando um exemplo básico de gerenciamento de uma lista de pessoas. Isso nos permitiu utilizar os conceitos apresentados nas seções anteriores. Na versão analisada, a lista de pessoas era mantida na memória. Em breve, estudaremos versões nas quais essa lista será mantida em uma tabela de banco de dados.
Mas, antes disso, vamos apresentar uma ferramenta chamada Spring IoC, que facilita a integração das diferentes camadas de uma aplicação ntier.

















