17. Rendere accessibile un database sul Web
17.1. Architettura del servizio Web/JSON
Implementeremo la seguente architettura:
![]() |
- in [1], i livelli [DAO, [JPA], JDBC] sono implementati utilizzando una delle 24 configurazioni presentate nei capitoli precedenti, in particolare nel paragrafo 15;
- il livello [DAO] [3] del client remoto implementa la stessa interfaccia del livello [DAO] [1], il che ci permette di utilizzare lo stesso livello di test dei capitoli precedenti. È come se i livelli [2-3] fossero trasparenti al livello [4];
Ci affideremo ai seguenti progetti:
- il progetto [sgbd-config-jdbc], che configura il livello JDBC di uno dei sei DBMS;
- il progetto [sgbd-config-jpa-*], che configura il livello JPA del DBMS selezionato per una delle tre implementazioni JPA esaminate (Hibernate, EclipseLink, OpenJpa);
- il progetto generico [spring-jdbc-04], che implementa il livello [DAO] [1];
- il progetto generico [spring-jpa-generic] che implementa il livello [DAO] [2];
- il progetto generico [spring-webjson-server-jdbc-generic], che implementa un servizio web basato sul progetto [spring-jdbc-04];
- il progetto generico [spring-webjson-server-jpa-generic], che implementa un servizio web basato sul progetto [spring-jpa-generic];
- il client generico [spring-webjson-client-generic], che sarà l'unico client per tutte le 24 configurazioni di servizi web;
17.2. Configurazione dell'ambiente di sviluppo
Lavoreremo con i seguenti componenti:
- database MySQL 5.6.25;
- implementazione JPA di Hibernate;
Importare i seguenti progetti in STS:
![]() |
- I progetti [spring-webjson-*] si trovano nella cartella [<examples>\spring-database-generic\spring-webjson];
- Premere [Alt-F5] e quindi rigenerare tutti i progetti sopra indicati;
Per verificare che l'ambiente di sviluppo sia installato correttamente, procedere come segue:
- Avviare il servizio web utilizzando la configurazione di runtime [spring-webjson-server-jpa-generic-hibernate], che si basa su un'implementazione JPA/Hibernate;
![]() | ![]() |
quindi:
- avvia il client per questo servizio web utilizzando la configurazione di runtime [spring-webjson-client-generic], che è un test JUnit:
![]() | ![]() |
Il test dovrebbe avere esito positivo:
![]() |
- In [1], arrestare il servizio web, quindi avviare il servizio web con la configurazione di runtime [spring-webjson-server-jdbc-generic], che si basa su un'implementazione JDBC:
![]() | ![]() |
quindi avviare il client per questo servizio web con la configurazione di runtime [spring-webjson-client-generic]:
![]() | ![]() |
Il test dovrebbe avere esito positivo:
![]() |
17.3. Implementazione di servizi web / JSON / JDBC
Ci concentreremo innanzitutto sulla seguente architettura:
![]() |
in cui il livello [DAO] [1] comunica direttamente con il livello JDBC del DBMS.
17.3.1. Il progetto Eclipse per il servizio web
Il progetto Eclipse per il servizio web / JSON / JDBC è il seguente:
![]() |
Si tratta di un progetto Maven il cui file [pom.xml] è il seguente:
<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>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-webjson-server-jdbc-generic</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- web layer -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- layer [DAO] -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- righe 11–15: il progetto Maven principale;
- righe 24–28: la dipendenza dal livello [DAO / JDBC] implementato dal progetto [spring-jdbc-generic-04];
- righe 19–22: la dipendenza dall'artefatto [spring-boot-starter-web]. Questo artefatto include tutte le dipendenze necessarie per creare un servizio web / JSON. Include anche librerie non necessarie. Sarebbe quindi necessaria una configurazione più precisa, ma questa configurazione è utile per iniziare.
Le dipendenze introdotte da questa configurazione sono le seguenti:
![]() | ![]() | 1 ![]() |
- In [1], possiamo vedere che Eclipse ha rilevato la dipendenza dall'archivio del progetto [spring-jdbc-generic-04];
Le dipendenze sopra indicate riguardano sia il livello [DAO] che il livello [web].
17.3.2. Configurazione del livello [web]
Il livello [web] è configurato da due file di configurazione Spring:
![]() |
17.3.2.1. La classe [WebConfig]
Il ruolo principale della classe [WebConfig] è quello di configurare:
- il server Tomcat su cui verrà distribuito il servizio web;
- i filtri JSON per la serializzazione e la deserializzazione degli oggetti [Product] e [Category]:
package spring.webjson.server.config;
import java.util.List;
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.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- 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("", 8081);
}
// -------------------------------- configuration filters [json]
...
}
- riga 25: la classe è una classe di configurazione Spring;
- riga 26: l'annotazione [@EnableWebMvc] indica che il livello web è implementato utilizzando Spring MVC. Ciò attiverà configurazioni implicite che non dovremo impostare;
- righe 30-31: iniezione del contesto Spring dell'applicazione;
- righe 33–37: definizione del bean [dispatcherServlet], che, nelle applicazioni Spring MVC, funge da [FrontController], il cui ruolo è quello di instradare le richieste dei client al controller in grado di gestirle;
- righe 39–42: il servlet del servizio web viene registrato insieme agli URL che gestisce. Qui abbiamo scritto [/*], che significa tutti gli URL;
- Righe 44–47: definizione del bean [embeddedServletContainerFactory], che specifica il server web da utilizzare. In questo caso, sarà il server web Tomcat [http://tomcat.apache.org/]. È possibile utilizzare anche il server Jetty [http://www.eclipse.org/jetty/]. Entrambi sono server incorporati inclusi nelle dipendenze di Maven. Quando [Spring Boot] avvia il progetto, avvia automaticamente il server web specificato nella configurazione e distribuisce il servizio o l'applicazione web su di esso;
I filtri JSON sono configurati come segue:
package spring.webjson.server.config;
import java.util.List;
...
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- layer configuration [web]
...
// -------------------------------- configuration filters [json]
// mapping jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// filters jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
}
- Riga 8: La classe [WebConfig] estende la classe [WebMvcConfigurerAdapter]. Quest'ultima classe configura l'applicazione web con valori predefiniti. Quando si desidera personalizzare questa configurazione, è necessario sovrascrivere determinati metodi di questa classe. In questo caso, vogliamo sovrascrivere il metodo [configureMessageConverters] nelle righe [22–26] (notare l'annotazione @Override), che definisce un elenco di "convertitori". Il servizio web/JSON e il suo client si scambiano righe di testo. Un convertitore è uno strumento in grado di creare un oggetto da una riga di testo ricevuta (deserializzazione) e di creare una riga di testo da un oggetto (serializzazione). In questo caso, le righe di testo saranno stringhe JSON. Faremo quindi riferimento alla serializzazione/deserializzazione JSON;
- riga 23: il metodo [configureMessageConverters] accetta come parametro un elenco di convertitori;
- Righe 24–25: il convertitore JSON [MappingJackson2HttpMessageConverter] delle righe [14–20] viene aggiunto a questo elenco. Ciò consentirà gli scambi JSON tra il client e il server;
- righe [14-20]: definiscono un convertitore JSON implementato dalla classe [MappingJackson2HttpMessageConverter]. Questa classe (riga 10) si trova nelle dipendenze Maven del progetto;
- righe [17-18]: viene creato un mappatore JSON e assegnato al [MappingJackson2HttpMessageConverter];
- righe [29-32]: definiscono il mappatore JSON creato alla riga 17 come bean Spring. Questo lo inserisce nel contesto Spring e lo rende disponibile per essere iniettato in altri bean o utilizzato nel codice dell'applicazione web;
- righe 34-41: definiscono un filtro JSON per il mappatore JSON precedente;
- riga 35: l'annotazione [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] garantisce che il bean qui definito non sia un singleton. Ogni volta che viene richiesto dal contesto, il metodo [jsonMapperCategoryWithoutProducts] verrà rieseguito. Ciò è necessario in questo caso perché stiamo definendo quattro filtri JSON. Tuttavia, solo uno dovrebbe essere attivo in un dato momento. Assegnando al bean l'ambito [ConfigurableBeanFactory.SCOPE_PROTOTYPE], ci assicuriamo che il metodo venga rieseguito e che il filtro precedente venga sostituito da quello nuovo;
- Per comprendere questi filtri, ricordiamo che nel livello [DAO]:
- l'entità [Product] è stata annotata con l'annotazione [jsonFilterProduct];
- l'entità [Category] è stata annotata con [jsonFilterCategory];
Dobbiamo quindi definire filtri con questi nomi.
- righe [34-41]: definiscono un filtro chiamato [jsonMapperShortCategorie] che fornisce la rappresentazione JSON di una categoria senza i suoi prodotti;
- righe [43-51]: definiscono un filtro chiamato [jsonMapperLongCategory] che fornisce la rappresentazione JSON di una categoria insieme ai suoi prodotti;
- righe [53-60]: definiscono un filtro chiamato [jsonMapperShortProduct] che fornisce la rappresentazione JSON di un prodotto senza la sua categoria;
- righe [62-70]: definiscono un filtro chiamato [jsonMapperLongProduct] che fornisce la rappresentazione JSON di un prodotto con la sua categoria;
17.3.2.2. La classe [AppConfig]
La classe [AppConfig] configura l'intera applicazione, ovvero i livelli [web] e [DAO]:
package spring.webjson.server.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {
}
- Riga 7: la classe è una classe di configurazione Spring;
- riga 9: importiamo i bean dal livello [DAO / JDBC] e quelli definiti dalla classe [WebConfig]. Tutti i bean del livello [DAO] saranno quindi disponibili nell'applicazione web/JSON;
- riga 8: specifica i pacchetti in cui si trovano altri bean Spring;
17.3.3. L'[ServerException]
![]() |
Proprio come nei capitoli precedenti, in cui il livello [DAO] generava un'eccezione [DaoException] non gestita, il livello [web] genererà un'eccezione [ServerException] non gestita:
package spring.webjson.server.infrastructure;
import generic.jdbc.infrastructure.UncheckedException;
public class ServerException extends UncheckedException {
private static final long serialVersionUID = 1L;
// manufacturers
public ServerException() {
super();
}
public ServerException(int code, Throwable e, String simpleClassName) {
super(code, e, simpleClassName);
}
}
- riga 5: la classe [ServerException] estende la classe [UncheckedException] definita nel progetto che configura il livello JDBC (riga 3);
17.3.4. I controller
![]() |
![]() |
Qui avremo due controller:
- [CategoryController] gestirà le richieste relative alle categorie;
- [CategorieController] gestirà le richieste relative ai prodotti;
Gli URL esposti dai controller corrispondono in modo biunivoco ai metodi delle interfacce [DaoCategorie] e [DaoProduit] nel livello [DAO]:
![]() | ![]() |
Quindi, come mostrato sopra:
- il metodo web [deleteAllCategories] chiamerà il metodo [deleteAllEntities] della classe [DaoCategorie];
- il metodo web [getShortCategoriesById] richiamerà il metodo [getShortEntitiesById] della classe [DaoCategorie];
Lo stesso vale per i prodotti:
![]() | ![]() |
17.3.4.1. Gli URL esposti dal [CategoryController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. Gli URL esposti dal [ProductController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.5. Implementazione generica del servizio web
![]() |
L'elenco degli URL esposti dal servizio web mostra che offriamo gli stessi tipi di URL per la gestione delle categorie e dei prodotti. Anziché scrivere due controller molto simili, li faremo derivare da una classe che gestirà tutto il lavoro comune a entrambi i controller. Si tratterà della classe [AbstractController] sopra riportata. Questa classe implementerà la seguente interfaccia [Iws]:
package spring.webjson.server.service;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import spring.jdbc.entities.AbstractCoreEntity;
public interface Iws<T extends AbstractCoreEntity> {
// list of all T entities
public Response<List<T>> getAllShortEntities();
public Response<List<T>> getAllLongEntities();
// special entities - short version
public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
// special entities - long version
public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
// update of several entities
public Response<List<T>> saveEntities(HttpServletRequest request);
// delete all entities
public Response<Void> deleteAllEntities();
// deletion of multiple entities
public Response<Void> deleteEntitiesById(HttpServletRequest request);
public Response<Void> deleteEntitiesByName(HttpServletRequest request);
}
Questa interfaccia implementa i metodi dell'interfaccia del livello [DAO] che verrà utilizzata:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.AbstractCoreEntity;
public interface IDao<T extends AbstractCoreEntity> {
// list of all T entities
public List<T> getAllShortEntities();
public List<T> getAllLongEntities();
// special entities - short version
public List<T> getShortEntitiesById(Iterable<Long> ids);
public List<T> getShortEntitiesById(Long... ids);
public List<T> getShortEntitiesByName(Iterable<String> names);
public List<T> getShortEntitiesByName(String... names);
// special entities - long version
public List<T> getLongEntitiesById(Iterable<Long> ids);
public List<T> getLongEntitiesById(Long... ids);
public List<T> getLongEntitiesByName(Iterable<String> names);
public List<T> getLongEntitiesByName(String... names);
// update of several entities
public List<T> saveEntities(Iterable<T> entities);
public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
// delete all entities
public void deleteAllEntities();
// deletion of multiple entities
public void deleteEntitiesById(Iterable<Long> ids);
public void deleteEntitiesById(Long... ids);
public void deleteEntitiesByName(Iterable<String> names);
public void deleteEntitiesByName(String... names);
public void deleteEntitiesByEntity(Iterable<T> entities);
public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}
Il passaggio dall'interfaccia [IDao<T>] nel livello [DAO] all'interfaccia [Iws<T>] nel servizio web ha seguito queste regole:
- i metodi dell'interfaccia [Iws<T>] non genereranno eccezioni. Se si verifica un'eccezione, questa verrà incapsulata nell'oggetto [Response];
- le variazioni dei parametri come quelle alle righe 45 e 47 [Iterable<String> names, String... names] vengono rimosse. I metodi ottengono i propri parametri dalla richiesta HTTP del client di tipo [HttpServletRequest request];
Tutte le risposte del servizio web saranno incapsulate nel seguente oggetto [Response]:
![]() |
package spring.webjson.server.service;
public class Response<T> {
// ----------------- properties
// operation status
private int status;
// an error message
private String exception;
// the body of the reply
private T body;
// manufacturers
public Response() {
}
public Response(int status, String exception, T body) {
this.status = status;
this.exception = exception;
this.body = body;
}
// getters and setters
...
}
- riga 4: la risposta incapsula un tipo T;
- riga 12: la risposta di tipo T;
- righe 7–10: un metodo può incontrare un'eccezione. In questo caso, restituirà una risposta con:
- riga 8: status!=0;
- riga 10: un messaggio di errore;
La classe [AbstractController] è la seguente:
package spring.webjson.server.service;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// layer DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
// local
private String simpleClassName = getClass().getSimpleName();
@PostConstruct
public void init(){
dao=getDao();
}
@Override
public Response<List<T>> getAllShortEntities() {
try {
// answer
return new Response<List<T>>(0, null, dao.getAllShortEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getAllLongEntities() {
try {
// answer
return new Response<List<T>>(0, null, dao.getAllLongEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
@Override
public Response<Void> deleteAllEntities() {
try {
// we delete
dao.deleteAllEntities();
// answer
return new Response<Void>(0, null, null);
} catch (DaoException e) {
return new Response<Void>(1, e.toString(), null);
} catch (Exception e) {
return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
}
}
@Override
public Response<Void> deleteEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
...
}
}
Tutti i metodi sono implementati allo stesso modo:
- se hanno bisogno di informazioni, le recuperano dall'oggetto [HttpServletRequest request];
- chiamano il metodo nel livello [DAO] che ha lo stesso nome del loro;
- gestiscono eventuali eccezioni che potrebbero verificarsi, sia nell'operazione 1 (recupero dei parametri) che nell'operazione 2 (chiamata del livello [DAO]);
Esaminiamo innanzitutto come il livello [DAO] viene iniettato nella classe [AbstractController]:
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// layer DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
@PostConstruct
public void init(){
dao=getDao();
}
- riga 1: la classe è astratta e implementa l'interfaccia generica [Iws<T>];
- righe 3-4: iniezione del contesto Spring;
- riga 7: il riferimento ancora sconosciuto al livello [DAO] da utilizzare;
- riga 9: il metodo astratto [getDao] che restituirà il riferimento al livello [DAO] da utilizzare. Questo metodo verrà sovrascritto dalla classe figlia, quindi sarà la classe figlia a specificare quale livello [DAO] utilizzare (DaoProduct o DaoCategory);
- riga 11: l'annotazione [@PostConstruct] contrassegna un metodo da eseguire una volta completata l'istanziazione dell'oggetto. Una volta completata l'istanziazione, le iniezioni Spring sono state eseguite. La classe figlia avrà quindi ottenuto il riferimento al proprio livello [DAO] e potrà quindi passarlo alla propria classe padre;
Il metodo [getShortEntitiesById] è il seguente:
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
try {
// retrieve the posted value
String body = CharStreams.toString(request.getReader());
// we deserialize it
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
});
// answer
return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
}
}
- riga 5: il valore inviato dal client sarà una stringa JSON. È qui che viene recuperato;
- righe 7–9: la stringa JSON contiene l'elenco delle chiavi primarie delle entità di cui si desidera la versione abbreviata;
- riga 11: chiamiamo il metodo [DAO] con lo stesso nome. La risposta di tipo [List<T>] è incapsulata in un oggetto [Response];
- riga 13: caso in cui il livello [DAO] ha generato un'eccezione;
- riga 15: caso per altre eccezioni, in particolare la possibile eccezione durante la deserializzazione del parametro JSON, riga 8;
Il metodo [getShortEntitiesByName] è simile:
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
try {
// retrieve the posted value
String body = CharStreams.toString(request.getReader());
// we deserialize it
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
});
// answer
return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
}
}
- righe 4–9: qui, il parametro jSON è l'elenco dei nomi delle categorie per cui vogliamo la versione breve;
Il metodo [saveEntities] non è stato implementato perché dipende fortemente dal tipo di entità da salvare, [Category] o [Product]. C'è poco codice da rifattorizzare. Questo compito è quindi lasciato alle classi figlie.
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
17.3.6. Il [CategoryController]
![]() |
Il controller [CategorieController] gestisce gli URL relativi alle categorie:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class CategorieController extends AbstractController<Categorie> {
@Autowired
private IDao<Categorie> daoCategorie;
@Override
protected IDao<Categorie> getDao() {
return daoCategorie;
}
// local
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories() {
// parent
Response<List<Categorie>> response = super.getAllShortEntities();
// serialization filters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllLongCategories() {
// parent
Response<List<Categorie>> response = super.getAllLongEntities();
// serialization filters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
// parent
Response<List<Categorie>> response = super.getShortEntitiesById(request);
// serialization filters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
// parent
Response<List<Categorie>> response = super.getShortEntitiesByName(request);
// serialization filters jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
// parent
Response<List<Categorie>> response = super.getLongEntitiesById(request);
// serialization filters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
// parent
Response<List<Categorie>> response = super.getLongEntitiesByName(request);
// serialization filters jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
public Response<Void> deleteAllCategories() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
- riga 26: la classe [CategorieController] estende la classe [AbstractController];
- riga 25: l'annotazione [@RestController] rende la classe un componente Spring. Questa annotazione indica anche che la classe è un servizio web i cui metodi inviano le loro risposte direttamente al client in formato JSON;
- righe 28–29: qui viene iniettato il riferimento al livello [DAO];
- righe 31–34: ridefinizione del metodo [getDao], dichiarato astratto nella classe padre, il cui scopo è restituire un riferimento al livello [DAO] da utilizzare;
I metodi sono tutti costruiti sullo stesso modello:
- delega dell'elaborazione alla classe padre;
- inizializzazione del mapper JSON che serializzerà la risposta;
- invio della risposta;
Diamo un'occhiata alle firme di alcuni URL:
| - l'URL [/getAllShortCategories] viene chiamato con un GET |
| - L'URL [/getShortCategoriesById] viene chiamato con una richiesta POST. Il valore inviato è la stringa JSON contenente le chiavi primarie delle categorie desiderate; |
| - L'URL [/getLongCategoriesByName] viene chiamato con una richiesta POST. Il valore inviato è la stringa JSON contenente i nomi delle categorie desiderate; |
Ora, diamo un'occhiata più da vicino al metodo [saveCategories], che non segue il formato degli altri metodi:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// we persist categories
try {
// retrieve the posted value
String body = CharStreams.toString(request.getReader());
// we deserialize it
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// we persist categories
categories = daoCategorie.saveEntities(categories);
// we return the result
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// result
return new Response<List<CoreCategorie>>(0, null, coreCategories);
} catch (DaoException e) {
return new Response<List<CoreCategorie>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
}
}
- riga 1: l'URL [/saveCategories] è accompagnato da un valore inviato tramite POST. Si tratta della stringa JSON contenente le versioni complete delle categorie da salvare;
- righe 5–10: le categorie da salvare vengono ricreate dalla stringa JSON. Il collegamento [product.category] che collega un [Product] alla sua [Category] è nullo perché nella versione lunga di una [Category], ogni [Product] è nella sua versione breve senza il campo [category]. Questo non è un problema, poiché il livello [DAO] implementato con JDBC non richiede questa informazione;
- riga 12: le categorie vengono salvate. L'elenco delle categorie ricevuto è stato arricchito con le chiavi primarie degli elementi salvati, delle categorie e dei prodotti. Nient'altro è cambiato. Anziché restituire l'intero elenco ricevuto, operazione costosa, restituiremo solo le chiavi primarie degli elementi presenti in questo elenco. Per farlo, utilizziamo le seguenti classi [CoreCategory] e [CoreProduct]:
![]() |
package spring.webjson.server.entities;
import java.util.List;
public class CoreCategorie {
// primary key
private Long id;
// manufacturers
public CoreCategorie() {
}
public CoreCategorie(Long id) {
this.id=id;
}
// list of products
private List<CoreProduit> coreProduits;
// getters and setters
...
}
- riga 8: la chiave primaria di un prodotto;
- riga 20: le chiavi primarie dei suoi prodotti;
package spring.webjson.server.entities;
public class CoreProduit {
// primary key
private Long id;
// manufacturers
public CoreProduit() {
}
public CoreProduit(Long id) {
this.id = id;
}
// getters and setters
...
}
- riga 6: la chiave primaria di un prodotto;
Torniamo al codice del metodo [saveCategories]:
...
// on persiste les catégories
categories = daoCategorie.saveEntities(categories);
// on rend le résultat
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// résultat
return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
- righe 5–17: creiamo l'elenco di oggetti [CoreCategory] che restituiremo al client remoto;
- riga 19: la risposta viene restituita e serializzata in JSON;
17.3.7. Gestione dei filtri JSON
Per ogni metodo di un controller, ci sono due punti per la serializzazione/deserializzazione JSON:
- deserializzazione del valore inviato: qui viene gestita in modo esplicito;
- serializzazione del risultato: qui viene gestita implicitamente;
Iniziamo con la deserializzazione del valore inviato in [CategorieController.saveCategories]:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// we persist categories
try {
// retrieve the posted value
String body = CharStreams.toString(request.getReader());
// we deserialize it
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
- Riga 8: Recuperiamo dal contesto Spring un mapper configurato per gestire il filtro JSON [jsonMapperLongCategorie]. Torniamo alla definizione di questo mapper nella classe di configurazione [WebConfig]:
// -------------------------------- configuration filters [json]
// mapping jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// filters jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
- righe 32–40: il mappatore JSON [jsonMapperLongCategorie] recuperato dalla classe [CategorieController];
- riga 35: questo mappatore viene restituito dal metodo [jsonMapper] nelle righe 18–21;
- righe 18–21: il metodo [jsonMapper] restituisce il mappatore JSON dal [MappingJackson2HttpMessageConverter] nelle righe 3–9;
In altre parole, il mappatore JSON recuperato dalla riga 4 qui sotto in [CategorieController.saveCategories]:
// on récupère la valeur postée
String body = CharStreams.toString(request.getReader());
// on la désérialise
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
è il convertitore predefinito utilizzato da Spring MVC per deserializzare il valore inviato dal client e serializzare il risultato che gli viene restituito. Nelle righe precedenti non c'era alcuna deserializzazione implicita del valore inviato. Per farlo, avresti dovuto scrivere:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {
In questo caso, ci sarebbe stata una deserializzazione automatica del valore inviato nel parametro [categories]. Ma c'era il problema del filtro [jsonFilterCategorie] che le entità [Categorie] possiedono. Deve essere configurato. Ecco perché abbiamo scelto la deserializzazione esplicita (righe 4–5). Il secondo punto da notare è che il mapper alla riga 4 (che è quello utilizzato di default da Spring MVC) è adatto anche per serializzare il risultato [Response<List<CoreCategory>]. Infatti, l'entità [CoreCategorie] non ha un filtro JSON. Pertanto, non è necessario configurare il mapper JSON risultante con un filtro aggiuntivo. In questo caso, la risposta inviata al client verrà serializzata implicitamente.
17.3.8. Il [ProductController]
![]() |
Il controller [ProduitController] gestisce gli URL relativi ai prodotti. Il suo codice è simile a quello del controller [CategorieController]:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class ProduitController extends AbstractController<Produit> {
@Autowired
private IDao<Produit> daoProduit;
@Override
protected IDao<Produit> getDao() {
return daoProduit;
}
// local
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllShortProduits() {
// parent
Response<List<Produit>> response = super.getAllShortEntities();
// serialization filters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllLongProduits() {
// parent
Response<List<Produit>> response = super.getAllLongEntities();
// serialization filters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
// parent
Response<List<Produit>> response = super.getShortEntitiesById(request);
// serialization filters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
// parent
Response<List<Produit>> response = super.getShortEntitiesByName(request);
// serialization filters jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
// parent
Response<List<Produit>> response = super.getLongEntitiesById(request);
// serialization filters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
// parent
Response<List<Produit>> response = super.getLongEntitiesByName(request);
// serialization filters jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// answer
return response;
}
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
public Response<Void> deleteAllProduits() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
Solo il metodo [saveProducts] ha una struttura diversa dagli altri metodi:
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
try {
// retrieve the posted value
String body = CharStreams.toString(request.getReader());
// we deserialize it
ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
});
// we persist products
produits = daoProduit.saveEntities(produits);
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : produits) {
coreProduits.add(new CoreProduit(produit.getId()));
}
// we return the answer
return new Response<List<CoreProduit>>(0, null, coreProduits);
} catch (DaoException e) {
return new Response<List<CoreProduit>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
}
}
- righe 4–9: dalla stringa JSON ricevuta, ricostruiamo l'elenco degli oggetti [Product] da salvare. Poiché la stringa JSON ricevuta contiene le versioni abbreviate dei prodotti, il loro campo [category] è nullo. Ancora una volta, il livello DAO/JDBC non ha bisogno di questa informazione;
- riga 11: i prodotti vengono salvati;
- righe 12–15: viene costruito l'elenco degli oggetti [CoreProduct] da restituire;
- riga 18: viene restituita la risposta, che verrà serializzata (serializzazione implicita eseguita da Spring MVC) dal mapper della riga 7 prima di essere inviata al client remoto (vedi la discussione nella sezione 17.3.7);
17.3.9. Il servizio web / classe di esecuzione JSON
![]() |
La classe [Boot] è la classe eseguibile del progetto:
package spring.webjson.server.boot;
import org.springframework.boot.SpringApplication;
import spring.webjson.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- Riga 10: Viene eseguito il metodo statico [SpringApplication.run]. La classe [SpringApplication] è una classe del progetto [Spring Boot] (riga 3). Le vengono passati due parametri:
- [AppConfig.class]: la classe che configura l'intera applicazione;
- [args]: eventuali argomenti passati al metodo [main] alla riga 9. Questo parametro non viene utilizzato in questo caso;
Quando questa classe viene eseguita, vengono generati i seguenti log:
- righe 11-15: vengono individuati i bean che definiscono i filtri JSON. Questi sovrascrivono i bean con lo stesso nome individuati nel progetto di configurazione del livello JDBC;
- righe 17-18: viene avviato il server Tomcat per eseguire il servizio web/JSON;
- Righe 19–21: viene inizializzato il contesto Spring MVC;
- righe 24–43: vengono individuati gli URL esposti;
17.3.10. Test del servizio web /jSON
Per eseguire i test, utilizziamo [Advanced Rest Client] (vedere la sezione 23.11) per interrogare gli URL esposti dal servizio web /jSON (ovviamente, il servizio web /jSON deve essere in esecuzione, così come il DBMS). Per popolare il database, eseguiamo la configurazione di esecuzione denominata [spring-jdbc-generic-04-fillDataBase], che popola il database con 5 categorie e 10 prodotti:
![]() |
![]() |
- In [1-3], richiediamo l'URL [/getAllLongCategories] tramite una richiesta HTTP GET;
Riceviamo la seguente risposta:
![]() |
- in [1], la richiesta HTTP del client;
- in [2], la risposta HTTP del server;
- in [3], lo stato [200 OK] indica che il server ha elaborato con successo la richiesta;
- in [4], la risposta JSON del server;
La risposta JSON completa è la seguente:
{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
- status:0 significa che non ci sono stati errori lato server;
- exception: null significa che non c'è alcun messaggio di errore;
- body: è il corpo della risposta, in questo caso l'elenco delle categorie con i relativi prodotti. Ci sono due categorie, ciascuna con 5 prodotti;
Aggiungeremo il prodotto [product15] alla categoria [category1]. Per farlo, useremo l'URL [/saveProducts], che richiede una stringa JSON dei prodotti da salvare (inserimento/aggiornamento). Questa stringa sarà la seguente:
[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]
La richiesta al servizio web / JSON viene effettuata come segue:
![]() |
- in [1], l'URL richiesto;
- in [2], viene richiesto tramite un'operazione POST;
- in [3], la stringa JSON inviata;
- in [4], il server viene informato che verranno inviati dati JSON;
La risposta del server è la seguente:
![]() |
- in [1], abbiamo ricevuto un elenco di oggetti [CoreProduct] con le loro chiavi primarie. Qui, abbiamo ricevuto un elenco contenente un singolo elemento con la chiave primaria del prodotto che abbiamo appena inserito nel database;
Ora, richiediamo la versione completa della categoria denominata [category[1]:
![]() |
- In [1], l'URL richiesto;
- in [2], effettuiamo una richiesta POST;
- in [3,4], il valore inviato è una stringa JSON. Questa rappresenta l'elenco dei nomi delle categorie per le quali vogliamo la versione estesa;
Otteniamo il seguente risultato:
![]() |
- in [5], la categoria [category[1]] ora ha un sesto prodotto;
Ora eliminiamo questo prodotto:
![]() |
- In [1], l'URL richiesto;
- In [2], inviamo una richiesta POST;
- in [3-4], inviamo una stringa JSON che rappresenta l'elenco delle chiavi primarie dei prodotti che vogliamo eliminare;
Il risultato è il seguente:
![]() |
- [status:0] indica che l'eliminazione è andata a buon fine;
Ora, richiediamo il prodotto [product[1,5]] per verificare che sia stato effettivamente eliminato:
![]() |
Otteniamo il seguente risultato:
![]() |
- [status:0] indica che l'operazione è stata completata senza eccezioni;
- [body:[0]] indica che [body] è una lista vuota. L'entità [product[1,5]] è stata quindi eliminata con successo;
Tutte le operazioni [GET] possono essere eseguite in un browser web standard:
![]() |
I lettori sono invitati a provare gli altri URL del servizio web / JSON.








































