Skip to content

16. Introduzione a Spring MVC

16.1. Il ruolo di Spring MVC in un'applicazione web

Inquadriamo Spring MVC nello sviluppo di un'applicazione web. Molto spesso, essa sarà basata su un'architettura a più livelli come la seguente:

  • il livello [Web] è quello che interagisce con l'utente dell'applicazione web. L'utente interagisce con l'applicazione web tramite le pagine web visualizzate in un browser. Spring MVC risiede in questo livello e solo in questo livello;
  • il livello [business] implementa la logica di business dell'applicazione, come il calcolo di uno stipendio o di una fattura. Questo livello utilizza i dati provenienti dall'utente tramite il livello [Web] e dal DBMS tramite il livello [DAO];
  • il livello [DAO] (Data Access Objects), il livello [ORM] (Object Relational Mapper) e il driver JDBC gestiscono l'accesso ai dati nel DBMS. Il livello [ORM] funge da ponte tra gli oggetti gestiti dal livello [DAO] e le righe e le colonne delle tabelle in un database relazionale. Una specifica denominata JPA (Java Persistence API) consente di astrarre dall'ORM utilizzato se questo implementa tali specifiche. Questo sarà il caso in questo tutorial, quindi d'ora in poi ci riferiremo al livello ORM come al livello JPA;
  • L'integrazione di questi livelli è gestita dal framework Spring;

16.2. Il modello di sviluppo Spring MVC

Spring MVC implementa il modello architettonico MVC (Model–View–Controller) come segue:

L'elaborazione di una richiesta del cliente procede come segue:

  1. richiesta - gli URL richiesti hanno la forma http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... Il [Front Controller] utilizza un file di configurazione o annotazioni Java per "instradare" la richiesta al controller corretto e all'azione corretta all'interno di quel controller. Per farlo, utilizza il campo [Action] dell'URL. Il resto dell'URL [/param1/param2/...] è costituito da parametri opzionali che verranno passati all'azione. La "C" in MVC qui si riferisce alla catena [Front Controller, Controller, Action]. Se nessun controller è in grado di gestire l'azione richiesta, il server web risponderà che l'URL richiesto non è stato trovato.
  1. Elaborazione
      • (continua)
    • L'azione selezionata può utilizzare i parametri che il [Front Controller] le ha passato. Questi possono provenire da diverse fonti:
      • il percorso [/param1/param2/...] dell'URL,
      • i parametri [p1=v1&p2=v2] dell'URL,
      • dai parametri inviati dal browser con la sua richiesta;
    • Durante l'elaborazione della richiesta dell'utente, l'azione potrebbe aver bisogno del livello [business] [2b]. Una volta elaborata la richiesta del client, questa può innescare varie risposte. Un esempio classico è:
      • una pagina di errore se la richiesta non è stata elaborata correttamente
      • una pagina di conferma in caso contrario
    • L'azione istruisce una vista specifica a renderizzare [3]. Questa vista visualizzerà i dati noti come modello di vista. Questa è la "M" in MVC. L'azione creerà questo modello di vista [2c] e istruirà una vista a renderizzare [3];
  1. Risposta - La vista selezionata V utilizza il modello M creato dall'azione per inizializzare le parti dinamiche della risposta HTML che deve inviare al client, quindi invia questa risposta.

Per un servizio web / JSON, l'architettura precedente viene leggermente modificata:

  • in [4a], il modello, che è una classe Java, viene convertito in una stringa JSON da una libreria JSON;
  • in [4b], questa stringa JSON viene inviata al browser;

Ora, chiariamo la relazione tra l'architettura web MVC e l'architettura a livelli. A seconda di come è definito il modello, questi due concetti possono essere correlati o meno. Consideriamo un'applicazione web Spring MVC a livello singolo:

Se implementiamo il livello [Web] con Spring MVC, avremo effettivamente un'architettura web MVC ma non un'architettura a più livelli. In questo caso, il livello [Web] gestirà tutto: presentazione, logica di business e accesso ai dati. Saranno le azioni a svolgere questo lavoro.

