14. [TD]: Exposição na Web da camada [metier]
Palavras-chave: arquitetura multicamadas, Spring, injeção de dependências, serviço web / jSON, cliente / servidor.
Voltemos à arquitetura atual da aplicação do TD:
![]() |
Vamos evoluir esta arquitetura para a seguinte:
![]() |
para expor na Web a interface [IMetier] da camada de negócio. Para tal, vamos seguir a metodologia descrita no parágrafo 13.5.
14.1. Support
![]() |
Os projetos deste capítulo encontram-se na pasta [support / chap-14].
14.2. O projeto Eclipse da camada [métier]
![]() |
![]() |
14.2.1. Configuração do Maven
O projeto da camada [métier] é um projeto Maven configurado pelo seguinte ficheiro [pom.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
<!-- dependências -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- camada [DAO] -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Teste do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<!-- utiliza UTF-8 para tudo -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- linhas 18-22: a dependência da camada [DAO] criada no parágrafo 12;
- linhas 23-34: as dependências necessárias para os testes;
14.2.2. Configuração do Spring
![]() |
O projeto da camada [métier] é um projeto Spring configurado pelo seguinte ficheiro [MetierConfig]:
package elections.metier.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import elections.dao.config.DaoConfig;
@Import({ DaoConfig.class })
@ComponentScan({ "elections.metier.service" })
public class MetierConfig {
}
- Aqui não utilizamos a notação [@Configuration], que transforma a classe numa classe de configuração Spring. A presença das anotações [@Import, @ComponentScan] torna-a automaticamente numa classe de configuração;
- linha 8: importa-se o ficheiro de configuração da camada [DAO]. Ficam assim disponíveis todos os beans definidos nesse ficheiro;
- linha 9: outros beans Spring devem ser procurados na pasta [elections.metier.service];
14.2.3. Implementação da camada [métier]
![]() |
A implementação da camada [métier] é a que foi definida no parágrafo 8.5.
14.2.4. Teste da camada [métier]
![]() |
A classe de teste é a descrita no parágrafo 8.6.
Tarefa a realizar: implemente o projeto da camada [métier] e execute o seu teste unitário. Gere o arquivo da camada no repositório Maven local (run as/ Maven / install).
14.3. O projeto Eclipse da camada [web]
![]() |
A camada web é uma camada Spring MVC:
![]() |
O projeto Eclipse tem a seguinte estrutura:
![]() | ![]() |
- [Boot.java] é a classe que inicia o serviço web;
- [WebConfig.java] é a classe de configuração do serviço web;
- [Response.java] é a resposta gerada pelas diferentes instâncias de URL do serviço web;
- [ElectionsController] é a classe de implementação do serviço web;
14.4. Configuração do Maven
O projeto é um projeto Maven configurado pelo seguinte ficheiro [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-webjson-metier-dao-spring-data</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elections-webjson-metier-dao-spring-data</name>
<description>couche métier exposée comme un service web / jSON</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- camada de negócio -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
</dependency>
<!-- camada MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- linhas 19-23: a dependência do arquivo da camada [métier]. É a que criámos no parágrafo 14;
- linhas 25-28: a dependência para ter uma aplicação Spring MVC;
14.5. Configuração do Spring
![]() |
A classe [WebConfig] configura o serviço web:
package elections.webjson.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.metier.config.MetierConfig;
@EnableWebMvc
@Import({ MetierConfig.class })
@ComponentScan({ "elections.webjson.service" })
public class WebConfig {
// -------------------------------- configuração da camada [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// mapeador jSON
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- O significado desta configuração foi explicado no parágrafo 13.5.3.1. Apenas explicamos as novidades:
- linha 22: importa-se o ficheiro de configuração da camada [métier] para poder utilizar todos os seus beans;
- linha 23: indica-se que outros beans serão encontrados na pasta [elections.webjson.server.service];
14.6. A classe de inicialização do serviço web
![]() |
A classe [Boot] inicia o serviço web da seguinte forma:
package elections.webjson.boot;
import org.springframework.boot.SpringApplication;
import elections.webjson.config.WebConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(WebConfig.class, args);
}
}
- linha 10: o método estático [SpringApplication.run] irá utilizar o ficheiro de configuração [WebConfig]. Devido à anotação [@EnableAutoConfiguration], o Spring Boot irá iniciar o servidor Tomcat e implementar o serviço web nesse servidor;
14.7. A resposta do serviço web URL
![]() |
Todas as URL do serviço web / jSON enviam o mesmo tipo de resposta:
package elections.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- propriedades
// estado da operação
private int status;
// eventuais mensagens de erro
private List<String> messages;
// o corpo da resposta
private T body;
// construtores
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters e setters
...
}
Esta classe foi apresentada e analisada no parágrafo 13.5.5.3.
14.8. A implementação do serviço web / jSON
![]() |
O serviço web / jSON é implementado pela seguinte classe [ElectionsController]:
package elections.webjson.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.metier.service.IElectionsMetier;
@Controller
public class ElectionsController {
// dependências do Spring
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private IElectionsMetier metier;
@RequestMapping(value = "/getElectionsConfig", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getElectionsConfig() throws JsonProcessingException {
// resposta
Response<ElectionsConfig> response;
try {
response = new Response<>(0, null,
new ElectionsConfig(metier.getNbSiegesAPourvoir(), metier.getSeuilElectoral()));
} catch (ElectionsException e1) {
response = new Response<>(e1.getCode(), e1.getErreurs(), null);
} catch (RuntimeException e2) {
response = new Response<>(1000, getErreursForException(e2), null);
}
// resposta
return jsonMapper.writeValueAsString(response);
}
@RequestMapping(value = "/getListesElectorales", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getListesElectorales() throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/setListesElectorales", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String setListesElectorales(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
@RequestMapping(value = "/calculerSieges", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String calculerSieges(HttpServletRequest request) throws JsonProcessingException {
throw new UnsupportedOperationException("Not supported yet");
}
// métodos privados -----------------------------
// lista de mensagens de erro de uma RuntimeException
private List<String> getErreursForException(Exception e) {
// recupera-se a lista de mensagens de erro da exceção
Throwable cause = e;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// recupera-se a mensagem apenas se esta for !=null e não estiver vazia
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa seguinte
cause = cause.getCause();
}
return erreurs;
}
}
Tarefa a realizar: seguindo o que foi feito no parágrafo 13.5.5, complete o código da classe [ElectionsController].
Notas:
- aqui não existem filtros jSON, uma vez que as tabelas [CONF] e [LISTES] não estão ligadas entre si por uma relação de chave estrangeira, o que alivia consideravelmente o código do serviço web;
- não se esqueça das várias anotações Spring necessárias;
- atribuiremos aos URL o nome dos métodos associados;
- o método [setListeElectorales] é chamado com uma operação [POST]. O valor enviado é o array das listas em competição (do tipo ListeElectorale[]) com os seus atributos [sieges, voix, elimine], que devem ser registados na base de dados. Este método devolve um tipo [Response<Void>] com um campo [status=0] se não tiver ocorrido nenhum erro; caso contrário, devolve outro valor;
- o método [calculerSieges] é chamado com uma operação [POST]. O valor enviado é a matriz das listas em concorrência (do tipo ListeElectorale[]) com os seus atributos [nom, voix]. Este método devolve um tipo [Response<ListeElectorale[]>] cujo corpo é constituído pelas listas eleitorais com os seus campos [sieges, elimine] inicializados;
14.9. Tests
Após ter lançado o serviço web, deverá realizar os seguintes testes para se certificar de que o serviço web funciona corretamente com o utilitário [Advanced Rest Client]:
![]() |
A resposta jSON à solicitação anterior é a seguinte: [1]:
![]() |
1 ![]() | 2 ![]() |
Em [2], copie a resposta para a área de transferência e, em seguida, cole-a num editor de texto qualquer [3]:
![]() |
Isolar o valor do campo [body] e alterar, por exemplo, os votos das listas. Abaixo, em [4], aumentamos para 100 os votos de todas as listas:
![]() |
Verifique se a sua cadeia jSON começa por [ et se termine par ]. Estes caracteres servem para delimitar uma tabela jSON. Em [5], cole a sequência jSON acima. Este será o valor a ser lançado para o próximo URL. Para tal, é necessário selecionar o método HTTP [POST] [7].
![]() |
- no [6], solicite o URL [setListesElectorales]. Este URL é solicitado com um POST. O valor lançado é a tabela jSON das listas em competição, cujos resultados devem ser registados na base de dados;
Obtém-se o seguinte resultado:
![]() |
O campo [status=0] indica que não houve erros. Para verificar, solicite novamente as listas em competição e verifique se as alterações que efetuou nas listas foram tidas em conta:
![]() |
Executa-se novamente um [POST] para calcular os lugares obtidos pelas listas:
![]() |
- em [1]: o URL do cálculo dos lugares;
- em [2]: executa-se um [POST];
- em [3]: as listas concorrentes. Atribuímos ao campo [voix] os valores do TD, todos os [sieges] ficam a 0 e todos os campos [elimine] ficam a «false»;
O resultado obtido é o seguinte:
![]() |

























