14. Aplicação Web MVC numa arquitetura de 3 camadas – Exemplo 1
14.1. Introdução
Até este momento, limitámo-nos a exemplos destinados a fins educativos. Por esse motivo, tiveram de ser simples. Apresentamos agora uma aplicação básica que, no entanto, é mais rica em funcionalidades do que qualquer uma das apresentadas até agora. Será única na medida em que utiliza as três camadas de uma arquitetura de três camadas:

Recomenda-se aos leitores que revisem os princípios de uma aplicação Web MVC numa arquitetura de três camadas na Secção 4, caso os tenham esquecido.
A aplicação web que vamos escrever permitir-nos-á gerir um grupo de pessoas utilizando quatro operações:
- lista de pessoas no grupo
- adicionar uma pessoa ao grupo
- alterar uma pessoa no grupo
- remover uma pessoa do grupo
Estas são as quatro operações básicas numa tabela de base de dados. Vamos escrever duas versões desta aplicação:
- Na versão 1, a camada [DAO] não utilizará uma base de dados. Os membros do grupo serão armazenados num objeto [ArrayList] simples, gerido internamente pela camada [DAO]. Isto permitirá ao leitor testar a aplicação sem as restrições de uma base de dados.
- Na versão 2, colocaremos o grupo de pessoas numa tabela de base de dados. Demonstraremos que isto pode ser feito sem afetar a camada web da versão 1, que permanecerá inalterada.
As seguintes capturas de ecrã mostram as páginas que a aplicação troca com o utilizador.



![]() |
![]() |
14.2. O Projeto Eclipse
O projeto da aplicação chama-se [people-01]:

