Skip to content

3. Azioni: la risposta

Consideriamo l'architettura di un'applicazione Spring MVC:

In questo capitolo, esaminiamo il processo che instrada la richiesta [1] al controller e all'azione [2a] che la elaborerà, un meccanismo noto come routing. Presentiamo inoltre le varie risposte [3] che un'azione può restituire al browser. Queste possono essere diverse da una vista V [4b].

3.1. Il nuovo progetto

Creiamo un nuovo progetto Spring MVC:

  • In [1-2], creiamo un nuovo progetto basato su Spring Boot;
  • in [3], il nome del progetto Maven;
  • in [4], il gruppo Maven in cui verrà collocato l'output della compilazione del progetto;
  • in [5], il nome assegnato all'output della compilazione;
  • in [6], una descrizione del progetto;
  • in [7], il pacchetto in cui verrà collocata la classe eseguibile del progetto;
  • in [8], la natura del progetto. Si tratta di un progetto web con viste Thymeleaf. Qui vediamo tutte le dipendenze Maven pronte all'uso fornite dal progetto Spring Boot;
  • in [9], specifichiamo che l'output della build Maven sarà impacchettato in un archivio JAR anziché in un WAR. Il progetto utilizzerà quindi un server Tomcat incorporato, che sarà incluso nelle sue dipendenze;
  • in [10], si procede al passo successivo della procedura guidata;
  • in [11], specifichiamo la directory del progetto;
  • in [12], il progetto generato;
  • In [14-15], rinominare il pacchetto [istia.st.springmvc];
  • in [16], il nuovo nome del pacchetto;
  • in [17], il nuovo progetto;

Ora creiamo una nuova classe;

  • nei punti [1-3], creiamo una nuova classe;
  • in [5] le diamo un nome e in [4] ne specifichiamo il pacchetto;
  • in [6] il nuovo progetto;

La classe è attualmente la seguente:


package istia.st.springmvc;
 
public class ActionsController {
 
}

Stiamo aggiornando questo codice come segue:


package istia.st.springmvc;
 
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class ActionsController {
 
}
  • Riga 6: L'annotazione [@RestController] indica due cose:
    • che la classe [ActionsController] annotata in questo modo è un controller Spring MVC e, pertanto, contiene azioni che gestiscono gli URL dei client;
    • che il risultato di queste azioni viene inviato al client;

L'altra annotazione [@Controller] che abbiamo incontrato è diversa: le azioni di un controller annotato in questo modo restituiscono il nome della vista che dovrebbe essere visualizzata. È quindi la combinazione di questa vista e del modello costruito dall'azione per questa vista che fornisce la risposta inviata al client.

Il cambiamento nella struttura del nostro progetto richiede una modifica nella configurazione del nostro progetto:

  

La classe [Application] si evolve come segue:


package istia.st.springmvc.main;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
 
@Configuration
@ComponentScan({"istia.st.springmvc.controllers"})
@EnableAutoConfiguration
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • Riga 9: L'annotazione [ComponentScan] accetta come parametro un array di nomi di pacchetti in cui Spring Boot deve cercare i componenti Spring. Qui, includiamo il pacchetto [istia.st.springmvc.controllers] in questo array in modo che il controller annotato con [@RestController] possa essere trovato;

Creeremo varie azioni nel controller per illustrarne le caratteristiche principali. Innanzitutto, ci concentreremo sui vari tipi di risposte possibili per un'azione in un'applicazione senza viste.

3.2. [/a01, /a02] - Hello world

La nostra prima azione sarà la seguente:


@RestController
public class ActionsController {
    // ----------------------- hello world ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • Riga 4: L'annotazione [RequestMapping] specifica la richiesta gestita dall'azione annotata:
    • l'attributo [value] è l'URL in fase di elaborazione,
    • l'attributo [method] specifica il metodo accettato;

Pertanto, il metodo [a01] gestisce la richiesta HTTP [GET /a01].

