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
odergradle 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:
-
Gradle Beyond the Basics, von Tim Berglund (O'Reilly)
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 aufWriter
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:
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 einSpeaker
Objekt und serialisiert es auf die Standardausgabe, indem es die MethodeObjectMapper.writeValueAsString()
verwendet undSystem.out.println()
. Der Test setztSerializationFeature.INDENT_OUTPUT
auftrue
, um die JSON-Ausgabe einzurücken/auszudrucken. -
deSerializeObject()
ruftgetSpeakerFile()
auf, um eine JSON-Eingabedatei zu lesen (die ein einzelnesspeaker
JSON-Objekt enthält), und verwendet die MethodeObjectMapper.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 JSONspeaker
Objekten enthält. -
Ruft die Methode
ObjectMapper.readTree()
auf, um einJsonNode
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 einSpeaker
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 aktuellenThread
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:
Jackson bietet viel mehr Funktionen, als in diesem Kapitel gezeigt werden können. In den folgenden Ressourcen findest du einige tolle Tutorials:
-
Java Jackson Tutorial, von Eugen Paraschiv
-
Jackson Tutorial, Tutorials Punkt
-
Jackson JSON Java Parser API Example Tutorial, von Pankaj (JournalDev)
-
Java JSON Jackson Einführung, von Mithil Shah
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
undregistered
) in jedemspeaker
Objekt entspricht den erwarteten Werten.
-
-
Wenn du
gradle test
aufrufst, solltest du Folgendes als Teil der Ausgabe sehen:
-
-
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:
-
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:
-
Schreibe Quellcode:
-
Modell
-
Controller
-
Bewerbung
-
-
Erstelle ein Build-Skript(build.gradle).
-
Setze ein eingebettetes JAR mit
gradlew
ein. -
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 KlasseSpeakerController
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 diespeakers
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 alsArrayList
ab und gibt es als JSON Array in einem HTTP Response Body zurück. -
Die
@RequestMapping
Annotation bindet die /speakers URI an diegetAllSpeakers()
Methode für eine HTTPGET
Anfrage.
-
-
Die Methode
getSpeakerById()
funktioniert folgendermaßen:-
Antwortet auf HTTP
GET
Anfragen auf die /speakers/{id} URI (wobeiid
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 denid
Parameter für die Suche. -
Mit dem Rückgabewerttyp
ResponseEntity
kannst du den HTTP-Statuscode und/oder diespeakers
in 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:
-
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):
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:
-
Gib die URL http://localhost:8080/speakers/1 ein.
-
Wähle
GET
als HTTP-Verb. -
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.
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 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.