REST-Webservice mit Spring Boot

Im Post „Spring Boot Tutorial für Einsteiger“ haben wir gemeinsam eine kleine aber feine Spring Boot Applikation erstellt. Auch den Betrieb haben wir angesprochen. Leider fehlte unserer Beispieapplikation eine entscheidende Kleinigkeit: Sie hatte bisher keinerlei Funktionalität ;-) Darum kümmern wir uns heute!
Ein klassischer Anwendungsfall für den Einsatz von Spring Boot sind Microservices oder Self-Contained-Systems. Ohne an dieser Stelle die, teilweise mit religiösem Eifer geführte, Grundsatzdiskussion aufnehmen zu wollen, spreche ich einfach von Microservices. Diese sind für mich Services (oft mit einer REST-basierten Schnittstelle), die einen Bounded Context im Sinne des Domain-Driven-Designs bedienen.
Apropos REST und religiöser Eifer: Auch hier bin ich für den Einstieg gnädig und lasse eine HTTP basierte Schnittstelle, die Daten im JSON Format austauscht als REST gelten. Alternativ bezeichne ich es einfach als Webservice. Ich einem späteren Post können wir die Begrifflichkeiten gerne noch einmal genauer sezieren…
Einfacher Webservice
Entweder erstellen wir uns ein neues Projekt mit dem Spring Initializr oder wir recyceln die Applikation aus dem Post „Spring Boot Tutorial für Einsteiger“.
Um einen einfachen REST Service schreiben zu können, muss ich Spring Boot noch weitere Abhängigkeiten mitgeben. Wir erinnern uns: im Einstiegsbeispiel stoppte die Applikation sofort nach dem Start wieder. Ein Webservice soll aber nach dem Starten natürlich weiterlaufen und auf Anfragen lauschen.
Die Abhängigkeit, die wir heute benötigen ist spring-boot-starter-web aus der Gruppe org.springframework. Unklar? Kein Problem: Einfach die Datei pom.xml im Projektverzeichnis suchen und folgende Zeilen oberhalb von </dependencies> einfügen:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Leserinnen und Lesern mit Maven-Erfahrung fällt auf, dass keine Versionsnummer der Abhängigkeit angegeben ist. Diese wird geerbt, da die gesamte POM Datei eine Spring Boot POM als Parent angegeben hat. Dadurch werden die Versionen kompatibel gehalten und wir müssen nicht händisch alle Abhängigkeiten aktualisieren.
Aber was passiert jetzt? Ehrlich gesagt noch nicht so viel… Auch nach einem Start der Applikation per Kommandozeile mittels
./mvnw package
bzw. unter Windows
mvnw.cmd package
stoppt die Ausführung nach dem erfolgreichen Hochfahren. Wir müssen noch Komponenten hinzufügen, die auf die eingehenden Anfragen lauschen.
Der erste REST Controller
Diese Komponenten nennen sich im klassischen Spring Boot Jargon „Controller“. Man kann sie natürlich auch anders benennen, ich empfehle jedoch, Euch an die gängige Nomenklatur zu halten. So gelingt die Umsetzung von Best Practices und die Orientierung in Beispielprojekten deutlich besser.
Als Beispiel bemühen wir die gute alte TODO-Liste. Wir legen also eine Datei mit dem Namen TaskController.java und folgenden Inhalt unter src/main/java/de/mischokacademy/springbooteinstieg/controller an:
package de.mischok.academy.springbooteinstieg.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collections;
import java.util.List;
@Controller("tasks")
public class TaskController {
@GetMapping
public ResponseEntity<List<Task>> getTasks() {
Task task = new Task(1L, "Paint fence");
List<Task> taskList = Collections.singletonList(task);
return ResponseEntity.ok(taskList);
}
private static class Task {
private final long id;
private final String title;
public Task(long id, String title) {
this.id = id;
this.title = title;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
}
}
Bevor wir uns den Code genauer anschauen, machen wir zunächst einen Testlauf. Nach dem Start des Projektes stellen wir wohlwollend fest, dass die Applikation sich nun nicht mehr direkt nach dem Hochfahren beendet. Sie scheint also erfolgreich zu lauschen… Um zu verifizieren, dass wir einen funktionsfähigen Webservice und nicht wieder nur eine leere Spring Boot Applikation haben, nutzen wir einen beliebigen Browser. Darin navigieren wir auf die Adresse
http://localhost:8080/tasks
Die Anzeige sollte nun so ähnlich aussehen:

Hurra, es hat geklappt! In der Anzeige der Raw Daten zeigt sich, dass wir wirklich einen (rudimentären, aber funktionsfähigen) REST Service erstellt haben!
Wer lauscht?
Schauen wir uns den Code Stück für Stück an. Zunächst fällt auf, dass auf Klassenebene zwei Annotationen vergeben wurden:
@Controller
@RequestMapping("tasks")
Für Einsteiger beinhaltet Spring Boot viel Magie – die im wesentlichen in den Annotationen steckt. Wie helfen uns also diese beiden dabei, unseren Webservice umzusetzen?
Die Annotation @Controller markiert die Klasse als sogenannte Spring Bean. Für unseren Fall des REST Services ist das nicht weiter relevant, diese kleine Zeile hat jedoch große Auswirkungen. Im Endeffekt handelt es sich um ein Alias der Annotation @Component das signalisiert, dass nicht eine beliebige Spring Bean vorliegt, sondern ein Controller, der als Endpunkt eines Webservices dient.
Oben haben wir als Anforderung an unseren Webservice definiert, dass er auf Anfragen lauschen soll. Dafür sorgt, zumindest teilweise, die Annotation @RequestMapping. Mit dem Parameter „tasks“ wird definiert, dass der der Controller auf eigehende HTTP Anfragen unterhalb des Pfades tasks reagieren soll.
Wer reagiert?
So weit so gut. Aber ein REST Service lebt davon, dass er eingehende Anfragen bearbeitet und Antworten verschickt. Wie wir oben ausprobiert haben, funktioniert ein GET Aufruf (oben via Browser geschickt) und liefert eine zumindest einelementige Liste von Tasks zurück.
Verantwortlich dafür ist die Methode getTasks:
@GetMapping
public ResponseEntity<List<Task>> getTasks() {
Task task = new Task(1L, "Paint fence");
List<Task> taskList = Collections.singletonList(task);
return ResponseEntity.ok(taskList);
}
Auch hier gibt es wieder eine Annotation zu bestaunen: @GetMapping. Wenig überraschend registriert man damit die annotierte Methode für GET Aufrufe. Da der gesamte Controller auf den Pfad /tasks lauscht, werden alle eingehenden Anfragen auf GET /tasks auf diese Java-Methode umgeleitet.
Für das erste Beispiel wird jetzt einfach ein statischer Task instanziiert und in eine einelementige Liste verpackt. Wirklich spannend ist erst wieder die letzte Zeile mit der Rückgabe. Hier nutzen wir die Klasse ResponseEntity aus dem Spring Framework. Zur Erinnerung: Wir wollen einen REST Service schreiben, also einen Webservice, der per HTTP kommuniziert und JSON austauscht. Diese Abstraktion nimmt uns das Framework ab. Mit
ResponseEntity.ok(taskList)
sagen wir Spring, dass wir eine erfolgreiche HTTP Antwort senden wollen (Status 200: OK) und als Body wollen wir die Liste mit dem Task ausliefern.
Wie wir oben gesehen haben, klappt das bereits, ohne dass wir uns explizit noch um die Generierung des JSON Outputs kümmern müssen. Spring Boot hat für diesen in Webservices wiederkehrenden Anwendungsfall sogenannte Message Converter vorregistriert. Diese können Java Klassen unter anderem in JSON Repräsentation serialisieren – und anders herum. Da die meisten REST Services JSON als Austauschformat verwenden, funktioniert das ohne weitere Konfiguration, also out-of-the-box, wie man so schön sagt.
Ausblick
Natürlich ist die Taskliste im gezeigten Zustand nicht annähernd funktionsfähig. Es fehlen Endpunkte zum Schreiben von Daten, zum Lesen von Einzeldatensätzen und die Liste kann sicherlich noch weitere Funktionen zum Filtern und Sortieren vertragen. Aber diesen Themen widmen wir uns in einem späteren Post! Bis dahin wünsche ich viel Spaß beim Basteln an der rudimentären Taskliste...

Danke für's lesen.
Julius Mischok ist Geschäftsführer der Mischok GmbH in Augsburg. Seine Kernaufgaben sind Prozessentwicklung, sowie Coaching und Schulung der Entwicklungsteams. Aktuell fokussiert sich seine Arbeit auf die Frage, wie Software schnell und mit einer maximalen Wertschöpfung produziert werden kann. Julius hat Mathematik studiert und entwickelt seit fast zwei Jahrzehnten Java. Seine Erfahrung brachte er unter anderem in Softwareprojekten für BMW, Audi, Hilti, Porsche, Allianz, Bosch, und viele mehr ein.