  • riga 5: il metodo [a01] restituisce un tipo [String], che verrà inviato così com'è al client;
  • riga 6: la stringa restituita;

Eseguiamo l'applicazione come abbiamo già fatto diverse volte in precedenza, quindi utilizzando [Advanced Rest Client], richiediamo l'URL [/a01] con un GET [1-2]:

  • in [3], la risposta del server;
  • in [4], le intestazioni HTTP della risposta. Possiamo vedere che la codifica utilizzata è [ISO-8859-1]. Potremmo preferire la codifica UTF-8. Questo può essere configurato;
  • in [5], richiediamo lo stesso URL utilizzando il browser Chrome;

Aggiungiamo la seguente azione [/a02] all'[ActionsController] (a volte confonderemo l'URL con il metodo che lo gestisce, indicato come azione):


    // ----------------------- accented characters - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • Riga 2: L'attributo [produces="text/plain;charset=UTF-8"] indica che l'azione invia un flusso di testo con caratteri codificati in formato [UTF-8]. Questo formato consente specificatamente l'uso di caratteri accentati;

Per applicare questa nuova azione, è necessario riavviare l'applicazione:

 

Il risultato è il seguente:

  • in [1], vediamo il tipo di documento inviato dal server;
  • In [2-3], i caratteri accentati sono chiaramente visibili;

3.3. [/a03]: Restituisce un flusso XML

Aggiungiamo la seguente azione [/a03]:


    // ----------------------- text/xml ------------------------
    @RequestMapping(value = "/a03", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a03() {
        String greeting = "<greetings><greeting>Greetings from Spring Boot!</greeting></greetings>";
        return greeting;
}
  • Riga 2: L'attributo [produces="text/xml;charset=UTF-8"] indica che l'azione invia un flusso XML con caratteri codificati in formato [UTF-8];

La sua esecuzione produce il seguente risultato:

  • In [1], l'intestazione HTTP specifica che il documento inviato è in formato HTML;
  • in [2], il browser Chrome utilizza questa informazione per formattare il testo XML ricevuto;

Ricorda che con Chrome puoi visualizzare gli scambi HTTP tra il client e il server nella console degli sviluppatori (Ctrl-Shift-I):

Image

D'ora in poi, non faremo sistematicamente screenshot degli scambi HTTP tra il client e il server. A volte, ci limiteremo a citare il testo di questi scambi.

3.4. [/a04, /a05]: restituzione di un feed JSON

Aggiungiamo la seguente azione [/a04]:


    // ----------------------- produce from jSON ------------------------
    @RequestMapping(value = "/a04", method = RequestMethod.GET)
    public Map<String, Object> a04() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("1", "un");
        map.put("2", new int[] { 4, 5 });
        return map;
}
  • Riga 3: L'azione restituisce un tipo [Map], ovvero un dizionario. Ricordiamo che con un controller [@RestController], il risultato dell'azione è la risposta inviata al client. Poiché HTTP è un protocollo per lo scambio di righe di testo, la risposta del client deve essere serializzata in una stringa. Per farlo, Spring MVC utilizza vari convertitori [Object <---> string]. L'associazione di un oggetto specifico con un convertitore avviene tramite configurazione. In questo caso, l'autoconfigurazione di Spring Boot esaminerà le dipendenze del progetto:
 

Le dipendenze Jackson elencate sopra sono librerie per la serializzazione e la deserializzazione di oggetti in stringhe JSON. Spring Boot utilizzerà quindi queste librerie per serializzare e deserializzare gli oggetti restituiti dalle azioni. Un esempio di codice Java per la serializzazione e la deserializzazione di oggetti Java in JSON è disponibile nella Sezione 9.7.

Si noti alla riga 2 che non abbiamo specificato il tipo della risposta inviata. Vedremo quale sarà il tipo predefinito che verrà inviato.

