REST-API mit Play Framework in Java

REST-API mit Play Framework in Java

1. Überblick

Der Zweck dieses Tutorials ist es, das Play Framework zu erkunden und zu lernen, wie man REST-Services mit Java erstellt.

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

In solchen Anwendungen verfügen wir normalerweise über 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 inIntroduction to the Play Frameworkbeschrieben installiert haben, können wir unsere Anwendung erstellen.

Verwenden Sie den Befehlsbt, um mitplay-java-seed eine neue Anwendung namensstudent-api zu erstellen:

sbt new playframework/play-java-seed.g8

3. Modelle

Navigieren wir mit unserem Anwendungsgerüst zustudent-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 durch einHashMap – 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 zustudent-api/app/controllers und erstellen einen neuen Controller namensStudentController.java. Wir werden den Code schrittweise durchgehen.

Zunächst müssen wirconfigure an *HttpExecutionContext*. ausführen. Wir implementieren unsere Aktionen mit asynchronem, nicht blockierendem Code. Dies bedeutet, dass unsere AktionsmethodenCompletionStage<Result> anstelle von nurResult zurückgeben. Dies hat den Vorteil, dass wir langfristige 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üssenHttpExecutionContext. angeben. Wenn wir den HTTP-Ausführungskontext nicht angeben, wird der berüchtigte Fehler „Es gibt keinen HTTP-Kontext verfügbar von hier “beim Aufrufen der Aktionsmethode.

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 denStudentStore -Sand hinzugefügt haben, der beide Felder im Konstruktor des Controllers mithilfe der@Inject -Sannotation injiziert hat. Danach können wir mit der Implementierung der Aktionsmethoden fortfahren.

Beachten Sie, dassPlay ships with Jackson to allow for data processing - damit wir alle benötigten Jackson-Klassen ohne externe Abhängigkeiten importieren können.

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

Erstellen wir also das Paketstudent-api/app/utilsund fügenUtil.javahinzu:

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 booleschenisSuccessful-Schlüssel und dem Antworttext.

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

4.1. Die Aktioncreate

Diese Methode wird alsPOST -Saction zugeordnet und behandelt die Erstellung desStudent-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 injiziertenHttp.Request -Skala, um den Anforderungshauptteil in JacksonsJsonNode-Klasse zu bringen. Beachten Sie, wie wir die Utility-Methode verwenden, um eine Antwort zu erstellen, wenn der Bodynull ist.

Wir geben auch einCompletionStage<Result> zurück, wodurch wir nicht blockierenden Code mit derCompletedFuture.supplyAsync -Smethod schreiben können.

Wir können jedesString oderJsonNode zusammen mit einemboolean-Flag übergeben, um den Status anzuzeigen.

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

Schließlich verwenden wir anstelle vonok(), an die wir gewöhnt sind, die Hilfsmethodecreated aus dem Paketplay.mvc.results. Die Idee ist, eine Methode zu verwenden, die den korrekten HTTP-Status für die in einem bestimmten Kontext ausgeführte Aktion angibt. Beispiel:ok() für den Status HTTP OK 200 undcreated(), wenn HTTP CREATED 201 der oben verwendete Ergebnisstatus ist. Dieses Konzept wird in den restlichen Aktionen berücksichtigt.

4.2. Die Aktionupdate

EinePUT -Anforderung anhttp://localhost:9000/ trifft dieStudentController.update-Methode, die die Schülerinformationen aktualisiert, indem dieupdateStudent -Smethod derStudentStore aufgerufen wird:

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 Aktionretrieve

Um einen Schüler abzurufen, übergeben wir die ID des Schülers als Pfadparameter in einerGET -Anforderung anhttp://localhost:9000/:id. Dies trifft dieretrieve -Saktion:

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 Aktiondelete

Diedelete -Saktion wird aufhttp://localhost:9000/:id abgebildet. Wir geben dieid 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. Die AktionlistStudents

Schließlich gibt die AktionlistStudentseine Liste aller Schüler zurück, die bisher gespeichert wurden. Es wirdhttp://localhost:9000/ alsGET-Anforderung zugeordnet:

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. Zuordnungen

Nachdem wir unsere Controller-Aktionen eingerichtet haben, können wir sie jetzt zuordnen, indem wir die Dateistudent-api/conf/routes öffnen und die folgenden Routen hinzufügen:

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)

Der Endpunkt von/assetsmuss immer vorhanden sein, damit statische Ressourcen heruntergeladen werden können.

Danach sind wir mit dem Erstellen derStudent-API fertig.

Weitere Informationen zum Definieren von Routenzuordnungen finden Sie in unserem Tutorial zuRouting in Play Applications.

6. Testen

Wir können jetzt Tests für unsere API ausführen, indem wir Anforderungen anhttp://localhost:9000/ senden und den entsprechenden Kontext hinzufügen. Das Ausführen des Basispfads über den Browser sollte Folgendes ausgeben:

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

Wie wir sehen können, ist der Körper leer, da wir noch keine Datensätze hinzugefügt haben. Lassen Sie uns mitcurl einige Tests ausführen (alternativ können wir einen REST-Client wie Postman verwenden).

Öffnen Sie ein Terminalfenster und führen Sie den Befehl curl aufadd a student aus:

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

Dies gibt den neu erstellten Schüler zurück:

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

Nach dem Ausführen des obigen Tests sollte das Laden vonhttp://localhost:9000 aus dem Browser nun Folgendes ergeben:

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

Das Attributidwird für jeden neuen Datensatz, den wir hinzufügen, erhöht.

Andelete a record senden wir eineDELETE Anfrage:

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

Im obigen Test löschen wir den im ersten Test erstellten Datensatz. Lassen Sie uns nuncreate it again so that we can test the update method:

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

Lassen Sie uns jetztupdate the record, indem Sie den Vornamen auf "Andrew" und das Alter auf 30 setzen:

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

Der obige Test zeigt die Änderung des Werts der FelderfirstName andagenach Aktualisierung des Datensatzes.

Lassen Sie uns einige zusätzliche Dummy-Datensätze erstellen. Wir werden zwei hinzufügen: John Doe und Sam Beispiel:

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

Lassen Sie uns nun alle Aufzeichnungen abrufen:

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

Mit dem obigen Test stellen wir die ordnungsgemäße Funktion der Regleraktion vonlistStudentsicher.

7. Fazit

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 Tutorialover on GitHub verfügbar.