Ora, consideriamo un'architettura web multistrato:

Il livello [Web] può essere implementato senza un framework e senza seguire il modello MVC. Si ottiene così un'architettura a più livelli, ma il livello Web non implementa il modello MVC.

Ad esempio, nell'ambiente .NET, il livello [Web] sopra descritto può essere implementato con ASP.NET MVC, ottenendo un'architettura a livelli con un livello [Web] in stile MVC. Una volta fatto ciò, possiamo sostituire questo livello ASP.NET MVC con un livello ASP.NET classico (WebForms) mantenendo invariato il resto (logica di business, DAO, ORM). Otterremo così un'architettura a livelli con un livello [Web] che non è più basato su MVC.

In MVC, abbiamo detto che il modello M era quello della vista V, ovvero l'insieme di dati visualizzati dalla vista V. Viene fornita un'altra definizione del modello M in MVC:

Molti autori ritengono che ciò che si trova a destra del livello [Web] costituisca il modello M dell'MVC. Per evitare ambiguità, possiamo fare riferimento al:

  • il modello di dominio quando ci si riferisce a tutto ciò che si trova a destra del livello [Web]
  • il modello di vista quando ci si riferisce ai dati visualizzati da una vista V

D'ora in poi, il termine "modello M" si riferirà esclusivamente al modello di una vista V.

16.3. Un progetto Web/JSON con Spring MVC

Il sito [http://spring.io/guides] offre tutorial introduttivi per esplorare l'ecosistema Spring. Ne seguiremo uno per scoprire la configurazione Maven necessaria per un progetto Spring MVC.

16.3.1. Il progetto demo

  • In [1], importiamo una delle guide di Spring;
  • in [2], selezioniamo l'esempio [Rest Service];
  • in [3], selezioniamo il progetto Maven;
  • in [4], selezioniamo la versione finale della guida;
  • in [5], confermiamo;
  • in [6], il progetto importato;

I servizi web accessibili tramite URL standard che restituiscono dati JSON sono spesso chiamati servizi REST (REpresentational State Transfer). Un servizio è detto RESTful se segue determinate regole.

Esaminiamo ora il progetto importato, iniziando dalla sua configurazione Maven.

16.3.2. Configurazione Maven

Il file [pom.xml] è il seguente:


<?xml version="1.0" encoding="UTF-8"?>
<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>org.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.2.RELEASE</version>
    </parent>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
 
    <properties>
        <start-class>hello.Application</start-class>
    </properties>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
    <repositories>
        <repository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>
</project>
  • righe 6–8: le proprietà del progetto Maven. Manca un tag [<packaging>] che specifichi il tipo di file prodotto dalla build di Maven. In sua assenza, viene utilizzato il tipo [jar]. L'applicazione è quindi un'applicazione eseguibile basata su console, non un'applicazione web, nel qual caso il packaging sarebbe [war];
  • righe 10–14: il progetto Maven ha un progetto padre [spring-boot-starter-parent]. Questo definisce la maggior parte delle dipendenze del progetto. Esse potrebbero essere sufficienti, nel qual caso non vengono aggiunte dipendenze aggiuntive, oppure potrebbero non esserlo, nel qual caso vengono aggiunte le dipendenze mancanti;
  • Righe 17–20: L'artefatto [spring-boot-starter-web] include le librerie necessarie per un progetto di servizio web Spring MVC in cui non vengono generate viste. Questo artefatto include un numero molto elevato di librerie, comprese quelle per un server Tomcat incorporato. L'applicazione verrà eseguita su questo server;

Le librerie incluse in questa configurazione sono numerose:

Sopra, vediamo i tre archivi del server Tomcat.

16.3.3. L'architettura di un servizio Spring [web / JSON]

Per un servizio web/JSON, Spring MVC implementa il modello MVC come segue:

  • In [4a], il modello — che è una classe Java — viene convertito in una stringa JSON da una libreria JSON;
  • in [4b], questa stringa JSON viene inviata al browser;

16.3.4. Il controller C

  

L'applicazione importata dispone del seguente controller:


package hello;
 
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class GreetingController {
 
    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();
 
    @RequestMapping("/greeting")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}
  • Riga 9: L'annotazione [@RestController] rende la classe [GreetingController] un controller Spring, il che significa che i suoi metodi sono registrati per gestire gli URL. Abbiamo già visto l'annotazione simile [@Controller]. Il tipo di ritorno dei metodi di quel controller era [String], ovvero il nome della vista da visualizzare. Qui è diverso. I metodi di un [@RestController] restituiscono oggetti che vengono serializzati per essere inviati al browser. Il tipo di serializzazione eseguita dipende dalla configurazione di Spring MVC. Qui, saranno serializzati in JSON. È la presenza di una libreria JSON nelle dipendenze del progetto che fa sì che Spring Boot configuri automaticamente il progetto in questo modo;
  • riga 14: l'annotazione [@RequestMapping] specifica l'URL gestito dal metodo, in questo caso l'URL [/greeting];
  • riga 15: abbiamo già spiegato l'annotazione [@RequestParam]. Il risultato restituito dal metodo è un oggetto di tipo [Greeting].
  • riga 12: un intero lungo di tipo atomico. Ciò significa che supporta l'accesso concorrente. Più thread potrebbero voler incrementare la variabile [counter] contemporaneamente. Ciò verrà gestito correttamente. Un thread può leggere il valore del contatore solo una volta che il thread che lo sta modificando ha terminato la modifica.

16.3.5. Il modello M

Il modello M prodotto dal metodo precedente è il seguente oggetto [Greeting]:

  

package hello;
 
public class Greeting {
 
    private final long id;
    private final String content;
 
    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }
 
    public long getId() {
        return id;
    }
 
    public String getContent() {
        return content;
    }
}