I risultati in Chrome sono i seguenti [1-3]:

Aggiungiamo ora la seguente azione [/a05]:


    // ----------------------- produce from jSON - 2 ------------------------
    @RequestMapping(value = "/a05", method = RequestMethod.GET)
    public Personne a05() {
        return new Personne(1,"carole",45);
}

La classe [Person] è la seguente:

  

package istia.st.sprinmvc.models;
 
public class Personne {
 
    // identifier
    private Integer id;
    // name
    private String nom;
    // age
    private int age;
 
    // manufacturers
    public Personne() {
 
    }
 
    public Personne(String nom, int age) {
        this.nom = nom;
        this.age = age;
    }
 
    public Personne(Integer id, String nom, int age) {
        this(nom, age);
        this.id = id;
    }
 
    @Override
    public String toString() {
        return String.format("[id=%s, nom=%s,  age=%d]", id, nom, age);
    }
 
    // getters and setters
...
}

L'esecuzione produce i seguenti risultati:

  • in [1], il server indica che il documento che sta inviando è in formato JSON;
  • in [2], il documento JSON ricevuto;

3.5. [/a06]: restituisce uno stream vuoto

Aggiungiamo la seguente azione [/a06]:


    // ----------------------- render an empty stream ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • Alla riga 3, l'azione [/a06] non restituisce nulla. Spring MVC genererà quindi una risposta vuota al client;

L'esecuzione produce i seguenti risultati:

 

Sopra, l'attributo HTTP [Content-Length] nella risposta indica che il server sta inviando un documento vuoto.

3.6. [/a07, /a08, /a09]: tipo di dati con [Content-Type]

Aggiungiamo la seguente azione [/a07]:


    // ----------------------- text/html ------------------------
    @RequestMapping(value = "/a07", method = RequestMethod.GET, produces = "text/html;charset=UTF-8")
    public String a07() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • Riga 2: l'azione [/a07] restituisce un flusso HTML [text/html];
  • riga 4: una stringa HTML;

L'esecuzione produce i seguenti risultati:

  • in [1], vediamo che Chrome ha interpretato il tag HTML <h1>, che visualizza il suo contenuto in caratteri grandi;

Ora facciamo la stessa cosa con la seguente azione [/a08]:


    // ----------------------- result HTML in text/plain ------------------------
    @RequestMapping(value = "/a08", method = RequestMethod.GET, produces = "text/plain;charset=UTF-8")
    public String a08() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • Riga 2: La risposta dell'azione è di tipo [text/plain];

I risultati sono i seguenti:

  • In [1], Chrome non ha interpretato il tag HTML <h1> perché il server gli ha comunicato che stava inviando un flusso [text/plain] [2];

Proviamo qualcosa di simile con la seguente azione [/a09]:


    // ----------------------- result HTML in text/xml ------------------------
    @RequestMapping(value = "/a09", method = RequestMethod.GET, produces = "text/xml;charset=UTF-8")
    public String a09() {
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        return greeting;
}
  • Riga 2: inviamo un flusso [text/xml];

I risultati sono i seguenti:

  • In [1], Chrome non ha interpretato il tag HTML <h1> perché il server gli ha comunicato che stava inviando un flusso [text/xml] [2]. Ha quindi trattato il tag <h1> come un tag XML;

Questi esempi evidenziano l'importanza dell'intestazione HTTP [Content-Type] nella risposta del server. Il browser utilizza questa intestazione per determinare come interpretare il documento che riceve;

3.7. [/a10, /a11, /a12]: reindirizzamento del client

Creiamo un nuovo controller [RedirectController]:

 

Il codice per [RedirectController] sarà per ora il seguente:


package istia.st.springmvc.controllers;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
 
@Controller
public class RedirectController {
}
  • Riga 7: Utilizziamo l'annotazione [@Controller], il che significa che, per impostazione predefinita, il tipo [String] del risultato dell'azione ora si riferisce al nome di un'azione o di una vista;

