Skip to content

3. Aktionen: die Antwort

Betrachten wir die Architektur einer Spring-Anwendung MVC:

In diesem Kapitel betrachten wir den Prozess, der die Anfrage [1] zum Controller und zur Aktion [2a] leitet, die sie verarbeiten werden – ein Mechanismus, der als Routing bezeichnet wird. Außerdem stellen wir die verschiedenen Antworten [3] vor, die eine Aktion an den Browser zurückgeben kann. Dabei kann es sich um etwas anderes als eine V-Ansicht [4b] handeln.

3.1. Das neue Projekt

Wir erstellen ein neues Spring-Projekt MVC:

  • In [1-2] erstellen wir ein neues Projekt auf Basis von Spring Boot;
  • „[3]“ als Namen des Maven-Projekts;
  • in [4] die Maven-Gruppe, in der das Ergebnis der Projektkompilierung abgelegt wird;
  • in [5] der Name des Kompilierungsprodukts;
  • in [6] eine Beschreibung des Projekts;
  • in [7] das Paket, in dem die ausführbare Klasse des Projekts abgelegt wird;
  • in [8] die Art des Projekts. Es handelt sich um ein Webprojekt mit Thymeleaf-Views. Hier sind alle gebrauchsfertigen Maven-Abhängigkeiten zu sehen, die das Spring-Boot-Projekt bereitstellt;
  • in [9] wird angegeben, dass das Ergebnis des Maven-Builds in ein JAR-Archiv und nicht in ein WAR-Archiv gepackt wird. Das Projekt wird dann einen eingebetteten Tomcat-Server verwenden, der in seinen Abhängigkeiten enthalten ist;
  • In [10] fahren wir mit dem Assistenten fort;
  • In [11] wird der Projektordner angegeben;
  • in [12] das generierte Projekt;
  • in [14-15] wird das Paket [istia.st.springmvc] umbenannt;
  • in [16], der neue Name des Pakets;
  • in [17], das neue Projekt;

Wir erstellen nun eine neue Klasse;

  • in [1-3] erstellen wir eine neue Klasse;
  • in [5] weisen wir ihr einen Namen zu und in [4] legen wir ihr Paket fest;
  • in [6] das neue Projekt;

Die Klasse sieht derzeit wie folgt aus:


package istia.st.springmvc;

public class ActionsController {

}

Wir entwickeln diesen Code wie folgt weiter:


package istia.st.springmvc;

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

@RestController
public class ActionsController {

}
  • Zeile 6: Die Annotation [@RestController] gibt zwei Dinge an:
    • dass die so annotierte Klasse [ActionsController] ein Spring-Controller MVC ist und somit Aktionen enthält, die URL von Clients verarbeiten;
    • dass das Ergebnis dieser Aktionen an den Client gesendet wird;

Die andere Annotation [@Controller], auf die wir gestoßen sind, ist anders: Die Aktionen eines so annotierten Controllers geben den Namen der Ansicht zurück, die angezeigt werden soll. Es ist dann die Kombination aus dieser Ansicht und dem von der Aktion für diese Ansicht erstellten Modell, die die an den Client gesendete Antwort liefert.

Die Änderung der Struktur unseres Projekts führt zu einer Änderung der Projektkonfiguration:

  

Die Klasse [Application] entwickelt sich wie folgt weiter:


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);
    }
}
  • Zeile 9: Die Annotation [ComponentScan] akzeptiert als Parameter ein Array mit Paketnamen, in denen Spring Boot nach Spring-Komponenten suchen soll. Hier fügen wir diesem Array das Paket [istia.st.springmvc.controllers] hinzu, damit der mit [@RestController] annotierte Controller gefunden wird;

Wir werden verschiedene Aktionen im Controller erstellen, um deren Hauptmerkmale zu veranschaulichen. Zunächst werden wir uns mit den verschiedenen möglichen Antworttypen einer Aktion in einer Anwendung ohne Views befassen.

3.2. [/a01, /a02] – Hello World

Unsere erste Aktion lautet wie folgt:


@RestController
public class ActionsController {
    // ----------------------- hello world ------------------------
    @RequestMapping(value = "/a01", method = RequestMethod.GET)
    public String a01() {
        return "Greetings from Spring Boot!";
    }
}
  • Zeile 4: Die Annotation [RequestMapping] qualifiziert die von der annotierten Aktion verarbeitete Anfrage:
    • Das Attribut [value] ist die verarbeitete URL,
    • das Attribut [method] legt die akzeptierte Methode fest;

