Kapitel 4. JSON in Java

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Wir haben gezeigt, wie man JSON mit JavaScript und Ruby on Rails verwendet, und jetzt kommen wir zu Java, unserer dritten und letzten Plattform für dieses Buch. Das werden wir behandeln:

  • Java/JSON-Serialisierung/Deserialisierung mit Jackson durchführen

  • Arbeiten mit Java-Objekten und JSON

  • Verwendung von JSON mit JUnit

  • RESTful API-Aufrufe durchführen und die Ergebnisse mit JUnit und JsonUnit testen

  • Aufbau einer kleinen JSON-basierten API mit Spring Boot

In unseren Beispielen werden wir RESTful-API-Aufrufe tätigen, um mit den Daten zu arbeiten, die wir im vorherigen Kapitel auf json-server bereitgestellt haben. Anschließend werden wir eine realistischere JSON-basierte Web-API erstellen. Bevor wir eine RESTful-API entwickeln, müssen wir mit den Grundlagen der Java-Serialisierung/Deserialisierung mit JSON beginnen und dann die Komplexität erhöhen.

Java und Gradle einrichten

In diesem Kapitel wird Gradle für die Erstellung von Quell- und Testcode verwendet.Wenn du Java und Gradle noch nicht installiert hast, gehe zu Anhang A und siehe "Installiere die Java-Umgebung" und "Installiere Gradle". Danach hast du eine grundlegende Umgebung, mit der du die Beispiele ausführen kannst.

Gradle Übersicht

Gradle nutzt die Konzepte von früheren Java-basierten Build-Systemen - ApacheAntund Maven. Gradle ist weit verbreitet und bietet die folgenden Funktionen für Java-Projekte:

  • Projektstruktur (eine gemeinsame/standardmäßige Projektverzeichnisstruktur)

  • Abhängigkeitsmanagement (für JAR-Dateien)

  • Ein gemeinsamer Bauprozess

Das Dienstprogramm gradle init initialisiert ein Projekt, indem es eine zentrale Verzeichnisstruktur und einige erste Implementierungen für das Build-Skript sowie einfachen Java-Quell- und Testcode erstellt. Hier sind die wichtigsten Verzeichnisse und Dateien in einem Gradle-Projekt:

  • src/main/ enthält Quellcode und Ressourcen.

    • java/ ist der Java-Quellcode.

    • resources/ enthält die Ressourcen (z. B. Eigenschaften, Datendateien, in unserem Fall JSON), die vom Quellcode verwendet werden.

  • test/main/ enthält Quellcode und Ressourcen.

    • java/ ist der Java-Quellcode.

    • resources/ enthält die Ressourcen (z. B. Eigenschaften, Datendateien, in unserem Fall JSON), die vom Quellcode verwendet werden.

  • build/ enthält die .class-Dateien, die beim Kompilieren des Quell- und Testcodes entstehen.

    • libs/ enthält die JAR- oder WAR-Dateien, die beim Erstellen des Projekts entstehen.

  • gradlew ist der Gradle-Wrapper, mit dem du ein Projekt als ausführbares JAR ausführen kannst. Wir werden dies später im Abschnitt über Spring Boot genauer behandeln.

  • build.gradle wird von gradle init für dich initiiert, aber du musst sie mit projektspezifischen Abhängigkeiten ausfüllen. Gradle verwendet eine Groovy-basierte DSL für seine Build-Skripte (anstelle von XML).

  • build/ enthält build-bezogene Artefakte, die von gradle build oder gradle test erstellt wurden.

Hier sind die wichtigsten Gradle-Aufgaben, die du kennen musst, um mit Gradle zu arbeiten.Du kannst diese Aufgaben sehen, wenn du gradle tasks auf der Kommandozeile eingibst:

gradle build

Baue das Projekt.

gradle classes

Kompiliere den Java-Quellcode.

gradle clean

Lösche das Build-Verzeichnis.

gradle jar

Kompiliere den Java-Quellcode und packe ihn (zusammen mit den Ressourcen) in eine JAR-Datei.

gradle javadoc

Erstelle JavaDoc-Dokumentation aus dem Java-Quellcode.

gradle test

Unit Tests ausführen (einschließlich Kompilieren von Java-Quell- und Testcode).

gradle testClasses

Kompiliere Java-Testcode.

Hier siehst du, wie die Beispielprojekte erstellt wurden:

  • gradle init --type java-application wurde verwendet, um die ersten Speakers-Test- und Speakers-Web-Anwendungenzu erstellen.

  • Die erzeugte build.gradle-Datei und die Java-Anwendungs- und Testdateien sind Stubs. Sie wurden für die Beispiele in diesem Kapitel durch echten Code ersetzt.

Gradle ist gut dokumentiert, und hier sind einige Tutorials und Referenzen, die dir helfen, tiefer zu gehen:

Nachdem wir nun die Grundlagen von Gradle kennengelernt haben, ist es an der Zeit, einen Blick auf Java-basierte JSON-Bibliotheken zu werfen und dann zu Programmierbeispielen überzugehen.

Gerade genug Unit Testing mit JUnit

JUnit ist ein weit verbreitetes Unit-Testing-Framework. Die Tests in diesem Kapitel verwenden JUnit, weil es in der Java-Community weit verbreitet ist. JUnit-Tests sind prozedural, also sind die Unit-Tests im TDD-Stil. Wenn du JUnit mit BDD kombinieren möchtest, ist Cucumber eine gute Wahl. Mehr über BDD und Cucumber in Java erfährst du in Micha Kops' ausgezeichnetem Artikel"BDD Testing with Cucumber, Java and JUnit".

Java-basierte JSON-Bibliotheken

Es gibt mehrere solide JSON-Bibliotheken für die Java/JSON-Serialisierung/Deserialisierung, einschließlich dieser:

Jackson

Details zu Jackson findest du unter im GitHub-Repository.

Gson

Gson wird von Google bereitgestellt.

JSON-java

Diese Bibliothek wird von Doug Crockford zur Verfügung gestellt.

Java SE (Standard Edition)

Die JSON-Unterstützung wurde mit JavaEE 7 als Teil derJava Specification Request (JSR) 353-Initiative in die Java-Plattform eingeführt.JSR-353 ist eine eigenständige Implementierung, die du ab Java SE 8 in deine Java SE-Anwendungen integrieren kannst. Java SE 9 wird als Teil der Java Enhancement Proposal (JEP) 198-Initiative native JSON-Unterstützung bieten.

Alle Beispiele in diesem Kapitel verwenden Jackson, weil er

  • ist weit verbreitet (insbesondere in der Spring Community)

  • Bietet hervorragende Funktionalität

  • Hat lange Zeit gut funktioniert

  • Ist gut gepflegt und hat eine aktive Entwicklungsgemeinschaft

  • Hat eine gute Dokumentation

Außerdem werden wir uns auf eine Java/JSON-Bibliothek konzentrieren. Wie bereits erwähnt, funktionieren die anderen Bibliotheken gut, du kannst sie also gerne selbst ausprobieren.

Beginnen wir mit den Grundlagen der Java-Serialisierung/Deserialisierung.

JSON Serialisierung/Deserialisierung mit Jackson

Java-Anwendungen müssen von Java-Datenstrukturen nach JSON konvertieren (serialisieren) und von JSON nach Java konvertieren (deserialisieren).

Serialisierung/Deserialisierung mit einfachen Java-Datentypen

Wie in den vorherigen Kapiteln beginnen wir mit der Serialisierung einiger grundlegender Java-Datentypen:

  • integer

  • string

  • array

  • boolean

Beispiel 4-1 zeigt einen einfachen Unit Test, der Jackson und JUnit 4 verwendet, um einfache Java-Datentypen zu serialisieren/deserialisieren.

Beispiel 4-1. speakers-test/src/test/java/org/jsonatwork/ch4/BasicJsonTypesTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.util.*;

import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.type.*;
import com.fasterxml.jackson.databind.*;

public class BasicJsonTypesTest {
  private static final String TEST_SPEAKER = "age = 39\n" +
    "fullName = \"Larson Richard\"\n" +
    "tags = [\"JavaScript\",\"AngularJS\",\"Yeoman\"]\n" +
    "registered = true";

  @Test
  public void serializeBasicTypes() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      Writer writer = new StringWriter();
      int age = 39;
      String fullName = new String("Larson Richard");
      List<String> tags = new ArrayList<String>(
          Arrays.asList("JavaScript", "AngularJS", "Yeoman"));

      boolean registered = true;
      String speaker = null;