La trasformazione JSON di questo oggetto creerà la stringa {"id":n,"content":"text"}. Alla fine, la stringa JSON prodotta dal metodo del controller avrà la forma:

{"id":2,"content":"Hello, World!"}

oppure

{"id":2,"content":"Hello, John!"}

16.3.6. Esecuzione

  

La classe [Application.java] è la classe eseguibile del progetto. Il suo codice è il seguente:


package hello;
 
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
 
@ComponentScan
@EnableAutoConfiguration
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 
}

Abbiamo già visto e spiegato questo codice nell'esempio precedente. Eseguiamo il progetto:

 

Otteniamo i seguenti log della console:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.9.RELEASE)

2014-11-28 15:22:55.005  INFO 3152 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 3152 (started by ST in D:\data\istia-1415\spring mvc\dvp-final\gs-rest-service)
2014-11-28 15:22:55.046  INFO 3152 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@62e136d3: startup date [Fri Nov 28 15:22:55 CET 2014]; root of context hierarchy
2014-11-28 15:22:55.762  INFO 3152 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-11-28 15:22:56.567  INFO 3152 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-11-28 15:22:56.738  INFO 3152 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2014-11-28 15:22:56.740  INFO 3152 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.56
2014-11-28 15:22:56.869  INFO 3152 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-11-28 15:22:56.870  INFO 3152 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1827 ms
2014-11-28 15:22:57.478  INFO 3152 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-11-28 15:22:57.481  INFO 3152 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-11-28 15:22:57.685  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.879  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public hello.Greeting hello.GreetingController.greeting(java.lang.String)
2014-11-28 15:22:57.884  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.885  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.906  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.907  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:58.231  INFO 3152 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-11-28 15:22:58.318  INFO 3152 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-11-28 15:22:58.319  INFO 3152 --- [           main] hello.Application                        : Started Application in 3.788 seconds (JVM running for 4.424)
  • riga 13: il server Tomcat si avvia sulla porta 8080 (riga 12);
  • riga 17: il servlet [DispatcherServlet] è presente;
  • riga 20: il metodo [GreetingController.greeting] è stato individuato;

