14. [TD]: Esposizione web del livello [business]
Parole chiave: architettura multilivello, Spring, iniezione di dipendenze, servizio web / JSON, client / server.
Torniamo all'architettura attuale dell'applicazione TD:
![]() |
Evolveremo questa architettura nella seguente:
![]() |
al fine di esporre l'interfaccia [IMetier] del livello business sul web. A tal fine, seguiremo la metodologia descritta nella sezione 13.5.
14.1. Supporto
![]() |
I progetti relativi a questo capitolo si trovano nella cartella [support / chap-14].
14.2. Il progetto Eclipse per il livello [business]
![]() |
![]() |
14.2.1. Configurazione Maven
Il progetto del livello [business] è un progetto Maven configurato dal seguente file [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>
<!-- dependencies -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- layer [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>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<!-- use UTF-8 for everything -->
<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>
- righe 18–22: la dipendenza dal livello [DAO] creato nel paragrafo 12;
- righe 23–34: le dipendenze necessarie per il test;
14.2.2. Configurazione Spring
![]() |
Il progetto del livello [business] è un progetto Spring configurato dal seguente file [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 {
}
- Qui non utilizziamo l'annotazione [@Configuration], che renderebbe la classe una classe di configurazione Spring. La presenza delle annotazioni [@Import] e [@ComponentScan] la rende automaticamente una classe di configurazione;
- Riga 8: Importiamo il file di configurazione dal livello [DAO]. Abbiamo quindi accesso a tutti i bean definiti da questo file;
- Riga 9: Altri bean Spring si trovano nella cartella [elections.metier.service];
14.2.3. Implementazione del livello [business]
![]() |
L'implementazione del livello [business] è quella definita nella Sezione 8.5.
14.2.4. Test del livello [business]
![]() |
La classe di test è quella descritta nella Sezione 8.6.
Compito: implementare il progetto del livello [business] e superare il relativo test unitario. Generare l'archivio del livello nel repository Maven locale (Esegui come / Maven / Installa).
14.3. Il progetto Eclipse per il livello [web]
![]() |
Il livello web è un livello Spring MVC:
![]() |
Il progetto Eclipse ha la seguente struttura:
![]() | ![]() |
- [Boot.java] è la classe che avvia il servizio web;
- [WebConfig.java] è la classe di configurazione del servizio web;
- [Response.java] è la risposta generata dai vari URL del servizio web;
- [ElectionsController] è la classe di implementazione del servizio web;
14.4. Configurazione Maven
Il progetto è un progetto Maven configurato dal seguente file [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>
<!-- business layer -->
<dependency>
<groupId>istia.st.elections</groupId>
<artifactId>elections-metier-dao-spring-data</artifactId>
<version>0.1.0</version>
</dependency>
<!-- layer 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>
- righe 19–23: la dipendenza dall'archivio del livello [business]. Si tratta di quello che abbiamo creato al paragrafo 14;
- righe 25–28: la dipendenza per un'applicazione Spring MVC;
14.5. Configurazione Spring
![]() |
La classe [WebConfig] configura il servizio 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 {
// -------------------------------- layer configuration [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);
}
// mapper jSON
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- Il significato di questa configurazione è stato spiegato nella Sezione 13.5.3.1. Ci limiteremo a spiegare le nuove funzionalità:
- riga 22: importiamo il file di configurazione dal livello [business] per utilizzare tutti i suoi bean;
- riga 23: specifichiamo che altri bean si trovano nella cartella [elections.webjson.server.service];
14.6. La classe di avvio del servizio web
![]() |
La classe [Boot] avvia il servizio web come segue:
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);
}
}
- Riga 10: Il metodo statico [SpringApplication.run] utilizzerà il file di configurazione [WebConfig]. Grazie all'annotazione [@EnableAutoConfiguration], Spring Boot avvierà il server Tomcat e distribuirà il servizio web su di esso;
14.7. La risposta dagli URL del servizio web
![]() |
Tutti gli URL del servizio web / JSON inviano lo stesso tipo di risposta:
package elections.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// any error messages
private List<String> messages;
// the body of the reply
private T body;
// manufacturers
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getters and setters
...
}
Questo concetto è stato presentato e approfondito nella sezione 13.5.5.3.
14.8. Implementazione del servizio web / JSON
![]() |
Il servizio web /JSON è implementato dalla seguente 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 {
// spring dependencies
@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 {
// answer
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);
}
// answer
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");
}
// private methods -----------------------------
// list of RuntimeException error messages
private List<String> getErreursForException(Exception e) {
// retrieve the list of exception error messages
Throwable cause = e;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
}
Compito: seguendo i passaggi descritti nella Sezione 13.5.5, completa il codice per la classe [ElectionsController].
Note:
- Qui non sono presenti filtri JSON poiché le tabelle [CONF] e [LISTES] non sono collegate da una relazione di chiave esterna, il che semplifica notevolmente il codice del servizio web;
- Non dimenticare le varie annotazioni Spring necessarie;
- Gli URL saranno denominati in base ai metodi associati;
- Il metodo [setListeElectorales] viene chiamato con un'operazione [POST]. Il valore inviato è l'array delle liste in lizza (di tipo ListeElectorale[]) con i relativi attributi [seats, votes, eliminated], che devono essere salvati nel database. Questo metodo restituisce un [Response<Void>] con un campo [status=0] se non si sono verificati errori, altrimenti restituisce un altro valore;
- Il metodo [calculateSeats] viene chiamato con un'operazione [POST]. Il valore inviato è l'array delle liste in competizione (di tipo ElectoralList[]) con i loro attributi [name, votes]. Questo metodo restituisce un [Response<ElectoralList[]>] con, come corpo, le liste elettorali con i campi [seats, eliminated] inizializzati;
14.9. Test
Dopo aver avviato il servizio web, eseguire i seguenti test per assicurarsi che il servizio web funzioni correttamente utilizzando l'utilità [Advanced Rest Client]:
![]() |
La risposta JSON alla richiesta precedente è la seguente [1]:
![]() |
1 ![]() | 2 ![]() |
In [2], copia la risposta negli appunti, quindi incollala in un editor di testo qualsiasi [3]:
![]() |
Isolare il valore del campo [body] e modificare, ad esempio, i voti per le liste. Di seguito [4], impostiamo i voti per tutte le liste a 100:
![]() |
Verifica che la tua stringa JSON inizi con [ e finisca con ]. Questi caratteri servono a delimitare un array JSON. In [5], incolla la stringa JSON sopra riportata. Questo sarà il valore inviato al prossimo URL. Per farlo, seleziona il metodo HTTP [POST] [7].
![]() |
- In [6], inviare una richiesta all'URL [setListesElectorales]. Questa richiesta viene effettuata tramite un metodo POST. Il valore inviato è l'array JSON delle liste in competizione, i cui risultati devono essere salvati nel database;
Si ottiene il seguente risultato:
![]() |
Il campo [status=0] indica che non ci sono stati errori. Per verificarlo, richiedi nuovamente gli elenchi in competizione e controlla che le modifiche apportate agli elenchi siano state applicate:
![]() |
Effettuiamo un'altra richiesta [POST] per calcolare i seggi ottenuti dalle liste:
![]() |
- in [1]: l'URL per il calcolo dei seggi;
- in [2]: inviamo una richiesta [POST];
- in [3]: le liste in competizione. Impostiamo il campo [votes] con i valori del tutorial, tutti i [seats] sono impostati a 0 e tutti i campi [eliminate] sono impostati a false;
Il risultato ottenuto è il seguente:
![]() |

























