Skip to content

3. Acties: het antwoord

Laten we eens kijken naar de architectuur van een Spring MVC-toepassing:

In dit hoofdstuk bekijken we het proces waarbij de aanvraag [1] naar de controller en de actie [2a] wordt geleid, die deze zullen verwerken; een mechanisme dat ‘routing’ wordt genoemd. Daarnaast bespreken we de verschillende reacties ([3]) die een actie naar de browser kan sturen. Dit kan iets anders zijn dan een V-weergave ([4b]).

3.1. Het nieuwe project

We maken een nieuw Spring-project MVC aan:

  • in [1-2] maken we een nieuw project op basis van Spring Boot;
  • in [3], de naam van het Maven-project;
  • in [4], de Maven-groep waarin het resultaat van de compilatie van het project wordt geplaatst;
  • in [5], de naam die aan het compilatieresultaat wordt gegeven;
  • in [6], een beschrijving van het project;
  • in [7], het pakket waarin de uitvoerbare klasse van het project wordt geplaatst;
  • in [8], de aard van het project. Het is een webproject met Thymeleaf-weergaven. Hier zien we alle kant-en-klare Maven-afhankelijkheden die door het Spring Boot-project worden aangeboden;
  • in [9] geven we aan dat het resultaat van de Maven-build in een jar-archief zal worden verpakt en niet in een war-archief. Het project zal dan gebruikmaken van een ingebouwde Tomcat-server die in de afhankelijkheden zal staan;
  • in [10] gaan we verder met de wizard;
  • in [11] wordt de projectmap opgegeven;
  • in [12], het gegenereerde project;
  • in [14-15] hernoem je het pakket [istia.st.springmvc];
  • in [16], de nieuwe naam van het pakket;
  • in [17], het nieuwe project;

We maken nu een nieuwe klasse aan;

  • in [1-3] maken we een nieuwe klasse aan;
  • in [5] geven we het een naam en in [4] specificeren we het pakket;
  • in [6] het nieuwe project;

De klasse ziet er voorlopig als volgt uit:


package istia.st.springmvc;

public class ActionsController {

}

We passen deze code als volgt aan:


package istia.st.springmvc;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class ActionsController {

}
  • regel 6: de annotatie [@RestController] geeft twee dingen aan:
    • dat de klasse [ActionsController], die op deze manier is geannoteerd, een Spring-controller MVC is en dus acties bevat die de URL van klanten verwerken;
    • dat het resultaat van deze acties naar de client wordt verzonden;

De andere annotatie [@Controller] die we zijn tegengekomen, is anders: de acties van een controller met deze annotatie leveren de naam op van de weergave die moet worden getoond. Het is dan de combinatie van deze weergave en het model dat door de actie voor deze weergave is opgebouwd, die het antwoord levert dat naar de client wordt verzonden.

De wijziging in de structuur van ons project brengt een wijziging in de configuratie van ons project met zich mee:

  

De klasse [Application] verandert als volgt:


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);
    }
}
  • regel 9: de annotatie [ComponentScan] accepteert als parameter een array met pakketnamen waarin Spring Boot naar Spring-componenten moet zoeken. Hier voegen we het pakket [istia.st.springmvc.controllers] toe aan deze array, zodat de controller die is geannoteerd met [@RestController] wordt gevonden;

We gaan verschillende acties in de controller bouwen om de belangrijkste kenmerken ervan te illustreren. We zullen ons eerst richten op de verschillende soorten reacties die een actie kan geven in een applicatie zonder weergaven.

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

Onze eerste actie ziet er als volgt uit:


@RestController
public class ActionsController {
    // ----------------------- hello world ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • regel 4: de annotatie [RequestMapping] specificeert de verzoek dat door de geannoteerde actie wordt verwerkt:
    • het attribuut [value] is de verwerkte URL,
    • het attribuut [method] bepaalt de geaccepteerde methode;

De methode [a01] verwerkt dus de aanvraag HTTP [GET /a01].

  • regel 5: de methode [a01] retourneert een type [String] dat ongewijzigd naar de client wordt verzonden;
  • regel 6: de geretourneerde tekenreeks;

Laten we de applicatie starten zoals we dat al meerdere keren hebben gedaan, en vervolgens met de client [Advanced Rest Client], vragen we de URL [/a01] op met een GET [1-2]:

  • in [3], het antwoord van de server;
  • in [4], de headers HTTP van het antwoord. We zien dat de gebruikte codering [ISO-8859-1] is. Men kan de voorkeur geven aan de codering UTF-8. Dit kan worden geconfigureerd;
  • in [5] vragen we dezelfde URL op met de Chrome-browser;

We voegen de volgende actie [/a02] toe aan de controller [ActionsController] (waardoor URL en de methode die deze verwerkt onder de naam van de actie soms door elkaar worden gehaald):