Este projeto abrange as três camadas da arquitetura de três camadas da aplicação:
![]() |
- a camada [dao] está contida no pacote [istia.st.mvc.personnes.dao]
- a camada [business] 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.entities] contém objetos partilhados entre diferentes camadas
- o pacote [istia.st.mvc.people.tests] contém os testes JUnit para as camadas [DAO] e [service]
Iremos explorar as três camadas [dao], [service] e [web] sucessivamente. Uma vez que demoraria demasiado tempo a escrever e poderia ser demasiado tedioso de ler, por vezes poderemos avançar rapidamente pelas explicações, exceto quando o material apresentado for novo.
14.3. Representação de uma pessoa
A aplicação gere um grupo de pessoas. As capturas de ecrã na Secção 14.1 mostraram algumas das características de uma pessoa. Formalmente, estas são representadas por uma classe [Person]:
![]()
A classe [Person] é a seguinte:
- Uma pessoa é identificada pelas seguintes informações:
- id: um identificador único para uma pessoa
- last_name: o apelido da pessoa
- firstName: o seu nome próprio
- dateOfBirth: a data de nascimento
- maritalStatus: se é casada ou não
- nbChildren: o número de filhos
- O atributo [version] é um atributo adicionado artificialmente para efeitos da aplicação. Numa perspetiva orientada para objetos, teria provavelmente sido preferível adicionar este atributo a uma classe derivada de [Person]. A sua necessidade torna-se evidente quando se consideram os casos de utilização da aplicação web. Um desses casos de utilização é o seguinte:
No momento T1, o utilizador U1 começa a editar uma pessoa P. Nesta altura, o número de filhos é 0. U1 altera este número para 1, mas antes de validar a alteração, o utilizador U2 começa a editar a mesma pessoa P. Como U1 ainda não validou a sua alteração, U2 vê o número de filhos como 0. U2 altera o nome da pessoa P para maiúsculas. Em seguida, U1 e U2 guardam as suas alterações nessa ordem. A alteração de U2 terá precedência: o nome ficará em maiúsculas e o número de filhos permanecerá em zero, mesmo que U1 acredite ter alterado esse número para 1.
O conceito de versão de uma pessoa ajuda-nos a resolver este problema. Vamos rever o mesmo caso de utilização:
No momento T1, um utilizador U1 começa a editar uma pessoa P. Neste 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 sua edição, um utilizador U2 entra no modo de edição para a mesma pessoa P. Como U1 ainda não confirmou a sua edição, U2 vê o número de filhos como 0 e a versão como V1. U2 altera o nome da pessoa P para maiúsculas. Em seguida, U1 e U2 confirmam as suas edições nessa ordem. Antes de confirmar uma alteração, verificamos se o utilizador que está a modificar a pessoa P tem a mesma versão que a versão atualmente guardada da pessoa P. Este será o caso do utilizador U1. A sua alteração é, portanto, aceite e, em seguida, alteramos 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, vamos perceber que este tem a versão V1 da pessoa P, enquanto a versão atual é a V2. Podemos então informar o utilizador U2 de que outra pessoa agiu antes dele e que deve começar com a nova versão da pessoa P. Ele fará isso, recuperará a versão V2 da pessoa P, que agora tem um filho, colocará o nome em maiúsculas e validará. A sua modificação será aceite se a pessoa P registada ainda tiver a versão V2. Em última análise, as modificações feitas por U1 e U2 serão tidas em conta, enquanto que no caso de utilização sem versões, uma das modificações se perdeu.
- linhas 32–40: um construtor capaz de inicializar os campos de uma pessoa. O campo [versão] é omitido.
- linhas 43–51: um construtor que cria uma cópia da pessoa que lhe é passada como parâmetro. Temos agora dois objetos com conteúdo idêntico, mas referenciados por dois ponteiros diferentes.
- Linha 55: O método [toString] é redefinido para devolver uma cadeia de caracteres que representa 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 desta interface, na qual o grupo de pessoas é encapsulado num objeto [ArrayList]
- [DaoException] é um tipo de exceção não verificada lançada pela camada [dao]
A interface [ IDao] é a seguinte:
- A interface possui quatro métodos para as quatro operações que pretendemos realizar no grupo de pessoas:
- getAll: para recuperar uma coleção de pessoas
- getOne: para recuperar uma pessoa com um ID específico
- saveOne: para adicionar uma pessoa (id=-1) ou modificar uma pessoa existente (id ≠ -1)
- deleteOne: para eliminar uma pessoa com um ID específico
A camada [DAO] pode lançar exceções. Estas serão do tipo [ DaoException]:
- Linha 3: A classe [DaoException], que deriva de [RuntimeException], é um tipo de exceção não tratada: o compilador não exige que:
- tratar este tipo de exceção com um bloco try/catch ao chamar um método que possa lançá-la
- incluirmos a palavra-chave "throws DaoException" na assinatura de um método que possa lançar a exceção
Esta técnica evita que tenhamos de assinar os métodos da interface [IDao] com exceções de um tipo específico. Qualquer implementação que lance exceções não verificadas será então aceitável, conferindo assim flexibilidade à arquitetura.
- Linha 6: um código de erro. A camada [dao] lançará várias exceções identificadas por diferentes códigos de erro. Isto permitirá que a camada responsável pelo tratamento da exceção determine a origem exata do erro e tome as medidas adequadas. Existem outras formas de alcançar o mesmo resultado. Uma delas é criar um tipo de exceção para cada tipo de erro possível, por exemplo, MissingLastNameException, MissingFirstNameException, IncorrectAgeException, ...
- linhas 13–16: o construtor que permite criar uma exceção identificada por um código de erro e uma mensagem de erro.
- Linhas 8–10: O método que permite ao manipulador de exceções recuperar 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 | |
Vamos apenas dar uma visão geral deste código. No entanto, vamos dedicar algum tempo às partes mais complexas.
- Linha 13: o objeto [ArrayList] que irá conter o grupo de pessoas
- Linha 16: o ID da última pessoa adicionada. Cada vez que uma nova pessoa for adicionada, este ID será incrementado em 1.
A classe [DaoImpl] será instanciada como uma única instância. Isto é conhecido como singleton. Uma aplicação web serve os seus utilizadores simultaneamente. A qualquer momento, existem múltiplas threads a executar-se no servidor web. Estas threads partilham os singletons:
- o da camada [dao]
- o da camada [service]
- os dos vários controladores, validadores de dados, etc., na camada web
Se um singleton tiver campos privados, deve perguntar-se imediatamente por que razão os tem. Estão justificados? Na verdade, serão partilhados entre diferentes threads. Se forem de leitura apenas, isto não é um problema se puderem ser inicializados num momento em que tenha a certeza de que existe apenas uma thread ativa. Geralmente sabemos como identificar esse momento. É quando a aplicação web é iniciada, mas ainda não começou a servir clientes. Se forem de leitura/gravação, então a sincronização de acesso aos campos deve ser implementada; caso contrário, o desastre é inevitável. Iremos ilustrar este problema quando testarmos a camada [dao].
- A classe [DaoImpl] não tem construtor. Por isso, será utilizado o seu construtor padrão.
- Linhas 19–38: O método [init] será chamado quando o singleton da camada [dao] for instanciado. Ele cria uma lista de três pessoas.
- Linhas 41–43: Implementa o método [getAll] da interface [IDao]. Retorna uma referência à lista de pessoas.
- Linhas 46–55: Implementa o método [getOne] da interface [IDao]. O seu parâmetro é o ID da pessoa que está a ser pesquisada.
Para a recuperar, chamamos um método privado [getPosition] nas linhas 113–126. Este método devolve a posição na lista da pessoa que está a ser procurada, ou -1 se a pessoa não for encontrada.
Se a pessoa for encontrada, o método [getOne] retorna uma referência (linha 51) a uma cópia dessa pessoa, não à própria pessoa. Na verdade, quando um utilizador pretende editar uma pessoa, as informações sobre essa pessoa são solicitadas à camada [dao] e passadas para a camada [web] para modificação, na forma de uma referência a um objeto [Person]. Esta referência serve como o contentor de entrada no formulário de edição. Quando o utilizador submete as suas alterações na camada web, o conteúdo do contentor de entrada será modificado. Se o contentor for uma referência à pessoa real na [ArrayList] da camada [dao], então essa pessoa é modificada, mesmo que as alterações não tenham sido apresentadas às camadas [service] e [dao]. Esta última é a única camada autorizada a gerir a lista de pessoas. Portanto, a camada web deve trabalhar numa cópia da pessoa a ser modificada. Aqui, a camada [dao] fornece essa cópia.
Se a pessoa procurada não for encontrada, é lançada uma [DaoException] com o código de erro 2 (linha 53).
- linhas 94–104: implementa o método [deleteOne] da interface [IDao]. O seu parâmetro é o ID da pessoa a ser eliminada. Se a pessoa a ser eliminada não existir, é lançada uma [DaoException] com o código de erro 2.
- Linhas 58–91: Implementa o método [saveOne] da interface [IDao]. O seu parâmetro é um objeto [Person]. Se este objeto tiver um id de -1, trata-se de uma nova pessoa a ser adicionada. Caso contrário, modifica a pessoa na lista com esse id utilizando os valores no parâmetro.
- Linha 60: A validade do parâmetro [Person] é verificada por um método privado [check] definido nas linhas 129–155. Este método realiza verificações básicas dos valores dos vários campos de [Person]. Sempre que uma anomalia é detetada, é lançada uma [DaoException] com um código de erro específico. Uma vez que o método [saveOne] não trata esta exceção, esta será propagada para o método de chamada.
- Linha 62: Se o parâmetro [Person] tiver um id igual a -1, trata-se de uma adição. O objeto [Person] é 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 [Person] tiver um [id] diferente de -1, isso implica a modificação da pessoa na lista interna com esse [id]. Primeiro, verificamos (linhas 70–75) se a pessoa a ser modificada existe. Se não for esse o caso, lançamos uma [DaoException] com o código de erro 2.
- Se a pessoa existir, verificamos se a sua versão atual corresponde à do parâmetro [Person], que contém as alterações a aplicar ao original. Se não for esse o caso, significa que o utilizador que tenta modificar a pessoa não possui a versão mais recente. Informamo-lo disso lançando uma [DaoException] com o código de erro 3 (linhas 79–80).
- Se tudo correr bem, as alterações são feitas no registo original da pessoa (linhas 85–90)
É evidente que este método deve ser sincronizado. Por exemplo, entre o momento em que verificamos se a pessoa a ser modificada está de facto presente e o momento em que a modificação é efetuada, a pessoa poderia ter sido removida da lista por outra pessoa. O método deve, portanto, ser declarado [synchronized] para garantir que apenas um thread o execute de cada vez. 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 as questões de sincronização, durante o teste da camada [dao], vamos pausar 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 efetivamente. A thread que executa [saveOne] perderá então a CPU para outra thread. Isto aumenta as nossas hipóteses de observar conflitos de acesso na lista de pessoas.
14.5. Testes da Camada [DAO]
É escrito um teste JUnit para a camada [dao]:
![]() | ![]() |
[TestDao] é o teste JUnit. Para destacar problemas de acesso simultâneo à lista de pessoas, são criadas threads do tipo [ThreadDaoMajEnfants]. Estas são responsáveis por aumentar em 1 o número de filhos de uma determinada pessoa.
[TestDao] tem cinco testes, [test1] a [test5]. Apresentamos aqui apenas dois deles; convidamos os leitores a explorar os restantes no código-fonte associado a este artigo.
- linha 9: referência à implementação da camada [dao] que está a ser testada
- linhas 12–15: o construtor de teste JUnit. Cria uma instância do tipo [DaoImpl] da camada [dao] a ser testada e inicializa-a.
O método [test1] testa os quatro métodos da interface [IDao] da seguinte forma:
- Linha 3: Solicitar a lista de pessoas
- linha 6: exibimos a 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]
O teste adiciona então uma pessoa, modifica-a e elimina-a. Assim, são utilizados os quatro métodos da interface [IDao].
- Linhas 8–10: É adicionada uma nova pessoa (id=-1).
- Linha 11: Recuperamos o ID da pessoa adicionada, pois a adição atribuiu-lhe um. Antes disso, ela não tinha nenhum.
- Linhas 13–14: Solicitamos à camada [dao] uma cópia da pessoa que acabou de ser adicionada. Tenha em mente que, se a pessoa solicitada não for encontrada, a camada [dao] lança uma exceção. Isto causará uma falha na linha 13. Poderíamos ter tratado este caso de forma mais elegante. Na linha 14, verificamos o nome da pessoa recuperada.
- Linhas 16–17: Modificamos este nome e pedimos à camada [DAO] para guardar as alterações.
- Linhas 19–20: Solicitamos à camada [DAO] uma cópia da pessoa que acabou de ser adicionada e verificamos o seu novo nome.
- Linha 22: Elimine a pessoa adicionada no início do teste.
- Linhas 23–34: Solicitamos uma cópia da pessoa que acabou de ser eliminada à camada [dao]. Deverá receber uma [DaoException] com o código 2.
- Linhas 36–37: A lista de pessoas é solicitada novamente. Devemos obter a mesma lista que no início do teste.
O método [test4] tem como objetivo destacar problemas com o acesso simultâneo aos métodos da camada [dao]. Lembre-se de que estes métodos não foram sincronizados. O código de teste é o seguinte:
- linhas 3–6: adicionamos uma pessoa P sem filhos à lista. Registamos o seu [id] (linha 6).
- linhas 7–13: Iniciamos N threads. Cada um deles incrementará o número de filhos da pessoa P em 1. No final, a pessoa P deverá ter N filhos.
- linhas 15–17: O método [test4] que iniciou as N threads aguarda que estas concluam o seu trabalho antes de verificar o novo número de filhos da pessoa P.
- linhas 18–21: Recuperamos a pessoa P e verificamos se o seu número de filhos é N.
- Linhas 22–35: A pessoa P é removida e verificamos se já não existe na lista.
Na linha 11, vemos que as threads são do tipo [ThreadDaoMajEnfants]. O construtor deste tipo tem três parâmetros:
- o nome dado à thread, usado para a rastrear através de registos
- uma referência à camada [dao] para que o thread possa aceder-lhe
- o ID da pessoa com quem o thread deve trabalhar
O tipo [ThreadDaoMajEnfants] é o seguinte:
- linha 9: [ThreadDaoMajEnfants] é, de facto, 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]. Note-se que, mais uma vez, estamos a trabalhar com o tipo de interface [IDao] e não com o tipo de implementação [DaoImpl].
- o identificador [id] da pessoa em que o thread irá trabalhar
Quando [test4] inicia uma thread [ThreadDaoMajEnfants] (linha 12 de test4), o seu método [run] (linha 25) é executado:
- linhas 78–81: o método privado [suivi] permite o registo no ecrã. O método [run] utiliza-o para acompanhar a execução da thread.
- O thread tenta incrementar em 1 o número de filhos da pessoa P com o identificador [id]. Esta atualização pode exigir várias tentativas. Consideremos dois threads [TH1] e [TH2]. [TH1] solicita uma cópia da pessoa P à camada [dao]. Obtém-na e regista que tem a versão V1. [TH1] é interrompido. [TH2], que a seguia, faz o mesmo e obtém a mesma versão V1 da pessoa P. [TH2] é interrompida. [TH2] retoma o controlo, incrementa o número de filhos da P e guarda as suas alterações. Sabemos que estas alterações estão agora guardadas e que a versão da P mudará para V2. [TH1] terminou o seu trabalho. [TH2] retoma o controlo e faz o mesmo. A sua atualização de P será rejeitada porque mantém uma cópia de P na versão V1, enquanto o P original está agora na versão V2. [TH2] deve então repetir todo o ciclo [ler -> atualizar -> guardar]. É por isso que encontramos o loop nas linhas 32–72. Neste loop, o thread:
- solicita uma cópia da pessoa P para modificar (linha 34)
- espera 10 ms (linha 43). Isto é artificial e visa interromper a thread entre a leitura da pessoa P e a 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 guarda P (linha 56). Se a thread não tiver a versão correta de P, será lançada uma exceção pela camada [dao]. Recuperamos então o código da exceção (linha 61) para verificar se é de facto o código 3 (versão incorreta de P). Se não for esse o caso, a exceção é lançada novamente para o método de chamada, em última instância o método de teste [test4]. Se tivermos a exceção de código 3, reiniciamos o ciclo [ler -> atualizar -> guardar]. Se não houver exceção, a atualização foi concluída e o trabalho do thread está terminado.
O que mostram os testes?
Na primeira configuração testada:
- comentamos a instrução de espera no método [saveOne] de [DaoImpl] (linha 83, secção 14.4).
- O método [test4] cria 100 threads (linha 8, secção 14.5).
Os seguintes resultados são obtidos:

Todos os cinco testes foram bem-sucedidos.
Na segunda configuração testada:
- A instrução «wait» no método [saveOne] de [DaoImpl] está descomentada (linha 83, secção 14.4).
- o método [test4] cria 2 threads (linha 8, secção 14.5).
Os seguintes resultados são obtidos:
![]() | ![]() |
O teste [test4] falhou. Criámos duas threads, cada uma com a tarefa de incrementar em 1 o número de filhos de uma pessoa P que inicialmente tinha 0. Por isso, esperávamos 2 filhos após a execução das duas threads, mas temos apenas um.
Vamos examinar os registos de ecrã do [test4] para compreender o que aconteceu:
- Linha 1: O thread n.º 0 inicia o seu trabalho
- linha 2: recuperou uma cópia da pessoa P e verifica que o número de filhos é 0
- linha 3: encontra o [Thread.sleep(10)] no seu método [run] e, por isso, faz uma pausa no momento [1145536368171] (ms)
- linha 4: a thread #1 assume então o controlo do processador e inicia o seu trabalho
- linha 5: recuperou uma cópia da pessoa P e verifica que o número de filhos é 0
- Linha 6: Encontra o [Thread.sleep(10)] no seu método [run] e, por isso, faz uma pausa
- Linha 7: A thread 0 recupera a CPU no momento [1145536368187] (ms), ou seja, 16 ms após a ter perdido.
- linha 8: o mesmo para o thread #1
- linha 9: o thread #0 atualizou-se e definiu o número de filhos para 1
- linha 10: o thread #1 fez o mesmo
A questão é: por que razão a thread n.º 1 conseguiu efetuar a sua atualização quando, normalmente, já não detinha a versão correta da pessoa P, que acabara de ser atualizada pela thread n.º 0?
Primeiro, podemos observar uma anomalia entre as linhas 7 e 8: parece que a thread #0 perdeu a CPU entre estas duas linhas para a thread #1. O que estava a fazer nesse momento? Estava a executar o método [saveOne] da camada [dao]. Este método tem a seguinte estrutura (ver secção 14.4):
- O thread #0 executou [saveOne] e avançou para a linha 8, onde foi forçado a libertar o processador. Entretanto, leu a versão da pessoa P, que era 1 porque a pessoa P ainda não tinha sido atualizada.
- Como a CPU ficou livre, a thread #1 assumiu o controlo. Por sua vez, executou [saveOne] e chegou à linha 8, onde foi forçada a libertar a CPU. Entretanto, leu a versão da pessoa P, que era 1 porque a pessoa P ainda não tinha sido atualizada.
- Como o processador ficou livre, o thread #0 adquiriu-o. A partir da linha 9, realizou a sua atualização e definiu o número de filhos para 1. Em seguida, o método [run] do thread #0 terminou, e o thread exibiu o registo indicando que tinha definido o número de filhos para 1 (linha 9).
- Como o processador ficou livre, a thread #1 herdou-o. A partir da linha 9, realizou a sua atualização e definiu o número de filhos para 1. Porquê 1? Porque mantém uma cópia de P com o número de filhos definido para 0. Isto é indicado pelo registo (linha 5). Em seguida, o método [run] da thread #1 terminou e a thread apresentou o registo indicando que tinha definido o número de filhos para 1 (linha 10).
De onde vem o problema? Decorre do facto de a thread #0 não ter tido tempo de confirmar a sua alteração e, assim, atualizar a versão da pessoa P antes de a thread #1 tentar ler essa versão para verificar se a pessoa P tinha mudado. Este cenário é improvável, mas não impossível. Tivemos de forçar a thread #0 a perder a CPU para que parecesse haver apenas duas threads. Sem esta solução alternativa, a configuração anterior não tinha conseguido reproduzir este mesmo cenário com 100 threads. O teste [test4] tinha sido bem-sucedido.
Qual é a solução? Sem dúvida, existem várias. Uma delas, que é simples de implementar, consiste em sincronizar o método [saveOne]:
public synchronized void saveOne(Personne personne)
A palavra-chave [synchronized] garante que apenas um thread de cada vez possa executar o método. Assim, o thread #1 só poderá executar [saveOne] depois de o thread #0 ter saído do método. Podemos então ter a certeza de que a versão da pessoa P terá sido alterada quando o thread #1 entrar em [saveOne]. A sua atualização será então rejeitada, pois não terá a versão correta de P.
Estes são os quatro métodos da camada [dao] que precisariam de ser sincronizados. No entanto, decidimos manter esta camada tal como descrita e transferir a sincronização para a camada [service]. Existem várias razões para isso:
- Partimos do princípio de que o acesso à camada [dao] ocorre sempre através de uma camada [service]. É o que acontece na nossa aplicação web.
- também pode ser necessário sincronizar o acesso aos métodos da camada [service] por razões diferentes daquelas que nos levariam a sincronizar os da camada [dao]. Neste caso, não há necessidade de sincronizar os métodos da camada [dao]. Se tivermos a certeza de que:
- todo o acesso à camada [DAO] passa pela camada [service]
- apenas um thread de cada vez utiliza a camada [service]
então podemos ter a certeza de que os métodos da camada [DAO] não serão executados por duas threads ao mesmo tempo.
Vamos agora explorar a camada [service].
14.6. A camada [service]
A camada [service] é composta pelas seguintes classes e interfaces:
![]()
- [IService] é a interface exposta pela camada [dao]
- [ServiceImpl] é uma implementação desta interface
A interface [IService] é a seguinte:
É idêntico à interface [IDao].
A implementação [ServiceImpl] da interface [IService] é a seguinte:
- linhas 10–19: O atributo [IDao dao] é uma referência à camada [dao]. Será inicializado pelo Spring IoC.
- linhas 22–24: implementação do método [getAll] da interface [IService]. O método simplesmente delega a solicitação à camada [dao].
- linhas 27–29: implementação do método [getOne] da interface [IService]. O método simplesmente delega a solicitação à camada [dao].
- Linhas 32–34: Implementação do método [saveOne] da interface [IService]. O método simplesmente delega a solicitação à camada [dao].
- Linhas 37–39: Implementação do método [deleteOne] da interface [IService]. O método simplesmente delega a solicitação à camada [dao].
- Todos os métodos são sincronizados (utilizando a palavra-chave `synchronized`), garantindo que apenas um thread de cada vez possa utilizar a camada [service] e, consequentemente, a camada [dao].
14.7. Testes para a camada [service]
É escrito um teste JUnit para a camada [service]:
![]() | ![]() |
[TestService] é o teste JUnit. Os testes realizados são exatamente os mesmos que os realizados para a camada [dao]. O esqueleto de [TestService] é o seguinte:
- Linhas 9: A camada [service] que está a ser testada é do tipo [ServiceImpl].
- linhas 11–15: o construtor de teste JUnit cria uma instância da camada [service] a ser testada (linha 12), cria uma instância da camada [dao] (linha 13) e instrui a camada [service] a utilizar esta 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. A única diferença é que acede à camada [service] (linhas 25, 32, 35) em vez de à camada [dao].
O método [test4] tem como objetivo destacar problemas com o acesso simultâneo aos métodos da camada [service]. É, mais uma vez, idêntico ao método de teste [test4] da camada [dao]. No entanto, existem alguns detalhes que diferem:
- abordamos a camada [service] em vez da camada [dao] (linha 55)
- passamos uma referência à camada [service] para os threads em vez de para a camada [dao] (linha 61)
O tipo [ThreadServiceMajEnfants] é também quase idêntico ao tipo [ThreadDaoMajEnfants], com a exceção de que funciona com a camada [service] em vez de com a camada [dao]:
- Linha 12: O thread funciona com a camada [service]
Estamos a executar os testes com a configuração que causou problemas na camada [dao]:
- descomentamos a instrução wait no método [saveOne] de [DaoImpl] (linha 83, secção 14.4).
- O método [test4] cria 100 threads (linha 65, secção 14.7).
Os resultados obtidos são os seguintes:
![]() |
Foi a sincronização dos métodos na camada [service] que permitiu o sucesso do teste [test4].
14.8. A camada [web]
Vamos rever a arquitetura de três camadas da nossa aplicação:
![]() |
A camada [web] fornecerá ecrãs ao utilizador para que este possa gerir o grupo de pessoas:
- lista de pessoas no grupo
- adicionar uma pessoa ao grupo
- editar uma pessoa no grupo
- remover uma pessoa do grupo
Para tal, irá recorrer à camada [service], que, por sua vez, irá invocar a camada [DAO]. Já apresentámos os ecrãs geridos pela camada [web] (secção 14.1). Para descrever a camada web, iremos apresentar o seguinte, por ordem:
- a sua configuração
- as suas vistas
- o seu controlador
- alguns testes
14.8.1. Configuração da aplicação web
O projeto Eclipse para a aplicação é o seguinte:

- No pacote [istia.st.mvc.personnes.web], encontrará o controlador [Application].
- As páginas JSP/JSTL encontram-se em [WEB-INF/views].
- A pasta [lib] contém as bibliotecas de terceiros necessárias para a aplicação. Estas estão visíveis na pasta [Web App Libraries].
[web.xml]
O ficheiro [web.xml] é o ficheiro utilizado pelo servidor web para carregar a aplicação. O 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>
<!-- Mapping ServletPersonne-->
<servlet-mapping>
<servlet-name>personnes</servlet-name>
<url-pattern>/do/*</url-pattern>
</servlet-mapping>
<!-- welcome files -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!-- Unexpected error page -->
<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 tratadas pelo servlet [people]
- 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, urlErrors] que identificam as URLs das páginas JSP para as vistas [list, edit, errors].
- linhas 32–34: A aplicação tem uma página de entrada predefinida [index.jsp] localizada na raiz da pasta da aplicação web.
- linhas 36–39: A aplicação tem uma página de erro predefinida que é apresentada quando o servidor web encontra uma exceção não tratada pela aplicação.
- Linha 37: A tag <exception-type> especifica o tipo de exceção tratada pela diretiva <error-page>; aqui, é o tipo [java.lang.Exception] e os seus subtipos, ou seja, todas as exceções.
- Linha 38: A tag <location> especifica a página JSP a ser exibida quando ocorre uma exceção do tipo definido por <exception-type>. A exceção que ocorreu está disponível nesta página num objeto chamado exception, se a página tiver 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 propagada até ao servidor web, o servidor envia ao cliente uma página de exceção proprietária, que geralmente não é muito intuitiva. Daí a importância da tag <error-page> no ficheiro [web.xml].
[index.jsp]
Esta página é apresentada se um utilizador solicitar diretamente o contexto da aplicação sem especificar um URL, ou seja, aqui [/personnes-01]. O 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 o URL [/do/list]. Este URL apresenta a lista de pessoas do grupo.
14.8.2. As páginas JSP/JSTL da aplicação
É utilizada para apresentar a lista de pessoas:

O 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>
- Esta vista recebe um elemento no seu modelo:
- o elemento [people] associado a uma [ArrayList] de objetos [Person]
- linhas 22–34: percorremos a lista ${people} para apresentar uma tabela HTML contendo as pessoas do grupo.
- linha 31: o URL apontado pelo link [Edit] é definido utilizando o campo [id] da pessoa atual, para que o controlador associado ao URL [/do/edit] saiba qual a pessoa a editar.
- linha 32: o mesmo é feito para o link [Delete].
- linha 28: Para apresentar a data de nascimento da pessoa no formato DD/MM/AAAA, utilizamos a tag <dt> da biblioteca de tags [DateTime] do projeto Apache [Jakarta Taglibs]:

O ficheiro de descrição desta biblioteca de tags é definido na linha 3.
- Linha 37: O link [Adicionar] para adicionar uma nova pessoa aponta para a URL [/do/edit], tal como o link [Editar] na linha 31. O valor -1 para o parâmetro [id] indica que se trata de uma adição e não de uma edição.
É utilizada para apresentar o formulário para adicionar uma nova pessoa ou modificar uma já existente:
![]() |
O código para a vista [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>
Esta vista apresenta um formulário para adicionar uma nova pessoa ou atualizar uma já existente. A partir de agora, para simplificar o texto, utilizaremos o termo único [atualizar]. O botão [Submit] (linha 73) aciona um pedido POST para o URL [/do/validate] (linha 16). Se o POST falhar, a vista [edit.jsp] é exibida novamente com o(s) erro(s) que ocorreu(ram); caso contrário, a vista [list.jsp] é exibida.
- A vista [edit.jsp], que é exibida tanto numa solicitação GET como numa solicitação POST com falha, recebe os seguintes elementos no seu modelo:
atributo | GET | POST |
ID da pessoa que está a ser atualizada | o mesmo | |
a sua versão | igual | |
nome | Nome introduzido | |
o apelido dele/dela | Apelido introduzido | |
data de nascimento | data de nascimento introduzida | |
estado civil | Estado civil introduzido | |
número de filhos | Número de filhos introduzido | |
vazio | Uma mensagem de erro indicando que a adição ou modificação falhou durante o POST acionado pelo botão [Submeter]. Vazio se não houver erro. | |
vazio | indica um nome próprio incorreto – vazio caso contrário | |
vazio | indica um nome 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 envio do formulário falhar, [errorEdit!=''] será devolvido e será exibida uma mensagem de erro.
- 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: introdução do nome próprio da pessoa:
- Quando o formulário é exibido inicialmente (GET), ${firstName} exibe o valor atual do campo [firstName] do objeto [Person] atualizado, e ${firstNameError} está vazio.
- em caso de erro após o POST, o valor introduzido ${firstName} é exibido novamente, juntamente com qualquer mensagem de erro ${firstNameError}
- linhas 33-39: introdução do apelido da pessoa
- linhas 40–46: Introdução da data de nascimento da pessoa
- Linhas 47–61: introdução do estado civil da pessoa utilizando um botão de opção. Utilizamos o valor do campo [married] do objeto [Person] para determinar qual dos dois botões de opção deve ser selecionado.
- linhas 62-68: introdução do número de filhos da pessoa
- linha 71: um campo HTML oculto chamado [id] com um valor igual ao campo [id] da pessoa que está a ser atualizada, -1 para uma adição ou outro valor para uma modificação.
- linha 72: um campo HTML oculto chamado [version] com um valor igual ao campo [id] da pessoa que está a ser atualizada.
- Linha 73: O botão [Submit] do formulário
- linha 74: um link para voltar à lista de pessoas. Está rotulado como [Cancelar] porque permite ao utilizador sair do formulário sem o enviar.
É utilizada para apresentar uma página indicando que ocorreu uma exceção não tratada pela aplicação e que foi propagada para o servidor web.
Por exemplo, vamos eliminar uma pessoa que não existe no grupo:
![]() |
O código para a vista [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>
- Esta vista recebe uma chave no seu modelo, o elemento [exception], que corresponde à exceção que foi interceptada pelo servidor web. Para que este elemento seja incluído no modelo da página JSP pelo servidor web, a página deve ter definido a tag na linha 3.
- Linha 6: Definimos o código de estado HTTP da resposta como 200. Este é o primeiro cabeçalho HTTP da resposta. O código de estado 200 indica ao cliente que o seu pedido foi bem-sucedido. Normalmente, um documento HTML foi incluído na resposta do servidor. É o que acontece aqui. Se o código de estado HTTP da resposta não for definido como 200, terá o valor 500, o que significa que ocorreu um erro. De facto, quando o servidor web intercepta uma exceção não tratada, considera esta situação anormal e sinaliza-a com um código 500. A resposta a um código HTTP 500 varia consoante o navegador: o Firefox apresenta o documento HTML que pode acompanhar esta resposta, enquanto o IE ignora este documento e apresenta a sua própria página. É por isso que substituímos o código 500 pelo código 200.
- Linha 16: O texto da exceção é exibido
- Linha 18: É oferecido ao utilizador um link para regressar à lista de pessoas
A visualização [ -errors .jsp]
É utilizado para apresentar uma página que informa erros de inicialização da aplicação, ou seja, erros detetados durante a execução do método [init] do servlet controlador. Isto pode ser, por exemplo, a ausência de um parâmetro no ficheiro [web.xml], como se pode ver no exemplo abaixo:

O código da página [errors.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 um elemento [errors] no seu modelo, que é uma [ArrayList] de objetos [String]; estes são mensagens de erro. São apresentadas pelo ciclo nas linhas 13–15.
14.8.3. O controlador da aplicação
O controlador [Application] está definido no pacote [istia.st.mvc.personnes.web]:
![]()
Estrutura e inicialização do controlador
A estrutura básica do controlador [Application] é a seguinte:
- linhas 20–36: recuperam os parâmetros especificados no ficheiro [web.xml].
- Linhas 39–41: O parâmetro [urlErrors] deve estar presente, pois especifica a URL da vista [errors], que exibe quaisquer erros de inicialização. Se não existir, a aplicação é encerrada através do lançamento de uma [ServletException] (linha 40). Esta exceção será propagada para o servidor web e tratada pela tag <error-page> no ficheiro [web.xml]. A vista [exception.jsp] é, portanto, exibida:

O link [Voltar à lista] acima está inativo. Clicar nele retorna a mesma resposta, desde que a aplicação não tenha sido modificada e recarregada. É ú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 esta instância (cria uma lista inicial de três pessoas)
- linha 46: cria uma instância de [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, os seus métodos têm uma referência [service] à camada [service] (linha 15) que utilizarão para executar as ações solicitadas pelo utilizador. Estas serão interceptadas pelo método [doGet], que as fará processar por um método específico do controlador:
Url | Método HTTP | Método do controlador |
GET | doListPeople | |
GET | doEditPerson | |
POST | doValidatePerson | |
GET | executarDeletePessoa |
O método [doGet]
O objetivo deste método é encaminhar o processamento das ações solicitadas pelo utilizador para o método correto. O seu código é o seguinte:
- linhas 7–13: Verificamos se a lista de erros de inicialização está vazia. Se não estiver, exibimos a vista [errors(errors)], que irá relatar o(s) erro(s).
- linha 15: Recuperamos o método [get] ou [post] que o cliente utilizou para efetuar o pedido.
- linha 17: recuperamos o valor do parâmetro [action] da solicitação.
- Linhas 23–27: Processamos a solicitação [GET /do/list], que solicita a lista de pessoas.
- Linhas 28–32: Processamos a solicitação [GET /do/delete], que solicita a exclusão de uma pessoa.
- Linhas 33–37: Processamos a solicitação [GET /do/edit], que solicita o formulário para atualizar uma pessoa.
- linhas 38–42: processar a solicitação [POST /do/validate], que solicita a validação da pessoa atualizada.
- linha 44: se a ação solicitada não for uma das cinco anteriores, tratamo-la como se fosse [GET /do/list].
O método [doListPersonnes]
Este método trata a solicitação [GET /do/list], que solicita a lista de pessoas:

O seu código é o seguinte:
- Linha 5: Solicitamos a lista de pessoas do grupo à camada [service] e guardamo-la no modelo sob a chave "people".
- Linha 7: A vista [list.jsp] descrita na secção 14.8.2 é apresentada.
O método [doDeletePerson]
Este método trata do pedido [GET /do/delete?id=XX], que solicita a eliminação da pessoa com id=XX. A URL [/do/delete?id=XX] é a dos links [Delete] na vista [list.jsp]:

cujo código é o seguinte:
A linha 12 mostra o URL [/do/delete?id=XX] para o link [Eliminar]. O método [doDeletePerson], que trata deste URL, deve eliminar a pessoa com id=XX e, em seguida, apresentar a lista atualizada de pessoas no grupo. O seu código é o seguinte:
- Linha 5: A URL que está a ser processada tem o formato [/do/delete?id=XX]. Recuperamos o valor [XX] do parâmetro [id].
- linha 7: solicitamos à camada [service] que elimine a pessoa com o ID obtido. Não realizamos qualquer validação. Se a pessoa que estamos a tentar eliminar não existir, a camada [dao] lança uma exceção que é propagada até à camada [service]. Também não a tratamos aqui no controlador. Por conseguinte, propagar-se-á até ao servidor web, que, por configuração, exibirá a página [exception.jsp], descrita na secção 14.8.2:

- Linha 9: Se a eliminação foi bem-sucedida (sem exceção), o cliente é redirecionado para o URL relativo [list]. Uma vez que o URL acabado de processar foi [/do/delete], o URL de redirecionamento será [/do/list]. O navegador irá, portanto, efetuar um pedido [GET /do/list], que irá apresentar a lista de pessoas.
O método [doEditPerson]
Este método trata da solicitação [GET /do/edit?id=XX], que solicita o formulário para atualizar a pessoa com id=XX. A URL [/do/edit?id=XX] é a utilizada para os links [Editar] e [Adicionar] na visualização [list.jsp]:

cujo código é o seguinte:
Na linha 11, vemos o URL [/do/edit?id=XX] para o link [Editar] e, na linha 17, o URL [/do/edit?id=-1] para o link [Adicionar]. O método [doEditPersonne] deve apresentar o formulário de edição para a pessoa com id=XX ou, se for uma adição, apresentar um formulário vazio.
![]() | ![]() |
O código do método [doEditPerson] é o seguinte:
- A solicitação GET tem como destino uma URL no formato [/do/edit?id=XX]. Na linha 5, recuperamos o valor de [id]. Existem então dois casos:
- Se id não for igual a -1, trata-se de uma atualização, e precisamos de apresentar um formulário pré-preenchido com as informações da pessoa a ser editada. Na linha 10, esta pessoa é solicitada à camada [service].
- Se id for igual a -1, trata-se de uma adição e deve ser apresentado um formulário vazio. Para tal, é criada uma pessoa vazia nas linhas 13–14.
- O objeto [Person] é colocado no modelo de página [edit.jsp] descrito na Secção 14.8.2. Este modelo inclui os seguintes elementos: [errorEdit, id, version, firstName, errorFirstName, lastName, errorLastName, dateOfBirth, errorDateOfBirth, spouse, numberOfChildren, errorNumberOfChildren]. Estes elementos são inicializados nas linhas 17–30, com exceção daqueles cujo valor é uma cadeia vazia [firstNameError, lastNameError, birthDateError, childrenCountError]. Sabemos que, se estiverem em falta no modelo, a biblioteca JSTL apresentará uma cadeia de caracteres vazia como valor. Embora o elemento [errorEdit] também tenha uma cadeia de caracteres vazia como valor, é, no entanto, inicializado porque é realizada uma verificação do seu valor na página [edit.jsp].
- Assim que o modelo estiver pronto, o controlo é transferido para a página [edit.jsp], linhas 32–33, que irá gerar a vista [edit].
O método [doValidatePersonne]
Este método trata do pedido [POST /do/validate], que valida o formulário de atualização. Este POST é acionado pelo botão [Validate]:

Vamos rever os elementos de entrada do formulário HTML na vista acima:
O pedido POST contém os parâmetros [firstName, lastName, dateOfBirth, spouse, numberOfChildren, id, version] e é enviado para o URL [/do/validate] (linha 1). É processado pelo seguinte método [doValidatePerson]:
- linhas 8-14: o parâmetro [firstName] da solicitação POST é recuperado e a sua validade é verificada. Se estiver incorreto, o elemento [firstNameError] é inicializado com uma mensagem de erro e colocado nos atributos da solicitação.
- linhas 16–22: o mesmo processo é seguido para o parâmetro [lastName]
- linhas 24–32: o mesmo processo é aplicado ao parâmetro [dateOfBirth]
- Linha 34: O parâmetro [spouse] é recuperado. Não verificamos a sua validade porque, em princípio, provém do valor de um botão de opção. Dito isto, nada impede que um programa faça uma solicitação [POST /people-01/do/validate] acompanhada de um parâmetro [spouse] fictício. Devemos, portanto, testar a validade deste parâmetro. Aqui, contamos com o nosso tratamento de exceções, que faz com que a página [exception.jsp] seja exibida se o controlador não tratar a exceção por si próprio. Assim, se a conversão do parâmetro [marie] para um booleano falhar na linha 34, será lançada uma exceção, resultando no envio da página [exception.jsp] para o cliente. Este comportamento funciona para nós.
- Linhas 34–54: Recuperamos o parâmetro [nbEnfants] e verificamos o seu valor.
- Linha 56: Recuperamos o parâmetro [id] sem verificar o seu valor
- Linha 58: Fazemos o mesmo para o parâmetro [version]
- Linhas 60–65: Se o formulário for inválido, ele é exibido novamente com as mensagens de erro geradas anteriormente
- Linhas 67–69: Se for válido, criamos um novo objeto [Person] utilizando os campos do formulário
- Linhas 70–78: a pessoa é guardada. A operação de gravação pode falhar. Num ambiente multiutilizador, a pessoa a ser modificada pode ter sido eliminada ou já ter sido modificada por outra pessoa. Neste caso, a camada [dao] irá lançar uma exceção, que tratamos aqui.
- Linha 80: Se não ocorreu nenhuma exceção, o cliente é redirecionado para o URL [/do/list] para exibir o novo estado do grupo.
- Linha 75: Se ocorreu uma exceção durante o salvamento, solicitamos que o formulário inicial seja exibido novamente, passando-lhe 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] utilizando os valores introduzidos (request.getParameter(" ... ")). Recorde-se que as mensagens de erro já foram adicionadas ao modelo pelo método [doValidatePersonne]. A página [edit.jsp] é apresentada nas linhas 99–100.
14.9. Teste da aplicação Web
Foram apresentados vários testes na Secção 14.1. Convidamos o leitor a executá-los novamente. Aqui mostramos capturas de ecrã adicionais que ilustram casos de conflitos de acesso a dados num ambiente multiutilizador:
O [Firefox] será o navegador do utilizador U1. O utilizador U1 solicita o URL [http://localhost:8080/personnes-01]:

O [IE] será o navegador do utilizador U2. O utilizador U2 solicita a mesma URL:

O utilizador U1 começa a editar o registo de [Lemarchand]:

O utilizador U2 faz o mesmo:

O utilizador U1 faz alterações e guarda:
![]() |
O utilizador U2 faz o mesmo:
![]() |
O utilizador U2 volta à lista de pessoas que utilizam o link [Cancelar] no formulário:

Encontra a pessoa [Lemarchand] tal como foi modificada por U1. Agora, U2 elimina [Lemarchand]:
![]() |
O U1 ainda tem a sua própria lista e quer editar [Lemarchand] novamente:
![]() |
O U1 usa o link [Voltar à lista] para ver o que se passa:

Ele descobre que [Lemarchand] já não consta, de facto, da lista...
14.10. Conclusão
Implementámos a arquitetura MVC no âmbito de uma arquitetura de três camadas [web, lógica de negócio, DAO] utilizando um exemplo básico de gestão de uma lista de pessoas. Isto permitiu-nos aplicar os conceitos apresentados nas secções anteriores. Na versão que analisámos, a lista de pessoas era mantida na memória. Em breve exploraremos versões em que esta lista é armazenada numa tabela de base de dados.
Mas, primeiro, iremos apresentar uma ferramenta chamada Spring IoC, que facilita a integração das diferentes camadas de uma aplicação de n camadas.

