Creiamo la seguente azione [/a10]:


    // ------------ bridge to third-party action -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • Riga 4: restituiamo 'a01' come risultato, che è il nome di un'azione. Questa azione invierà quindi la risposta al client;

Ecco un esempio:

  • In [2], abbiamo ricevuto lo stream dall'azione [/a01];
  • in [3], il browser visualizza l'URL dell'azione [/a10];

Ora creiamo la seguente azione [/a11]:


    // ------------ temporary 302 redirect to a third-party action -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Otteniamo i seguenti risultati:

  • Nei log di Chrome [1-2], vediamo due richieste, una a [/a11] e l'altra a [/a01];
  • in [3], il server risponde con un codice di stato [302], che indica al browser client di reindirizzarsi all'URL specificato dall'intestazione HTTP [Location:] [4]. Il codice di stato [302] è un codice di reindirizzamento temporaneo;

Il browser effettua quindi la seconda richiesta all'URL di reindirizzamento:

  • in [5], la seconda richiesta del client;
  • in [6], il browser del client visualizza l'URL della richiesta di reindirizzamento;

Potresti voler indicare un reindirizzamento permanente; in tal caso, devi inviare al client il seguente header HTTP:

HTTP/1.1 301 Moved Permanently 

il che significa che il reindirizzamento è permanente. Questa differenza tra un reindirizzamento temporaneo (302) e uno permanente (301) viene presa in considerazione da alcuni motori di ricerca.

Scriviamo l'azione [/a12] che eseguirà questo reindirizzamento permanente:


    // ------------ permanent 301 redirect to a third-party action----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • Riga 3: chiediamo a Spring MVC di iniettare l'oggetto [HttpServletResponse], che incapsula la risposta inviata al client;
  • riga 4: impostiamo lo [status] della risposta, l'intestazione HTTP [301]:
HTTP/1.1 301 Moved Permanently
  • riga 5: creiamo manualmente la seguente intestazione HTTP:
Location: /a01 

che è l'URL di reindirizzamento.

L'esecuzione produce i seguenti risultati:

Da questo esempio impareremo come:

  • generare lo stato della risposta HTTP;
  • includere un'intestazione HTTP nella risposta;

3.8. [/a13]: generare la risposta completa

È possibile controllare completamente la risposta, come mostra la seguente azione nella classe [ResponsesController]:

  

    // ----------------------- complete response generation ------------------------
    @RequestMapping(value = "/a13")
    public void a13(HttpServletResponse response) throws IOException {
        response.setStatus(666);
        response.addHeader("header1", "qq chose");
        response.addHeader("Content-Type", "text/html;charset=UTF-8");
        String greeting = "<h1>Greetings from Spring Boot!</h1>";
        response.getWriter().write(greeting);
}
  • Riga 3: Il risultato dell'azione è [void]. In questo caso, per inviare una risposta non vuota al client, è necessario utilizzare l'oggetto [HttpServletResponse response] fornito da Spring MVC;
  • riga 4: assegniamo alla risposta uno stato che non verrà riconosciuto dal client;
  • riga 5: aggiungiamo un'intestazione HTTP che non verrà riconosciuta dal client;
  • riga 6: aggiungiamo un'intestazione HTTP [Content-Type] per specificare il tipo di dati che stiamo inviando, in questo caso HTML;
  • Righe 7–8: il documento che segue le intestazioni HTTP nella risposta;

I risultati sono i seguenti:

  • in [1], riconosciamo gli elementi della nostra risposta;
  • in [2-3], vediamo che Chrome ha ignorato il fatto che:
    • lo stato HTTP della risposta non era uno stato HTTP riconosciuto,
    • che l'intestazione [header1] non era un'intestazione HTTP riconosciuta;

Se il client non è un browser ma un client programmatico, sei libero di utilizzare qualsiasi codice di stato e intestazione desideri.