REST-API mit Play Framework in Java

1. Übersicht

Der Zweck dieses Tutorials besteht darin, das Play Framework zu erkunden und zu lernen, wie Sie mit Java REST-Services damit erstellen.

Wir werden eine REST-API zusammenstellen, um Schülerdatensätze zu erstellen, abzurufen, zu aktualisieren und zu löschen.

In solchen Anwendungen hätten wir normalerweise eine Datenbank zum Speichern von Schülerdatensätzen. Das Play Framework verfügt über eine integrierte H2-Datenbank sowie Unterstützung für JPA mit Hibernate und anderen Persistenz-Frameworks.

Um die Dinge einfach zu halten und sich auf das Wichtigste zu konzentrieren, verwenden wir eine einfache Karte, um Schülerobjekte mit eindeutigen IDs zu speichern.

2. Erstellen Sie eine neue Anwendung

Sobald wir das Play Framework wie in unserer Einführung in das Play Framework beschrieben installiert haben, können wir unsere Anwendung erstellen.

Verwenden Sie den Befehl sbt , um mit play-java-seed eine neue Anwendung namens student-api zu erstellen :

sbt new playframework/play-java-seed.g8

3. Modelle

Navigieren wir mit unserem Anwendungsgerüst zu student-api / app / models und erstellen eine Java-Bean für den Umgang mit Schülerinformationen:

public class Student { private String firstName; private String lastName; private int age; private int id; // standard constructors, getters and setters }

Wir erstellen jetzt einen einfachen Datenspeicher - unterstützt von einer HashMap - für Schülerdaten mit Hilfsmethoden zum Ausführen von CRUD-Operationen:

public class StudentStore { private Map students = new HashMap(); public Optional addStudent(Student student) { int id = students.size(); student.setId(id); students.put(id, student); return Optional.ofNullable(student); } public Optional getStudent(int id) { return Optional.ofNullable(students.get(id)); } public Set getAllStudents() { return new HashSet(students.values()); } public Optional updateStudent(Student student) { int id = student.getId(); if (students.containsKey(id)) { students.put(id, student); return Optional.ofNullable(student); } return null; } public boolean deleteStudent(int id) { return students.remove(id) != null; } }

4. Controller

Gehen wir zu student-api / app / controller und erstellen einen neuen Controller namens StudentController.java . Wir werden den Code schrittweise durchgehen.

Zunächst müssen wir einen HttpExecutionContext konfigurieren . Wir werden unsere Aktionen mit asynchronem, nicht blockierendem Code implementieren. Dies bedeutet, dass unsere Aktionsmethoden CompletionStage anstelle von nur Result zurückgeben . Dies hat den Vorteil, dass wir lang laufende Aufgaben schreiben können, ohne sie zu blockieren.

Beim Umgang mit asynchroner Programmierung in einem Play Framework-Controller gibt es nur eine Einschränkung: Wir müssen einen HttpExecutionContext bereitstellen. Wenn wir den HTTP-Ausführungskontext nicht angeben, wird beim Aufrufen der Aktionsmethode der berüchtigte Fehler "Von hier ist kein HTTP-Kontext verfügbar" angezeigt.

Lassen Sie es uns injizieren:

private HttpExecutionContext ec; private StudentStore studentStore; @Inject public StudentController(HttpExecutionContext ec, StudentStore studentStore) { this.studentStore = studentStore; this.ec = ec; }

Beachten Sie, dass wir auch den StudentStore hinzugefügt und beide Felder mithilfe der Annotation @Inject in den Konstruktor des Controllers eingefügt haben . Nachdem dies geschehen ist, können wir nun mit der Implementierung der Aktionsmethoden fortfahren.

Beachten Sie, dass Play mit Jackson geliefert wird, um die Datenverarbeitung zu ermöglichen. So können wir alle benötigten Jackson-Klassen ohne externe Abhängigkeiten importieren.

Definieren wir eine Utility-Klasse, um sich wiederholende Operationen auszuführen. In diesem Fall werden HTTP-Antworten erstellt.

Erstellen wir also das Paket student-api / app / utils und fügen Util.java hinzu :

public class Util { public static ObjectNode createResponse(Object response, boolean ok) { ObjectNode result = Json.newObject(); result.put("isSuccessful", ok); if (response instanceof String) { result.put("body", (String) response); } else { result.putPOJO("body", response); } return result; } }

Mit dieser Methode erstellen wir Standard-JSON-Antworten mit einem booleschen isSuccessful- Schlüssel und dem Antworttext.

Wir können jetzt die Aktionen der Controller-Klasse schrittweise ausführen.

4.1. Die Aktion erstellen

Diese als POST- Aktion zugeordnete Methode behandelt die Erstellung des Student- Objekts:

public CompletionStage create(Http.Request request) { JsonNode json = request.body().asJson(); return supplyAsync(() -> { if (json == null) { return badRequest(Util.createResponse("Expecting Json data", false)); } Optional studentOptional = studentStore.addStudent(Json.fromJson(json, Student.class)); return studentOptional.map(student -> { JsonNode jsonObject = Json.toJson(student); return created(Util.createResponse(jsonObject, true)); }).orElse(internalServerError(Util.createResponse("Could not create data.", false))); }, ec.current()); }

Wir verwenden einen Aufruf von der injizierten Http.Request- Klasse, um den Anforderungshauptteil in Jacksons JsonNode- Klasse zu übertragen. Beachten Sie, wie wir die Utility-Methode verwenden, um eine Antwort zu erstellen, wenn der Body null ist .

Wir geben auch eine CompletionStage zurück , mit der wir nicht blockierenden Code mit der CompletedFuture.supplyAsync- Methode schreiben können .

Wir können ihm einen beliebigen String oder einen JsonNode sowie ein boolesches Flag übergeben, um den Status anzuzeigen.

Beachten Sie auch, wie wir Json.fromJson () verwenden , um das eingehende JSON-Objekt in ein Student- Objekt und für die Antwort zurück in JSON zu konvertieren .

Schließlich verwenden wir anstelle von ok (), an das wir gewöhnt sind, die erstellte Hilfsmethode aus dem Paket play.mvc.results . Die Idee ist, eine Methode zu verwenden, die den korrekten HTTP-Status für die Aktion angibt, die in einem bestimmten Kontext ausgeführt wird. Beispiel: ok () für den HTTP-Status OK 200 und create (), wenn HTTP CREATED 201 der oben verwendete Ergebnisstatus ist. Dieses Konzept wird im weiteren Verlauf der Aktionen zur Sprache kommen.

4.2. Die Update- Aktion

Eine PUT- Anforderung an // localhost: 9000 / trifft den StudentController. Update - Methode, die die Schüler Informationen aktualisiert , indem Sie die Aufruf updateStudent Methode des StudentStor :

public CompletionStage update(Http.Request request) { JsonNode json = request.body().asJson(); return supplyAsync(() -> { if (json == null) { return badRequest(Util.createResponse("Expecting Json data", false)); } Optional studentOptional = studentStore.updateStudent(Json.fromJson(json, Student.class)); return studentOptional.map(student -> { if (student == null) { return notFound(Util.createResponse("Student not found", false)); } JsonNode jsonObject = Json.toJson(student); return ok(Util.createResponse(jsonObject, true)); }).orElse(internalServerError(Util.createResponse("Could not create data.", false))); }, ec.current()); }

4.3. Die abrufen Aktion

Um einen Schüler abzurufen, übergeben wir die ID des Schülers als Pfadparameter in einer GET- Anforderung an // localhost: 9000 /: id . Dies wird die Abrufaktion treffen :

public CompletionStage retrieve(int id) { return supplyAsync(() -> { final Optional studentOptional = studentStore.getStudent(id); return studentOptional.map(student -> { JsonNode jsonObjects = Json.toJson(student); return ok(Util.createResponse(jsonObjects, true)); }).orElse(notFound(Util.createResponse("Student with id:" + id + " not found", false))); }, ec.current()); }

4.4. Die Löschaktion

Die Löschaktion ist // localhost: 9000 /: id zugeordnet . Wir geben die ID an, um zu identifizieren, welcher Datensatz gelöscht werden soll:

public CompletionStage delete(int id) { return supplyAsync(() -> { boolean status = studentStore.deleteStudent(id); if (!status) { return notFound(Util.createResponse("Student with id:" + id + " not found", false)); } return ok(Util.createResponse("Student with id:" + id + " deleted", true)); }, ec.current()); }

4.5. The listStudents Action

Finally, the listStudents action returns a list of all the students that have been stored so far. It's mapped to //localhost:9000/ as a GET request:

public CompletionStage listStudents() { return supplyAsync(() -> { Set result = studentStore.getAllStudents(); ObjectMapper mapper = new ObjectMapper(); JsonNode jsonData = mapper.convertValue(result, JsonNode.class); return ok(Util.createResponse(jsonData, true)); }, ec.current()); }

5. Mappings

Having set up our controller actions, we can now map them by opening the file student-api/conf/routes and adding these routes:

GET / controllers.StudentController.listStudents() GET /:id controllers.StudentController.retrieve(id:Int) POST / controllers.StudentController.create(request: Request) PUT / controllers.StudentController.update(request: Request) DELETE /:id controllers.StudentController.delete(id:Int) GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

The /assets endpoint must always be present for downloading static resources.

After this, we're done with building the Student API.

To learn more about defining route mappings, visit our Routing in Play Applications tutorial.

6. Testing

We can now run tests on our API by sending requests to //localhost:9000/ and adding the appropriate context. Running the base path from the browser should output:

{ "isSuccessful":true, "body":[] }

As we can see, the body is empty since we haven't added any records yet. Using curl, let's run some tests (alternatively, we can use a REST client like Postman).

Let's open up a terminal window and execute the curl command to add a student:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Baeldung","age": 18}' \ //localhost:9000/

This will return the newly created student:

{ "isSuccessful":true, "body":{ "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } }

After running the above test, loading //localhost:9000 from the browser should now give us:

{ "isSuccessful":true, "body":[ { "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } ] } 

The id attribute will be incremented for every new record we add.

To delete a record we send a DELETE request:

curl -X DELETE //localhost:9000/0 { "isSuccessful":true, "body":"Student with id:0 deleted" } 

In the above test, we delete the record created in the first test, now let's create it again so that we can test the update method:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Baeldung","age": 18}' \ //localhost:9000/ { "isSuccessful":true, "body":{ "firstName":"John", "lastName":"Baeldung", "age":18, "id":0 } }

Let's now update the record by setting the first name to “Andrew” and age to 30:

curl -X PUT -H "Content-Type: application/json" \ -d '{"firstName":"Andrew","lastName":"Baeldung","age": 30,"id":0}' \ //localhost:9000/ { "isSuccessful":true, "body":{ "firstName":"Andrew", "lastName":"Baeldung", "age":30, "id":0 } }

The above test demonstrates the change in the value of the firstName and age fields after updating the record.

Let's create some extra dummy records, we'll add two: John Doe and Sam Baeldung:

curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"John","lastName":"Doe","age": 18}' \ //localhost:9000/
curl -X POST -H "Content-Type: application/json" \ -d '{"firstName":"Sam","lastName":"Baeldung","age": 25}' \ //localhost:9000/

Now, let's get all the records:

curl -X GET //localhost:9000/ { "isSuccessful":true, "body":[ { "firstName":"Andrew", "lastName":"Baeldung", "age":30, "id":0 }, { "firstName":"John", "lastName":"Doe", "age":18, "id":1 }, { "firstName":"Sam", "lastName":"Baeldung", "age":25, "id":2 } ] }

With the above test, we are ascertaining the proper functioning of the listStudents controller action.

7. Conclusion

In diesem Artikel haben wir gezeigt, wie Sie mit dem Play Framework eine vollwertige REST-API erstellen.

Wie üblich ist der Quellcode für dieses Tutorial auf GitHub verfügbar.