REST / JSON APIs in Java leicht gemacht mit Spring und Jackson

Mit Spring Annotationen kann man sehr leicht Java Methoden als REST-Services exponieren. Die Jackson-Bibliothek wandelt JSON Daten dann automatisch in Java Objekte um. Wie einfach das geht, zeige ich in diesem Post.


Spring RestContoller

Model View Controller Design Pattern

Spring Web MVC ist von Anfang an Bestandteil des Spring Frameworks. MVC steht für Model View Controller und ist ein Design Pattern für Softwareentwicklung:
  • View ist die Präsentation für den Benutzer. Also z.B. das html, welches im Browser angezeigt wird.
  • Model ist das Datenmodell, welches in der View dargestellt wird und im Controller verarbeitet wird.
  • Der Controller kümmert sich um die Programmsteuerung und stellt somit die Daten bereit.
Model-View-Controller Aufrufbeziehungen

Im Detail wird das MVC Pattern und weitere Entwurfsmuster für guten Code z.B. in diesem Buch erklärt: Head First: Design Patterns


RestController erstellen

Ein typischer Controller einer REST API unterstützt also die verschiedenen http Methoden, um dann das Datenmodell zu verarbeiten. Hier seht ihr einen einfachen RestController, der Daten (Items) zu einer Liste hinzufügt:

    @RestController
    @RequestMapping(path = "/item")
    public class ItemController {

List<Item> items = new ArrayList<>();
@PostMapping
public ResponseEntity<Void> apendItem(
                @RequestBody Item newItem) {
    items.add(newItem);
    // HTTP Code 201 wird zurueck geschickt.
    return ResponseEntity.status(HttpStatus.CREATED).build();
}
    }
  • @RestController macht aus der Java-Klasse einen Controller, der vom standard-mäßig konfigurierten Spring RequestMappingHandler erkannt und verarbeitet wird.
  • @RequestMapping Annotationen werden verwendet, um zu definieren auf welche http Methoden, URL-Pfade, Datentypen usw. die Klasse und oder die jeweilig annotierte Methode reagieren. Hier verarbeitet der Controller alle eingehenden Requests mit dem Pfad /item.
  • @PostMapping entspricht dabei @RequestMapping ergänzt um die zu verwendende http POST-Methode, also: @RequestMapping(method = RequestMethod.POST). Die Annotation an der Methode erbt den definierten Pfad in der Annotation an der Klasse.
  • @RequestBody wird verwendet um anzugeben, dass der Body des POST Requests in dem annotierten Parameter an die Methode übergeben wird. Wie das genau funktioniert, erkläre ich im nächsten Kapitel.
  • ResponseEntity ist eine Klasse des Spring Frameworks, mit der eine http Response gebaut werden kann. Im Beispiel wird einfach der http Code 201 (CREATED) gesetzt und ein leerer http Response Body mit den Standard-Headern zurückgeschickt. Status, Body und Header können generell mit dieser Klasse gesetzt werden, so dass unser Controller mit "ordentlichen" http Responses antwortet.
In einem anderen Blog-Post habe ich gezeigt, wie man mit Spring Boot eines neues Web Projekt erstellt. Dort werden die benötigten Bibliotheken bzw. Maven Dependencies gezeigt, damit der hier gezeigte Controller compiliert:
https://agile-coding.blogspot.com/2020/09/microservices-mit-spring-boot-erstellen.html

http Methoden hinzufügen

Spring unterstützt in RestControllern alle http Methoden:
  • GET mittels Annotation @GetMapping
  • POST mittels Annotation @PostMapping
  • PUT mittels Annotation @PutMapping
  • PATCH mittels Annotation @PatchMapping
  • HEAD mittels @RequestMapping(method = RequestMethod.HEAD)
  • DELETE mittels Annotation @DeleteMapping
  • OPTIONS mittels @RequestMapping(method = RequestMethod.OPTIONS)
  • TRACE mittels @RequestMapping(method = RequestMethod.TRACE) 