Somit verarbeitet die Methode [a01] die Anfrage HTTP [GET /a01].

  • Zeile 5: Die Methode [a01] gibt einen Typ [String] zurück, der unverändert an den Client gesendet wird;
  • Zeile 6: Die zurückgegebene Zeichenkette;

Starten wir die Anwendung wie bereits mehrfach zuvor und fordern wir anschließend mit dem Client [Advanced Rest Client] fordern wir den URL [/a01] mit einem GET [1-2] an:

  • in [3], die Antwort des Servers;
  • in [4], die Header HTTP der Antwort. Man sieht, dass die verwendete Kodierung [ISO-8859-1] ist. Man kann die Kodierung UTF-8 bevorzugen. Dies lässt sich konfigurieren;
  • mit [5] rufen wir dieselbe Seite URL mit dem Chrome-Browser auf;

Wir fügen die folgende Aktion [/a02] in den Controller [ActionsController] ein (dadurch kann es manchmal zu Verwechslungen zwischen URL und der Methode kommen, die sie unter dem Namen der Aktion verarbeitet):


    // ----------------------- Zeichen mit Akzenten - UTF8 ------------------------
    @RequestMapping(value = "/a02", method = RequestMethod.GET, produces="text/plain;charset=UTF-8")
    public String a02() {
        return "caractères accentués : éèàôûî";
}
  • Zeile 2: Das Attribut [produces="text/plain;charset=UTF-8"] gibt an, dass die Aktion einen Textstrom mit Zeichen sendet, die im Format [UTF-8] kodiert sind. Dieses Format ermöglicht insbesondere die Verwendung von Zeichen mit Akzenten;

Um diese neue Aktion zu berücksichtigen, müssen wir die Anwendung neu starten:

 

Das Ergebnis sieht wie folgt aus:

  • In [1] erkennt man die Art des vom Server gesendeten Dokuments;
  • in [2-3] sind die Zeichen mit Akzenten korrekt dargestellt;

3.3. [/a03]: einen Datenstrom in XML umwandeln

Wir fügen die folgende Aktion [/a03] hinzu:


    // ----------------------- 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;
}
  • Zeile 2: Das Attribut [produces="text/xml;charset=UTF-8"] gibt an, dass die Aktion einen Datenstrom XML mit Zeichen sendet, die im Format [UTF-8] kodiert sind;

Die Ausführung ergibt Folgendes:

  • in [1] gibt der Header HTTP an, dass das gesendete Dokument im Format HTML vorliegt;
  • in [2] nutzt der Chrome-Browser diese Information, um den empfangenen Text XML zu formatieren;

Zur Erinnerung: In Chrome hat man im Entwicklerfenster (Strg-Umschalt-I) Zugriff auf den Datenaustausch zwischen Client und Server:

Image

Künftig werden nicht mehr systematisch Screenshots der Datenaustausche HTTP zwischen dem Client und dem Server erstellt. Manchmal wird es ausreichen, den Text dieser Datenaustausche anzugeben.

3.4. [/a04, /a05]: Einen Datenstrom jSON zurückgeben

Wir fügen die folgende Aktion [/a04] hinzu:


    // ----------------------- jSON erzeugen ------------------------
    @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;
}
  • Zeile 3: Die Aktion gibt einen Typ [Map] zurück, ein Wörterbuch. Wir erinnern uns, dass bei einem Controller vom Typ [@RestController] das Ergebnis der Aktion die an den Client gesendete Antwort ist. Da es sich bei dem Protokoll HTTP um ein Protokoll zum Austausch von Textzeilen handelt, muss die Antwort des Clients in eine Zeichenkette serialisiert werden. Dazu verwendet Spring MVC verschiedene Konverter vom Typ [Objet <---> chaîne de caractères]. Die Zuordnung eines bestimmten Objekts zu einem Konverter erfolgt über die Konfiguration. Hier überprüft die Autokonfiguration von Spring Boot die Abhängigkeiten des Projekts:
 