Per testare l'applicazione web, richiediamo l'URL [http://localhost:8080/greeting]:

 

Riceviamo la stringa JSON prevista. Potrebbe essere interessante visualizzare le intestazioni HTTP inviate dal server. Per farlo, useremo l'estensione di Chrome chiamata [Advanced Rest Client] (Chrome / Ctrl-T / menu [Applicazioni] / [Advanced Rest Client] - vedi Appendici, paragrafo 23.11):

  • in [1], l'URL richiesto;
  • in [2], viene utilizzato il metodo GET;
  • in [3], la risposta JSON;
  • in [4], il server ha indicato che avrebbe inviato una risposta in formato JSON;
  • in [5], viene richiesto lo stesso URL, ma questa volta utilizzando una richiesta POST;
  • in [7], le informazioni vengono inviate al server in formato [urlencoded];
  • in [6], il parametro name con il suo valore;
  • in [8], il browser comunica al server che sta inviando dati [urlencoded];
  • in [9], la risposta JSON del server;

16.3.7. Creazione di un archivio eseguibile

Ora creeremo un archivio eseguibile:

  • in [1]: eseguiamo un target Maven;
  • in [2]: ci sono due goal: [clean] per eliminare la cartella [target] dal progetto Maven, [package] per rigenerarla;
  • in [3]: la cartella [target] generata si troverà in questa cartella;
  • in [4]: il target è generato;

Nei log che compaiono nella console, è importante vedere elencato [spring-boot-maven-plugin]. Questo è il plugin che genera l'archivio eseguibile (vedi [pom.xml] qui sotto):


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
</build>
[INFO] --- spring-boot-maven-plugin:1.1.0.RELEASE:repackage (default) @ gs-rest-service ---

In una finestra di comando, accedi alla cartella generata:


D:\Temp\wksSTS\gs-rest-service\target>dir
 ...
11/06/2014  15:30    <DIR>          classes
11/06/2014  15:30    <DIR>          generated-sources
11/06/2014  15:30        11 073 572 gs-rest-service-0.1.0.jar
11/06/2014  15:30             3 690 gs-rest-service-0.1.0.jar.original
11/06/2014  15:30    <DIR>          maven-archiver
11/06/2014  15:30    <DIR>          maven-status
...
  • riga 5: l'archivio generato;

Questo archivio viene eseguito come segue:


D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.0.RELEASE)
 
2014-06-11 15:32:47.088  INFO 4972 --- [           main] hello.Application
                  : Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...

Ora che l'applicazione web è in esecuzione, è possibile accedervi utilizzando un browser:

 

16.3.8. Distribuzione dell'applicazione su un server Tomcat

Come abbiamo fatto per il progetto precedente, modifichiamo il file [pom.xml] come segue:


<?xml version="1.0" encoding="UTF-8"?>
<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>org.springframework</groupId>
    <artifactId>gs-rest-service</artifactId>
    <version>0.1.0</version>
    <packaging>war</packaging>
 
    ...
</project>
  • Riga 9: È necessario specificare che si sta generando un file WAR (Web Archive);

È inoltre necessario configurare l'applicazione web. Se non è presente un file [web.xml], ciò viene effettuato utilizzando una classe che estende [SpringBootServletInitializer]:

  

La classe [ApplicationInitializer] è la seguente:


package hello;
 
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
 
public class ApplicationInitializer extends SpringBootServletInitializer {
 
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }
 
}
  • riga 6: la classe [ApplicationInitializer] estende la classe [SpringBootServletInitializer];
  • riga 9: il metodo [configure] viene sovrascritto (riga 8);
  • riga 10: viene fornita la classe che configura il progetto;

Per eseguire il progetto, procedere come segue:

  • In [1-2], eseguire il progetto su uno dei server registrati nell'IDE Eclipse;

Una volta fatto ciò, è possibile richiamare l'URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] in un browser:

 

16.4. Conclusione

Abbiamo presentato un tipo di progetto Spring MVC in cui l'applicazione web invia un flusso JSON al browser. Ora svilupperemo un'applicazione web/JSON per rendere accessibile sul web il database [dbproduitscategories] studiato nei capitoli precedenti.