Die jeweilige Annotation wird an die zugehörigen public Methoden geschrieben. Hier 2 Beispiele für eine GET und eine DELETE Methode:

    @GetMapping
    public ResponseEntity<Object> getItemByName(
            @RequestParam String itemName) {
// Mit Java 8 Stream API durch Liste aller Items iterieren
// und das erste Item mit gleichem Namen zurückschicken.
Optional<Item> optItem = items.stream()
                .filter(item -> itemName != null 
                        && itemName.equalsIgnoreCase(item.getName()))
.findFirst();
        if (optItem.isPresent())
            return ResponseEntity.ok(optItem.get());
        return ResponseEntity.notFound().build();
    }
    @DeleteMapping(path = "/{itemId}")
    public ResponseEntity<Void> deleteItemById(
            @PathVariable String itemId) {
// In Java 8 wurde das Collections Interface 
        // um Lambda unterstützende Methoden erweitert.
// Hier werden alle Item Instanzen entfernt
        // für die der Lambda Ausdruck true liefert.
if (items.removeIf(item -> itemId != null 
                && itemId.equals(item.getId())))
    return ResponseEntity.noContent().build();
return ResponseEntity.notFound().build();
    }

  • @RequestParam ist eine weitere Spring Annotation, die verwendet wird um Query-Parameter aus der URL in einen Methoden Parameter zu überführen.
    Wird unser REST-Service mit folgender URL aufgerufen:
    http://localhost:8080/item?itemName=Ball
    so nimmt der String Parameter itemName automatisch den Wert "Ball" an. Wenn der Parametername mit dem Namen des Request-Parameters in der URL nicht übereinstimmt, ist der Wert von String itemName null.
  • Mit ResponseEntity.ok(optItem.get()) wird hier eine http Response mit dem Code 200 erstellt. Im Body dieser Response befindet sich die gefundene Item Instanz.
  • Eine weitere Variante der Parameter-Übertragung von der URL in die public Methode (@PathVariablewird in der mit @DeleteMapping annotierten Methode gezeigt. In @DeleteMapping wird zusätzlich ein Teil des URL-Pfades definiert, nämlich "/{itemId}". Im vorherigen Abschnitt haben wir gesehen, dass man auch in der @RequestMapping Annotation den URL-Pfad spezifizieren kann. Wenn dies auf Klassenebene geschieht, so gilt es für alle Methoden. Eine valide URL für unsere Delete-Methode würde so aussehen:
    http://localhost:8080/item/123
    Teile des URL-Pfades, die in geschweiften Klammern notiert sind, können an public Methoden Parameter mit der Annotation @PathVariable übergeben werden. In unserem Beispiel hätte der String Parameter itemId also den Wert "123".
Die hier gezeigten Annotationen lassen sich beliebig kombinieren, so dass man die REST-API wie benötigt implementieren kann.

Von JSON zum Java Objekt mit Jackson

Betrachten wir nun noch einmal die zuvor vorgestellt POST-Methode, ihr Aufruf mit curl (ein Linux Shell Kommando zum Verschicken von Requests) sieht so aus:
curl -X POST "http://localhost:8080/item" -H  "Content-Type: application/json" -d "{  \"id\": \"123\", \"name\": \"Ball\"}"
Alternativ könnt ihr z.B. auch das Tool Postman verwenden, um POST-Requests mit http Body zu verschicken, siehe https://www.postman.com/.

Der curl-Befehl sendet einen POST-Request an die URL des ItemController und überträgt im Body JSON:
{
    "id": "123",
    "name": "Ball"
}

Spring kann nun diesen JSON String mit Hilfe der Jackson-Bibliothek automatisch in ein POJO Objekt der Klasse Item umwandeln. Jackson wird automatisch durch die Spring Boot Bibliothek spring-boot-starter-web geladen, die wir während der Erstellung unseres Spring Boot Projektes ausgewählt haben, siehe microservices-mit-spring-boot-erstellen.html.

Alles was wir selbst tun müssen, um Spring und Jackson zu erklären, wie ein JSON-Text in eine Instanz einer Klasse übersetzt wird, ist diese Klasse zu schreiben:
    package de.bsi.restdemo;

    public class Item {
private String id, name;

public String getId() { return id; }
public void setId(String id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }
    }    
Die Klasse besteht aus 2 String Attributen mit jeweiliger Getter- und Setter-Methode, das ist alles.
Solch einfache Klassen werden daher auch als POJO "Plain Old Java Object" bezeichnet.

Wenn wir noch mal die Signatur unserer public Methode zum Verarbeiten von POST-Request anschauen, dann sehen wir dort den Parameter vom Typ Item, der unserer POJO Klasse entspricht:
    @PostMapping
    public ResponseEntity<Void> apendItem(@RequestBody Item newItem)
Spring und Jackson kümmern sich darum, dass der JSON im "RequestBody" als Instanz der Klasse Item im Parameter newItem ankommen - und wir müssen keinen Parser für JSON schreiben oder selbst bereitstellen 😎

Fazit und Ausblick

Ich hoffe ich konnte euch in diesem Artikel zeigen, wie leicht man mit Spring und Jackson REST APIs entwickeln kann. Hier haben wir uns auf den Übergang vom http-Request in die Java-Anwendung konzentriert und darauf wie man eine ordentlich http-Response zurückschickt.

Den Code zu diesem Blog-Post findet ihr hier in GitHub:

In künftigen Posts könnten wir zum Beispiel folgende weiterführenden Themen betrachten:
  • Wie grenzt man die Geschäftslogik vom Controller ab bzw. wie funktionieren Spring Beans?
  • Wie kann man mit Spring Data die Daten in eine Datenbank schreiben?
  • Wie schreibt man JUnit Tests für Rest-Controller und wie unterstützt Spring MockMvc dabei?
  • Wie bietet man dem API-Benutzer eine Swagger-UI bzw. eine OpenAPI Spezifikation an? 
Fragen und Wünsche könnt ihr gerne als Kommentar hinterlassen.

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

CronJobs mit Spring

Kernkonzepte von Spring: Beans und Dependency Injection