Die oben genannten Jackson-Abhängigkeiten sind Bibliotheken zur Serialisierung und Deserialisierung von Objekten in Zeichenfolgen. Spring Boot verwendet diese Bibliotheken dann, um die von den Aktionen zurückgegebenen Objekte zu serialisieren bzw. zu deserialisieren. Ein Beispiel für Java-Code zur Serialisierung und Deserialisierung von Java-Objekten in jSON finden Sie in Abschnitt 9.7.

Beachten Sie in Zeile 2, dass wir den Typ der gesendeten Antwort nicht angegeben haben. Wir werden sehen, welcher Standardtyp gesendet wird.

Die Ergebnisse in Chrome lauten wie folgt: [1-3]:

Fügen wir nun die folgende Aktion [/a05] hinzu:


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

Die Klasse [Personne] lautet wie folgt:

  

package istia.st.sprinmvc.models;

public class Personne {

    // ID
    private Integer id;
    // Name
    private String nom;
    // Alter
    private int age;

    // Konstruktoren
    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);
    }

    // Getter und Setter
...
}

Die Ausführung liefert folgende Ergebnisse:

  • Bei [1] gibt der Server an, dass das von ihm gesendete Dokument jSON ist;
  • bei [2] das empfangene Dokument jSON;

3.5. [/a06]: einen leeren Datenstrom zurückgeben

Wir fügen die folgende Aktion [/a06] hinzu:


    // ----------------------- einen leeren Stream zurückgeben ------------------------
    @RequestMapping(value = "/a06")
    public void a06() {
}
  • Zeile 3: Die Aktion [/a06] gibt nichts zurück. Spring MVC generiert daraufhin eine leere Antwort an den Client;

Die Ausführung liefert folgende Ergebnisse:

 

Oben zeigt das Attribut HTTP [Content-Length] in der Antwort an, dass der Server ein leeres Dokument sendet.

3.6. [/a07, /a08, /a09]: Art des Datenstroms mit [Content-Type]

Wir fügen die folgende Aktion [/a07] hinzu:


    // ----------------------- 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;
}
  • Zeile 2: Die Aktion [/a07] gibt einen Datenstrom HTML [text/html] zurück;
  • Zeile 4: eine Zeichenkette HTML;

Die Ausführung liefert folgende Ergebnisse:

  • In [1] sieht man, dass Chrome das Tag HTML <h1> interpretiert hat, das seinen Inhalt in großer Schrift anzeigt;

Machen wir nun dasselbe mit der folgenden Aktion [/a08]:


    // ----------------------- Ergebnis von HTML als „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;
}
  • Zeile 2: Die Antwort der Aktion ist vom Typ [text/plain];

Die Ergebnisse lauten wie folgt:

  • Bei [1] hat Chrome das Tag HTML <h1> nicht interpretiert, da der Server ihm mitgeteilt hat, dass er einen Datenstrom vom Typ [text/plain] [2] sendet;

Versuchen wir es noch einmal mit einem ähnlichen Beispiel und der folgenden Aktion [/a09]:


    // ----------------------- Ergebnis von HTML als „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;
}
  • Zeile 2: Wir senden einen Datenstrom vom Typ [text/xml];

Die Ergebnisse lauten wie folgt:

  • Bei [1] hat Chrome das Tag HTML <h1> nicht interpretiert, da der Server ihm mitgeteilt hat, dass er einen Datenstrom vom Typ [text/xml] [2] sendet. Daraufhin hat er das Tag <h1> als ein XML-Tag behandelt;

Aus diesen Beispielen lässt sich die Bedeutung des Headers HTTP [Content-Type] in der Antwort des Servers ableiten. Der Browser nutzt diesen Header, um zu wissen, wie er das empfangene Dokument interpretieren soll;

3.7. [/a10, /a11, /a12]: Umleitung des Clients

Wir erstellen einen neuen Controller [RedirectController]:

 

Der Code für [RedirectCntroller] lautet vorerst wie folgt:


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 {
}
  • Zeile 7: Wir verwenden die Anmerkung [@Controller], wodurch der Typ [String] des Aktionsergebnisses nun standardmäßig den Namen einer Aktion oder einer Ansicht bezeichnet;

Wir erstellen die folgende Aktion [/a10]:


    // ------------ Weiterleitung zu einer Aktion eines Drittanbieters -----------------------
    @RequestMapping(value = "/a10", method = RequestMethod.GET)
    public String a10() {
        return "a01";
}
  • Zeile 4: Als Ergebnis wird „a01“ zurückgegeben, was der Name einer Aktion ist. Diese Aktion wird dann die Antwort an den Client senden;