    // ----------------------- tekens met accenten - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • regel 2: het attribuut [produces="text/plain;charset=UTF-8"] geeft aan dat de actie een tekststroom verstuurt met tekens die zijn gecodeerd in het formaat [UTF-8]. Dit formaat maakt met name het gebruik van tekens met accenten mogelijk;

Om deze nieuwe actie te activeren, moeten we de applicatie opnieuw starten:

 

Het resultaat is als volgt:

  • in [1] zien we het type document dat door de server is verzonden;
  • in [2-3] zijn de accenten correct weergegeven;

3.3. [/a03]: een stream weergeven XML

We voegen de volgende actie [/a03] toe:


    // ----------------------- 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;
}
  • regel 2: het attribuut [produces="text/xml;charset=UTF-8"] geeft aan dat de actie een stream XML verstuurt met tekens die zijn gecodeerd in het formaat [UTF-8];

De uitvoering levert het volgende op:

  • in [1] geeft de header HTTP aan dat het verzonden document van het type HTML is;
  • in [2] gebruikt de Chrome-browser deze informatie om de ontvangen tekst XML op te maken;

Ter herinnering: in Chrome heb je via het ontwikkelaarsvenster (Ctrl-Shift-I) toegang tot de uitwisselingen tussen de client en de server:

Image

Voortaan zullen we niet meer systematisch schermafbeeldingen maken van de uitwisselingen tussen de client en de server. Soms zullen we volstaan met het vermelden van de tekst van deze uitwisselingen.

3.4. [/a04, /a05]: een stream jSON weergeven

We voegen de volgende actie [/a04] toe:


    // ----------------------- jSON genereren ------------------------
    @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;
}
  • regel 3: de actie retourneert een type [Map], een woordenboek. We herinneren ons dat bij een controller van het type [@RestController] het resultaat van de actie het antwoord is dat naar de client wordt verzonden. Aangezien het protocol HTTP een protocol is voor de uitwisseling van tekstregels, moet het antwoord van de client worden geserialiseerd tot een tekenreeks. Hiervoor maakt Spring MVC gebruik van diverse [Objet <---> chaîne de caractères]-converters. De koppeling van een specifiek object aan een converter gebeurt via configuratie. Hier zal de automatische configuratie van Spring Boot de afhankelijkheden van het project controleren:
 

De bovenstaande Jackson-afhankelijkheden zijn bibliotheken voor het serialiseren en deserialiseren van objecten naar en vanuit jSON-tekenreeksen. Spring Boot zal deze bibliotheken vervolgens gebruiken om de objecten die door de acties worden geretourneerd te serialiseren en te deserialiseren. In paragraaf 9.7 vindt u een voorbeeld van Java-code voor het serialiseren en deserialiseren van Java-objecten in jSON.

Merk op dat we in regel 2 het type van het verzonden antwoord niet hebben opgegeven. We zullen zien welk standaardtype er wordt verzonden.

De resultaten in Chrome [1-3] zijn als volgt:

Laten we nu de volgende actie [/a05] toevoegen:


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

De klasse [Personne] is als volgt:

  

package istia.st.sprinmvc.models;

public class Personne {

    // identificatie
    private Integer id;
    // naam
    private String nom;
    // leeftijd
    private int age;

    // makers
    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 en setters
...
}

De uitvoering levert de volgende resultaten op:

  • in [1] geeft de server aan dat het document dat hij verstuurt jSON is;
  • in [2] is het ontvangen document jSON;

3.5. [/a06]: een lege stream retourneren

We voegen de volgende actie [/a06] toe:


    // ----------------------- een lege stream retourneren ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • regel 3: de actie [/a06] levert niets op. Spring MVC genereert dan een leeg antwoord naar de client;

De uitvoering levert de volgende resultaten op:

 

Hierboven geeft het attribuut HTTP [Content-Length] in het antwoord aan dat de server een leeg document verstuurt.

3.6. [/a07, /a08, /a09]: aard van de stream met [Content-Type]

We voegen de volgende actie [/a07] toe:


    // ----------------------- 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;
}
  • regel 2: de actie [/a07] levert een stream op van HTML [text/html];
  • regel 4: een tekenreeks HTML;

De uitvoering levert de volgende resultaten op:

  • in [1] zien we dat Chrome de tag HTML <h1> heeft geïnterpreteerd, waardoor de inhoud in grote letters wordt weergegeven;

Laten we nu hetzelfde doen met de volgende actie [/a08]:


    // ----------------------- resultaat 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;
}
  • regel 2: het antwoord van de actie is van het type [text/plain];

De resultaten zijn als volgt:

  • in [1] heeft Chrome de tag HTML <h1> niet geïnterpreteerd, omdat de server aangaf dat hij een stream [text/plain] [2] verstuurde;