      writer.write("age = ");
      mapper.writeValue(writer, age);
      writer.write("\nfullName = ");
      mapper.writeValue(writer, fullName);
      writer.write("\ntags = ");
      mapper.writeValue(writer, tags);
      writer.write("\nregistered = ");
      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      mapper.writeValue(writer, registered);
      speaker = writer.toString();
      System.out.println(speaker);
      assertTrue(TEST_SPEAKER.equals(speaker));
      assertTrue(true);
    } catch (JsonGenerationException jge) {
      jge.printStackTrace();
      fail(jge.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  @Test
  public void deSerializeBasicTypes() {
    try {
      String ageJson = "{ \"age\": 39 }";
      ObjectMapper mapper = new ObjectMapper();
      Map<String, Integer> ageMap = mapper.readValue(ageJson,
          new TypeReference<HashMap<String,Integer>>() {});

      Integer age = ageMap.get("age");

      System.out.println("age = " + age + "\n\n\n");
      assertEquals(39, age.intValue());
      assertTrue(true);
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

}

In diesem Beispiel weist die Annotation @Test JUnit an, die Methoden serializeBasicTypes() unddeSerializeBasicTypes() als Teil des Tests auszuführen. Diese Unit Tests führen nicht viele Assertions für die JSON-Daten selbst durch. Wir werden uns später ausführlicher mit Assertions beschäftigen, wenn wir gegen eine Web-API testen.

Hier sind die wichtigsten Jackson-Klassen und -Methoden, die in/aus JSON serialisieren/deserialisieren:

  • ObjectMapper konvertiert zwischen Java- und JSON-Konstrukten.

  • ObjectMapper.writeValue() konvertiert einen Java-Datentyp in JSON (und gibt ihn in diesem Fall auf Writer aus).

  • ObjectMapper.readValue() konvertiert JSON in einen Java-Datentyp.

Führe einen einzelnen Unit Test über die Kommandozeile wie folgt aus:

cd chapter-4/speakers-test

+gradle test --tests org.jsonatwork.ch4.BasicJsonTypesTest+

Du solltest diese Ergebnisse sehen:

json 04in01

Dieses Beispiel ist im Moment noch nicht so spannend, weil es nur einfache Datentypen in/aus JSON serialisiert/deserialisiert. Die Serialisierung/Deserialisierung wird interessanter, wenn Objekte beteiligt sind.

Serialisierung/Deserialisierung mit Java-Objekten

Jetzt, wo wir Jackson gut kennen und wissen, wie man mit einfachen Java-Datentypen arbeitet, können wir uns mit Objekten näher beschäftigen. Beispiel 4-2 zeigt, wie man mit Jackson ein einzelnes speaker Objekt serialisiert/deserialisiert und wie man eine JSON-Datei in mehrere speaker Objekte deserialisiert.

Beispiel 4-2. speakers-test/src/test/java/org/jsonatwork/ch4/SpeakerJsonFlatFileTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.net.*;
import java.util.*;

import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.type.*;

public class SpeakerJsonFlatFileTest {

private static final String SPEAKER_JSON_FILE_NAME = "speaker.json";
private static final String SPEAKERS_JSON_FILE_NAME = "speakers.json";
private static final String TEST_SPEAKER_JSON = "{\n" +
    "  \"id\" : 1,\n" +
    "  \"age\" : 39,\n" +
    "  \"fullName\" : \"Larson Richard\",\n" +
    "  \"tags\" : [ \"JavaScript\", \"AngularJS\", \"Yeoman\" ],\n" +
    "  \"registered\" : true\n" +
  "}";

@Test
public void serializeObject() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      Writer writer = new StringWriter();
      String[] tags = {"JavaScript", "AngularJS", "Yeoman"};
      Speaker speaker = new Speaker(1, 39, "Larson Richard", tags, true);
      String speakerStr = null;

      mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
      speakerStr = mapper.writeValueAsString(speaker);
      System.out.println(speakerStr);
      assertTrue(TEST_SPEAKER_JSON.equals(speakerStr));
      assertTrue(true);
    } catch (JsonGenerationException jge) {
      jge.printStackTrace();
      fail(jge.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  private File getSpeakerFile(String speakerFileName) throws URISyntaxException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL fileUrl = classLoader.getResource(speakerFileName);
    URI fileUri = new URI(fileUrl.toString());
    File speakerFile = new File(fileUri);

    return speakerFile;
  }

  @Test
  public void deSerializeObject() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      File speakerFile = getSpeakerFile(
          SpeakerJsonFlatFileTest.SPEAKER_JSON_FILE_NAME);

      Speaker speaker = mapper.readValue(speakerFile, Speaker.class);

      System.out.println("\n" + speaker + "\n");
      assertEquals("Larson Richard", speaker.getFullName());
      assertEquals(39, speaker.getAge());
      assertTrue(true);
    } catch (URISyntaxException use) {
      use.printStackTrace();
      fail(use.getMessage());
    } catch (JsonParseException jpe) {
      jpe.printStackTrace();
      fail(jpe.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

  @Test
  public void deSerializeMultipleObjects() {
    try {
      ObjectMapper mapper = new ObjectMapper();
      File speakersFile = getSpeakerFile(
          SpeakerJsonFlatFileTest.SPEAKERS_JSON_FILE_NAME);

      JsonNode arrNode = mapper.readTree(speakersFile).get("speakers");
      List<Speaker> speakers = new ArrayList<Speaker>();
        if (arrNode.isArray()) {
          for (JsonNode objNode : arrNode) {
          System.out.println(objNode);
          speakers.add(mapper.convertValue(objNode, Speaker.class));
        }
      }

      assertEquals(3, speakers.size());
      System.out.println("\n\n\nAll Speakers\n");
      for (Speaker speaker: speakers) {
        System.out.println(speaker);
      }

      System.out.println("\n");
      Speaker speaker3 = speakers.get(2);
      assertEquals("Christensen Fisher", speaker3.getFullName());
      assertEquals(45, speaker3.getAge());
      assertTrue(true);
    } catch (URISyntaxException use) {
      use.printStackTrace();
      fail(use.getMessage());
    } catch (JsonParseException jpe) {
      jpe.printStackTrace();
      fail(jpe.getMessage());
    } catch (JsonMappingException jme) {
      jme.printStackTrace();
      fail(jme.getMessage());
    } catch (IOException ioe) {
      ioe.printStackTrace();
      fail(ioe.getMessage());
    }
  }

}

Beachte Folgendes in diesem JUnit-Test:

  • serializeObject() erstellt ein Speaker Objekt und serialisiert es auf die Standardausgabe, indem es die MethodeObjectMapper.writeValueAsString() verwendet und System.out.println(). Der Test setztSerializationFeature.INDENT_OUTPUT auf true, um die JSON-Ausgabe einzurücken/auszudrucken.

  • deSerializeObject() ruft getSpeakerFile() auf, um eine JSON-Eingabedatei zu lesen (die ein einzelnesspeaker JSON-Objekt enthält), und verwendet die Methode ObjectMapper.readValue(), um sie in einSpeaker Java-Objekt zu deserialisieren.

  • deSerializeMultipleObjects() macht das Folgende:

    • Ruft getSpeakerFile() auf, um eine JSON-Eingabedatei zu lesen, die ein Array von JSON speaker Objekten enthält.

    • Ruft die Methode ObjectMapper.readTree() auf, um ein JsonNode Objekt zu erhalten, das ein Zeiger auf den Wurzelknoten des JSON-Dokuments ist, das sich in der Datei befand.

    • Besucht jeden Knoten im JSON-Baum und verwendet die Methode ObjectMapper.convertValue(), um jedesspeaker JSON-Objekt in ein Speaker Java-Objekt zu deserialisieren.

    • Druckt jedes Speaker Objekt in der Liste aus.

  • getSpeakerFile() findet eine Datei auf dem Klassenpfad und tut Folgendes:

    • Ruft die ContextClassLoader von der aktuellen Thread der Ausführung ab.

    • Verwendet die Methode ClassLoader.getResource(), um den Dateinamen als Ressource im aktuellen Klassenpfad zu finden.

    • Konstruiert ein File Objekt basierend auf dem URI des Dateinamens.

Jeder der vorangegangenen Tests verwendet die Assertion-Methoden von JUnit, um die Ergebnisse der JSON-Serialisierung/Deserialisierung zu testen.

Wenn du den Test über die Befehlszeile mitgradle test --tests org.jsonatwork.ch4.SpeakerJsonFlatFileTest ausführst, siehst du Folgendes:

json 04in02

Jackson bietet viel mehr Funktionen, als in diesem Kapitel gezeigt werden können. In den folgenden Ressourcen findest du einige tolle Tutorials:

Unit Testing mit einer Stub-API

Bis jetzt haben wir JUnit verwendet, um die Daten aus JSON-Flatfiles zu testen. Jetzt werden wir einen realistischeren Test gegen eine API durchführen. Aber wir brauchen eine API, gegen die wir testen können, ohne viel Code zu schreiben oder eine große Infrastruktur aufzubauen. Wir zeigen dir, wie du eine einfache Stub-API (die eine JSON-Antwort erzeugt) erstellen kannst, ohne eine einzige Codezeile zu schreiben.

Test Daten

Um den Stub zu erstellen, verwenden wir die Speaker-Daten von aus früheren Kapiteln als Testdaten, die auf GitHubverfügbar sind, und stellen sie als RESTful-API bereit. Wir nutzen das json-server Node.js-Modul, um die Datei speakers.jsonals Web-API bereitzustellen. Wenn du json-server installieren musst, findest du weitere Informationen unter "npm-Module installieren"in Anhang A. So führst du json-server auf Port 5000 von deinem lokalen Rechner aus (mit einer zweiten Terminalsitzung):

cd chapter-4/speakers-test/src/test/resources

json-server -p 5000 ./speakers.json

Du kannst auch einen einzelnen Sprecher abrufen, indem du die id wie folgt zur URI hinzufügst: http://localhost:5000/speakers/1. Nachdem die Stub-API eingerichtet ist, ist es an der Zeit, einige Unit Tests zu schreiben.

JSON und JUnit Testen mit APIs

Unser Unit Test wird Folgendes tun:

  • Mache HTTP-Aufrufe an die Stub Speakers API

  • Überprüfe das JSON (aus der HTTP-Antwort) mit den erwarteten Werten

Wie schon in den vorherigen Kapiteln werden wir , den Open Source Unirest API Wrapper, nutzen, aber dieses Mal verwenden wir die Java-Version.

In den vorangegangenen JUnit-Tests in diesem Kapitel haben wir sichergestellt, dass nur die Mindestfunktionalität funktioniert (es wurden keine Ausnahmen ausgelöst), und jetzt ist es an der Zeit, unsere Tests etwas anspruchsvoller zu gestalten. Die verbleibenden Unit Tests sehen sich den JSON-Inhalt an, der von einer HTTP-Antwort zurückgegeben wird, und überprüfen, ob er mit der erwarteten Ausgabe übereinstimmt. Wir können die Daten durchsuchen und mit eigenem Code vergleichen oder eine Bibliothek verwenden, um den Arbeitsaufwand zu verringern. JsonUnit hat viele hilfreiche Matcher, die den JSON-Vergleich in JUnit-Tests vereinfachen. Wir werden in diesen Unit Tests die Grundlagen von JsonUnit behandeln, aber es bietet viel mehr Funktionen, als wir hier abdecken können, darunter die folgenden:

  • Reguläre Ausdrücke

  • Mehr Matcher

  • Die Möglichkeit, bestimmte Felder und Werte zu ignorieren

Der Unit Test in Beispiel 4-3 fasst alles zusammen, indem er die Stub API aufruft und die JSON-Antwort mit den erwarteten Werten vergleicht.

Beispiel 4-3. speakers-test/src/test/java/org/jsonatwork/ch4/SpeakersJsonApiTest.java
package org.jsonatwork.ch4;

import static org.junit.Assert.*;

import java.io.*;
import java.net.*;
import java.util.*;

import org.apache.http.*;
import org.junit.Test;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.request.*;

import static net.javacrumbs.jsonunit.fluent.JsonFluentAssert.assertThatJson;

public class SpeakersApiJsonTest {
  private static final String SPEAKERS_ALL_URI = "http://localhost:5000/speakers";
  private static final String SPEAKER_3_URI = SPEAKERS_ALL_URI + "/3";

  @Test
  public void testApiAllSpeakersJson() {
    try {
      String json = null;
      HttpResponse <String> resp = Unirest.get(
        SpeakersApiJsonTest.SPEAKERS_ALL_URI).asString();

      assertEquals(HttpStatus.SC_OK, resp.getStatus());
      json = resp.getBody();
      System.out.println(json);
      assertThatJson(json).node("").isArray();
      assertThatJson(json).node("").isArray().ofLength(3);
      assertThatJson(json).node("[0]").isObject();
      assertThatJson(json).node("[0].fullName")
              .isStringEqualTo("Larson Richard");
      assertThatJson(json).node("[0].tags").isArray();
      assertThatJson(json).node("[0].tags").isArray().ofLength(3);
      assertThatJson(json).node("[0].tags[1]").isStringEqualTo("AngularJS");
      assertThatJson(json).node("[0].registered").isEqualTo(true);
      assertTrue(true);
    } catch (UnirestException ue) {
      ue.printStackTrace();
    }
  }

  @Test
  public void testApiSpeaker3Json() {
    try {
      String json = null;
      HttpResponse <String> resp = Unirest.get(
        SpeakersApiJsonTest.SPEAKER_3_URI).asString();

      assertEquals(HttpStatus.SC_OK, resp.getStatus());
      json = resp.getBody();
      System.out.println(json);
      assertThatJson(json).node("").isObject();
      assertThatJson(json).node("fullName")
              .isStringEqualTo("Christensen Fisher");
      assertThatJson(json).node("tags").isArray();
      assertThatJson(json).node("tags").isArray().ofLength(4);
      assertThatJson(json).node("tags[2]").isStringEqualTo("Maven");
      assertTrue(true);
    } catch (UnirestException ue) {
      ue.printStackTrace();
    }
  }

}

Beachte Folgendes in diesem JUnit-Test:

  • testApiAllSpeakersJson():

    • Ruft eine Liste aller Sprecher von der Speakers API ab, indem er Unirest.get() mit http://localhost:5000/speakers aufruft.

    • Überprüft, ob der HTTP-Statuscode OK (200) lautet.

    • Ruft das JSON-Dokument (das ein Array von speaker Objects enthält) aus dem HTTP Response Body ab.

    • Macht eine Reihe von Aussagen über das JSON-Dokument mit JSONUnit's assertThatJson(), um zu überprüfen, dass

      • Wir haben ein Array mit drei speaker Objekten.

      • Jedes Feld (zum Beispiel fullName, tags und registered) in jedem speaker Objekt entspricht den erwarteten Werten.

    • Wenn du gradle test aufrufst, solltest du Folgendes als Teil der Ausgabe sehen:

json 04in03
  • testApiSpeaker3Json():

    • Ruft den Lautsprecher 3 von der Speakers API ab, indem er Unirest.get() mit http://localhost:5000/speakers/3 aufruft.

    • Überprüft, ob der HTTP Response Code OK (200) lautet

    • Ruft das JSON-Dokument (das ein einzelnes speaker Objekt enthält) aus dem HTTP Response Body ab.

    • Macht eine Reihe von Aussagen über das JSON-Dokument mit JSONUnit's assertThatJson(), um zu überprüfen, dass

      • Wir haben ein einziges speaker Objekt.

      • Jedes Feld im speaker Objekt hat die erwarteten Werte.

    • Wenn du gradle test aufrufst, solltest du Folgendes als Teil der Ausgabe sehen:

json 04in04

Dieser Unit Test berührt nur die Grundlagen der Unirest Java-Bibliothek, die unter auch Folgendes bietet:

  • Vollständige HTTP-Verbabdeckung (GET, POST, PUT, DELETE, PATCH)

  • Die Möglichkeit, benutzerdefinierte Mappings von einem HTTP Response Body auf ein Java-Objekt zu erstellen

  • Asynchrone (d.h. nicht blockierende) Anfragen

  • Zeitüberschreitungen

  • Datei-Uploads

  • Und vieles mehr

Weitere Informationen über die Unirest Java-Bibliothek findest du auf der Unirest-Website.

Bevor du weitermachst, kannst du json-server stoppen, indem du in der Befehlszeile Strg-C drückst.

Wir haben gezeigt, wie man eine Stub-API einsetzt und mit ihr interagiert, und jetzt ist es an der Zeit, eine kleine RESTful-API zu bauen.

Eine kleine Web-API mit Spring Boot erstellen

Wir werden weiterhin die Speaker-Daten verwenden, um eine API(Kapitel-4/speakers-api in den Beispielen) mit Spring Boot zu erstellen. Das Spring Framework erleichtert die Entwicklung und Bereitstellung von Java-basierten Webanwendungen und RESTful APIs. Spring Boot erleichtert die Erstellung von Spring-basierten Anwendungen durch die Bereitstellung von Standardeinstellungen. Mit Spring Boot:

  • Es gibt keine mühsamen, fehleranfälligen XML-basierten Konfigurationsdateien.

  • Tomcat und/oder Jetty können eingebettet werden, so dass es nicht notwendig ist, ein WAR (Web Application ARchive) separat zu verteilen.Du könntest immer noch Spring Boot und Gradle verwenden, um eine WAR-Datei zu erstellen und an Tomcat zu verteilen. Aber wie du sehen wirst, vereinfacht ein ausführbares JAR die Umgebung eines Entwicklers, weil es die Anzahl der Einstellungen und Installationen reduziert, was eine iterative Anwendungsentwicklung ermöglicht.

In den folgenden Schritten erstellen wir die Speakers-API mit Spring Boot und stellen sie unter bereit:

  1. Schreibe Quellcode:

    • Modell

    • Controller

    • Bewerbung

  2. Erstelle ein Build-Skript(build.gradle).

  3. Setze ein eingebettetes JAR mit gradlew ein.

  4. Test mit Postman.

Das Modell erstellen

Die Klasse Speaker in Beispiel 4-4 ist ein Plain Old Java Object (POJO), das die Speaker-Daten repräsentiert, die von der API als JSON wiedergegeben werden.

Beispiel 4-4. speakers-api/src/main/java/org/jsonatwork/ch4/Speaker.java
package org.jsonatwork.ch4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Speaker {
  private int id;
  private int age;
  private String fullName;
  private List<String> tags = new ArrayList<String>();
  private boolean registered;

  public Speaker() {
    super();
  }

  public Speaker(int id, int age, String fullName, List<String> tags,
                 boolean registered) {
    super();
    this.id = id;
    this.age = age;
    this.fullName = fullName;
    this.tags = tags;
    this.registered = registered;
  }

  public Speaker(int id, int age, String fullName, String[] tags,
                 boolean registered) {
    this(id, age, fullName, Arrays.asList(tags), registered);
  }

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getFullName() {
    return fullName;
  }

  public void setFullName(String fullName) {
    this.fullName = fullName;
  }

  public List<String> getTags() {
    return tags;
  }

  public void setTags(List<String> tags) {
    this.tags = tags;
  }

  public boolean isRegistered() {
    return registered;
  }

  public void setRegistered(boolean registered) {
    this.registered = registered;
  }

  @Override
  public String toString() {
    return String.format(
      "Speaker [id=%s, age=%s, fullName=%s, tags=%s, registered=%s]",
      id, age, fullName, tags, registered);
  }

}

In diesem Code gibt es nichts Aufregendes. Er stellt lediglich die Datenelemente, Konstruktoren und Zugriffsmethoden (Getter und Setter) für ein speaker bereit. Dieser Code weiß nichts über JSON, denn (wie du gleich sehen wirst) konvertiert Spring dieses Objekt automatisch in JSON.

Den Controller erstellen

In einer Spring-Anwendung bearbeitet der Controller die HTTP-Anfragen und gibt HTTP-Antworten zurück. In unserem Fall werden die speaker JSON-Daten im Response Body zurückgegeben. Beispiel 4-5 zeigt die SpeakerController.

Beispiel 4-5. speakers-api/src/main/java/org/jsonatwork/ch4/SpeakerController.java
package org.jsonatwork.ch4;

import java.util.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;

@RestController
public class SpeakerController {

  private static Speaker speakers[] = {
    new Speaker(1, 39, "Larson Richard",
                new String[] {"JavaScript", "AngularJS", "Yeoman"}, true),
    new Speaker(2, 29, "Ester Clements",
                new String[] {"REST", "Ruby on Rails", "APIs"}, true),
    new Speaker(3, 45, "Christensen Fisher",
                new String[] {"Java", "Spring", "Maven", "REST"}, false)
  };

  @RequestMapping(value = "/speakers", method = RequestMethod.GET)
  public List<Speaker> getAllSpeakers() {
    return Arrays.asList(speakers);
  }

  @RequestMapping(value = "/speakers/{id}", method = RequestMethod.GET)
  public ResponseEntity<?>  getSpeakerById(@PathVariable long id) {
    int tempId = ((new Long(id)).intValue() - 1);

    if (tempId >= 0 && tempId < speakers.length) {
      return new ResponseEntity<Speaker>(speakers[tempId], HttpStatus.OK);
    } else {
      return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
  }
}

Beachte das Folgende in diesem Code:

  • Die Annotation @RestController identifiziert die Klasse SpeakerController als Spring MVC Controller, der HTTP-Anfragen verarbeitet.

  • Das Array speakers ist fest codiert, dient aber nur zu Testzwecken. In einer echten Anwendung würde ein separater Data Layer die speakers aus einer Datenbank oder einem externen API-Aufruf befüllen.

  • Die Methode getAllSpeakers() funktioniert folgendermaßen:

    • Reagiert auf HTTP GET Anfragen auf die /speakers URI.

    • Ruft das gesamte speakers Array als ArrayList ab und gibt es als JSON Array in einem HTTP Response Body zurück.

    • Die @RequestMapping Annotation bindet die /speakers URI an die getAllSpeakers() Methode für eine HTTPGET Anfrage.

  • Die Methode getSpeakerById() funktioniert folgendermaßen:

    • Antwortet auf HTTP GET Anfragen auf die /speakers/{id} URI (wobei id für eine Sprecher-ID steht).

    • Ruft eine speaker (basierend auf der Sprecher-ID) ab und gibt sie als JSON-Objekt in einem HTTP Response Body zurück.

    • Die @PathVariable Annotation bindet die Sprecher-ID aus dem HTTP-Anfragepfad an den id Parameter für die Suche.

    • Mit dem Rückgabewerttyp ResponseEntity kannst du den HTTP-Statuscode und/oder die speakersin der HTTP-Antwort festlegen.

In den beiden vorangegangenen Methoden wird das Speaker Objekt automatisch in JSON umgewandelt, ohne dass zusätzliche Arbeit anfällt. Standardmäßig ist Spring so konfiguriert, dass Jackson im Hintergrund die Java-zu-JSON-Konvertierung durchführt.

Die Anwendung registrieren

Wie bereits erwähnt, könnten wir die Speakers-API als WAR-Datei verpacken und sie auf einem Anwendungsserver wie Tomcat bereitstellen. Aber es ist einfacher, unsere API als eigenständige Anwendung über die Kommandozeile auszuführen. Dazu müssen wir Folgendes tun:

  • Hinzufügen einer Java main() Methode

  • Paketiere die Anwendung als ausführbares JAR

Die Klasse Application in Beispiel 4-6 bietet die Methode main(), die wir brauchen.

Beispiel 4-6. speakers-api/src/main/java/org/jsonatwork/ch4/Application.java
package org.jsonatwork.ch4;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

In diesem Beispiel registriert die Annotation @SpringBootApplication unsere Anwendung bei Spring und verdrahtet SpeakerController und Speaker.

Das ist der gesamte Code, den wir brauchen. Schauen wir uns jetzt das build.gradle-Skript an, um die Anwendung zu erstellen.

Das Build-Skript schreiben

Gradle verwendet ein Skript namens build.gradle, um eine Anwendung zu bauen. Beispiel 4-7 zeigt das Build-Skript für das Projekt speakers-api.

Beispiel 4-7. speakers-api/build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
  }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

ext {
  jdkVersion = "1.8"
}

sourceCompatibility = jdkVersion
targetCompatibility = jdkVersion

tasks.withType(JavaCompile) {
  options.encoding = 'UTF-8'
}

jar {
  baseName = 'speakers-api'
  version =  '0.0.1'
}

repositories {
  mavenCentral()
}

test {
  testLogging {
    showStandardStreams = true // Show standard output & standard error.
  }
  ignoreFailures = false
}

dependencies {
  compile (
    [group: 'org.springframework.boot', name: 'spring-boot-starter-web']
  )
}

Beachte das Folgende in diesem build.gradle-Skript:

  • Das Spring Boot Gradle Plug-in macht folgendes:

    • Packt alle Build-Artefakte in ein einziges, ausführbares JAR

    • Sucht nach einer Klasse in src/main/java, die eine main() Methode hat (in diesem Fall Application.java), um unsere API innerhalb des ausführbaren JARs einzusetzen.

  • Der jar Block definiert den Namen der JAR-Datei der Anwendung

  • repositories weist Gradle an, die Anwendungsabhängigkeiten aus dem Maven Central Repository zu beziehen.

  • testLogging weist Gradle an, bei der Ausführung von Tests die Standardausgabe und den Standardfehler anzuzeigen.

  • dependencies definiert die JARs, von denen die speakers-api abhängt.

Dies ist ein einfacher Build, aber Gradle hat weitaus leistungsfähigere Build-Funktionen. Besuche den Abschnitt "Wiring Gradle Build Scripts" im Gradle Benutzerhandbuch, um mehr zu erfahren.

Wir haben uns mit dem Build-Skript beschäftigt und jetzt ist es an der Zeit, die Speakers-API einzusetzen.

Die API bereitstellen

Das gradlew-Skript wurde mit dem Befehl gradle init erzeugt, mit dem das speakers-api-Projekt erstellt wurde. Mehr darüber, wie du ein Gradle-Projekt erstellst, erfährst du unter "Erstellen neuer Gradle-Builds" im Gradle-Benutzerhandbuch.

gradlew fasst alles zusammen und vereinfacht die Bereitstellung durch die folgenden Schritte:

  • Ruft das Skript build.gradle auf, um die Anwendung zu erstellen, und verwendet das Spring Boot-Plug-in, um das ausführbare JAR zu erstellen

  • Stellt die Speakers-API (als ausführbares JAR) auf http://localhost:8080/speakers auf einem eingebetteten (gebündelten) Tomcat-Server bereit

Führe im speakers-api-Verzeichnis ./gradlew bootRun aus, um die Anwendung bereitzustellen, und du wirst Folgendes sehen (am Ende aller Protokollmeldungen):

json 04in05

Teste die API mit Postman

Jetzt, wo die Speakers-API funktioniert, können wir mit Postman testen (wie in Kapitel 1), um die erste speaker zu erhalten. Gehe in der Postman-GUI wie folgt vor:

  1. Gib die URL http://localhost:8080/speakers/1 ein.

  2. Wähle GET als HTTP-Verb.

  3. Klicke auf die Schaltfläche Senden.

Du solltest sehen, dass GET in Postman ordnungsgemäß ausgeführt wurde, mit den speaker JSON-Daten im Textbereich des HTTP Response Body und einem 200 (OK) HTTP-Status, wie in Abbildung 4-1 gezeigt.

json 04in06
Abbildung 4-1. Lautsprecher-API auf Postman

Du kannst gradlew beenden, indem du in der Kommandozeile Strg-C drückst.

Wie versprochen, ist die Entwicklung und Bereitstellung einfacher, weil wir nichts von dem Folgenden getan haben:

  • XML-basierte Konfigurationsmetadaten für Spring oder Java EE (z.B. web.xml) erstellen oder ändern

  • Eine WAR-Datei bereitstellen

  • Tomcat installieren

Beachte, dass wir diese Bereitstellungsschritte gemacht haben, um zu zeigen, wie du eine einfache Entwicklungsumgebung für eine Web-API einrichtest. Wenn du in eine gemeinsam genutzte Umgebung (z. B. Staging, User Acceptance Testing, Production) wechselst, musst du immer noch eine WAR-Datei auf einem Anwendungsserver bereitstellen, damit du die Möglichkeit hast, die Anwendung zu optimieren und Lasttests durchzuführen.

Was wir abgedeckt haben

Wir begannen mit einer einfachen Konvertierung zwischen Java- und JSON-Konstrukten und zeigten dann, wie man eine (Stub) JSON-basierte Web-API aufruft und ihren Inhalt mit JUnit testet. Zum Schluss erstellten wir eine RESTful-API mit Spring Boot und testeten sie mit Postman.

Was kommt als Nächstes?

Nachdem wir die Grundlagen der JSON-Nutzung auf verschiedenen Kernplattformen (JavaScript, Ruby on Rails und Java) kennengelernt haben, werden wir in den nächsten drei Kapiteln tiefer in das JSON-Ökosystem einsteigen:

  • JSON-Schema

  • JSON Suche

  • JSON transformieren

In Kapitel 5 zeigen wir, wie du JSON-Dokumente mit JSON Schema strukturierst und validierst.

Get JSON bei der Arbeit now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.