Hier ein Beispiel:

  • In [2] haben wir den Datenfluss der Aktion [/a01] erhalten;
  • Bei „[3]“ zeigt der Browser „URL“ der Aktion „[/a10]“ an;

Wir erstellen nun die folgende Aktion [/a11]:


    // ------------ Temporäre 302-Weiterleitung zu einer Aktion eines Drittanbieters -----------------------
    @RequestMapping(value = "/a11", method = RequestMethod.GET)
    public String a11() {
        return "redirect:/a01";
}

Wir erhalten folgende Ergebnisse:

  • In den Chrome-Protokollen zu [1-2] sind zwei Anfragen zu sehen, eine an [/a11], die andere an [/a01];
  • bei [3] antwortet der Server mit einem Code [302], der den Client-Browser auffordert, sich zu derURL, der durch den Header HTTP [Location:] [4] angegeben wird. Der Code [302] ist ein temporärer Weiterleitungscode;

Der Browser sendet daraufhin die zweite Anfrage an die Weiterleitungs-URL URL:

  • in [5], die zweite Anfrage des Kunden;
  • in [6] zeigt der Browser des Kunden die Antwort URL der Weiterleitungsanfrage an;

Möglicherweise möchte man eine permanente Weiterleitung angeben; in diesem Fall muss der folgende Header HTTP an den Client gesendet werden:

HTTP/1.1 301 Moved Permanently 

was bedeutet, dass die Weiterleitung dauerhaft ist. Dieser Unterschied zwischen temporärer (302) und dauerhafter (301) Weiterleitung wird von einigen Suchmaschinen berücksichtigt.

Wir schreiben die Aktion [/a12], die diese permanente Weiterleitung ausführt:


    // ------------ Permanente Weiterleitung (301) zu einer Aktion eines Drittanbieters -----------------
    @RequestMapping(value = "/a12", method = RequestMethod.GET)
    public void a12(HttpServletResponse response) {
        response.setStatus(301);
        response.addHeader("Location", "/a01");
}
  • Zeile 3: Wir weisen Spring MVC an, das Objekt [HttpServletResponse] einzufügen, das die an den Client gesendete Antwort kapselt;
  • Zeile 4: Wir legen den [status] der Antwort, den [301] des Headers und den HTTP fest:
HTTP/1.1 301 Moved Permanently
  • Zeile 5: Der folgende Header „HTTP“ wird manuell erstellt:
Location: /a01 

Dies ist der Weiterleitungs-Header URL.

Die Ausführung liefert folgende Ergebnisse:

Aus diesem Beispiel lässt sich ableiten, wie man:

  • den Antwortstatus HTTP zu generieren;
  • einen Header HTTP in die Antwort einzufügen;

3.8. [/a13]: die vollständige Antwort generieren

Es ist möglich, die Antwort vollständig zu steuern, wie die folgende Aktion der Klasse [ResponsesController] zeigt:

  

    // ----------------------- vollständige Generierung der Antwort ------------------------
    @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);
}
  • Zeile 3: Das Ergebnis der Aktion ist [void]. Um in diesem Fall eine nicht leere Antwort an den Client zu senden, muss das von Spring bereitgestellte Objekt [HttpServletResponse response] verwendet werden;
  • Zeile 4: Der Antwort wird ein Status zugewiesen, der vom Client nicht erkannt wird;
  • Zeile 5: Es wird ein Header HTTP hinzugefügt, der vom Client nicht erkannt wird;
  • Zeile 6: Es wird ein Header „HTTP [Content-Type]“ hinzugefügt, um den Typ des zu sendenden Datenstroms anzugeben, in diesem Fall „HTML“;
  • Zeilen 7–8: Das Dokument, das in der Antwort auf die Header „HTTP“ folgt;

Die Ergebnisse lauten wie folgt:

  • In „[1]“ erkennt man die Elemente unserer Antwort;
  • In [2-3] sieht man, dass Chrome folgende Tatsache ignoriert hat:
    • der Status HTTP der Antwort kein erkannter Status HTTP war,
    • dass der Header [header1] kein erkannter Header HTTP war;

Wenn es sich bei dem Client nicht um einen Browser, sondern um einen programmierten Client handelt, kann man beliebige Status und Header verwenden.