Laten we iets soortgelijks opnieuw proberen met de volgende actie [/a09]:


    // ----------------------- resultaat van 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;
}
  • regel 2: we sturen een stream van het type [text/xml];

De resultaten zijn als volgt:

  • in [1] heeft Chrome de tag HTML <h1> niet geïnterpreteerd, omdat de server hem had meegedeeld dat hij een stream van het type [text/xml] [2] verstuurde. Vervolgens heeft Chrome de tag <h1> behandeld als een XML-tag;

Uit deze voorbeelden blijkt het belang van de header HTTP [Content-Type] in het antwoord van de server. De browser gebruikt deze header om te weten hoe hij het ontvangen document moet interpreteren;

3.7. [/a10, /a11, /a12]: de client omleiden

We maken een nieuwe controller [RedirectController]:

 

De code van [RedirectCntroller] ziet er voorlopig als volgt uit:


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 {
}
  • regel 7: we gebruiken de annotatie [@Controller], waardoor het type [String] van het resultaat van de acties voortaan standaard verwijst naar de naam van een actie of een weergave;

We maken de volgende actie [/a10] aan:


    // ------------ doorverwijzing naar een actie van een derde partij -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • regel 4: we geven als resultaat 'a01' terug, wat de naam van een actie is. Deze actie zal vervolgens het antwoord naar de client sturen;

Hier volgt een voorbeeld:

  • in [2] hebben we de gegevensstroom van de actie [/a01] ontvangen;
  • in [3] geeft de browser de URL van de actie [/a10] weer;

We maken nu de volgende actie [/a11] aan:


    // ------------ tijdelijke 302-omleiding naar een actie van een derde partij -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

We krijgen de volgende resultaten:

  • in de logbestanden van Chrome [1-2] zien we twee verzoeken, één naar [/a11] en één naar [/a01];
  • in [3] antwoordt de server met een code [302] die de clientbrowser vraagt om door te verwijzen naar deURL, zoals aangegeven door de header HTTP [Location:] [4]. De code [302] is een tijdelijke omleidingscode;

De browser verstuurt vervolgens het tweede verzoek naar de omleidingscode URL:

  • naar [5], het tweede verzoek van de client;
  • naar [6], de clientbrowser geeft de omleidingscode URL weer;

Het kan wenselijk zijn om een permanente omleiding aan te geven; in dat geval moet de volgende header HTTP naar de client worden verzonden:

HTTP/1.1 301 Moved Permanently 

wat betekent dat de omleiding permanent is. Dit verschil tussen tijdelijke (302) en permanente (301) omleiding wordt door sommige zoekmachines in aanmerking genomen.

We schrijven de actie [/a12] die deze permanente omleiding zal uitvoeren:


    // ------------ permanente omleiding 301 naar een actie van een derde partij----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • regel 3: we vragen Spring MVC om het object [HttpServletResponse] in te voegen, dat het naar de klant verzonden antwoord omvat;
  • regel 4: we stellen de [status] van het antwoord in, de [301] van de header HTTP:
HTTP/1.1 301 Moved Permanently
  • regel 5: we maken handmatig de volgende header HTTP aan:
Location: /a01 

dit is de omleidingsheader URL.

De uitvoering levert de volgende resultaten op:

Uit dit voorbeeld leren we hoe we:

  • de responsstatus HTTP te genereren;
  • een header HTTP in het antwoord op te nemen;

3.8. [/a13]: het genereren van het volledige antwoord

Het is mogelijk om het antwoord volledig te beheren, zoals blijkt uit de volgende actie van de klasse [ResponsesController]:

  

    // ----------------------- volledig genereren van het antwoord ------------------------
    @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);
}
  • regel 3: het resultaat van de actie is [void]. Om in dit geval een niet-leeg antwoord naar de client te sturen, moet het door Spring geleverde object [HttpServletResponse response] worden gebruikt MVC;
  • regel 4: we geven het antwoord een status die niet door de client zal worden herkend;
  • regel 5: er wordt een header HTTP toegevoegd die niet door de client wordt herkend;
  • regel 6: er wordt een header HTTP [Content-Type] toegevoegd om het type stream te specificeren dat wordt verzonden, in dit geval HTML;
  • regels 7-8: het document dat in het antwoord op de headers HTTP volgt;

De resultaten zijn als volgt:

  • in [1] herkennen we de elementen van ons antwoord;
  • in [2-3] zien we dat Chrome het volgende heeft genegeerd:
    • de status HTTP van het antwoord geen herkende status HTTP was,
    • dat de header [header1] geen herkenbare header HTTP was;

Als de client geen browser is maar een geprogrammeerde client, staat het vrij om de gewenste statussen en headers te gebruiken.