Java中使用Play Framework構建REST API

REST
Remote
1
02:47 PM · Dec 01 ,2025

1. 概述

本教程旨在探索 Play 框架並學習如何使用 Java 構建 RESTful 服務

我們將構建一個 REST API,用於創建、檢索、更新和刪除學生記錄

在諸如此類應用程序中,我們通常會有一個數據庫來存儲學生記錄。 Play 框架內置了 H2 數據庫,並支持與 JPA 和 Hibernate 等持久化框架的集成

為了保持簡單並專注於最重要的內容,我們將使用簡單的映射來存儲學生對象,其中包含唯一的 ID

2. 創建一個新的應用程序

在我們安裝了 Play Framework,如介紹中所述,之後,我們準備好創建我們的應用程序。

我們使用 sbt 命令來創建一個名為 student-api 的新應用程序,使用 play-java-seed

sbt new playframework/play-java-seed.g8

3. 模型有了我們的應用程序腳手架搭建好,請導航到 student-api/app/models,並創建一個 Java Bean 來處理學生信息:

public class Student {
    private String firstName;
    private String lastName;
    private int age;
    private int id;

    // 標準構造函數、獲取器和設置器
}

現在我們將創建一個簡單的存儲 – 背後使用 HashMap – 來存儲學生數據,並提供輔助方法來執行 CRUD 操作:

public class StudentStore {
    private Map<Integer, Student> students = new HashMap<>();

    public Optional<Student> addStudent(Student student) {
        int id = students.size();
        student.setId(id);
        students.put(id, student);
        return Optional.ofNullable(student);
    }

    public Optional<Student> getStudent(int id) {
        return Optional.ofNullable(students.get(id));
    }

    public Set<Student> getAllStudents() {
        return new HashSet<>(students.values());
    }

    public Optional<Student> 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. Controllers

Let’s head over to student-api/app/controllers and create a new controller called StudentController.java. We’ll step through the code incrementally.

First off, we need to configure an HttpExecutionContext.. We’ll implement our actions using asynchronous, non-blocking code. This means that our action methods will return CompletionStage<Result> instead of just Result. This has the benefit of allowing us to write long-running tasks without blocking.

There is just one caveat when dealing with asynchronous programming in a Play Framework controller: we have to provide an HttpExecutionContext. If we don’t supply the HTTP execution context, we’ll get the infamous error “There is no HTTP Context available from here” when calling the action method.

Let’s inject it:

private HttpExecutionContext ec;
private StudentStore studentStore;

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

Notice we have also added the StudentStore and injected both fields in the constructor of the controller using the @Inject annotation. Having done this, we can now proceed with the implementation of the action methods.

Note that Play ships with Jackson to allow for data processing – so we can import any Jackson classes we need without external dependencies.

Let’s define a utility class to perform repetitive operations. In this case, building HTTP responses.

So let’s create student-api/app/utils package and add Util.java in it:

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;
    }
}

With this method, we’ll be creating standard JSON responses with a boolean isSuccessful key and the response body.

We can now step through the actions of the controller class.

4.1. The create Action

Mapped as a POST action, this method handles the creation of the Student object:

public CompletionStage<Result> create(Http.Request request) {
    JsonNode json = request.body().asJson();
    return supplyAsync(() -> {
        if (json == null) {
            return badRequest(Util.createResponse("Expecting Json data", false));
        }

        Optional<Student> 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());
}

We use a call from the injected Http.Request class to get the request body into Jackson’s JsonNode class. Notice how we use the utility method to create a response if the body is null.

We’re also returning a CompletionStage<Result>, which enables us to write non-blocking code using the CompletedFuture.supplyAsync method.

We can pass to it any String or a JsonNode, along with a boolean flag to indicate status.

Notice also how we use Json.fromJson() to convert the incoming JSON object into a Student object and back to JSON for the response.

Finally, instead of ok() which we are used to, we are using the created helper method from the play.mvc.results package. The idea is to use a method that gives the correct HTTP status for the action being performed within a particular context. For example, ok() for HTTP OK 200 status, and created() when HTTP CREATED 201 is the result status as used above. This concept will come up throughout the rest of the actions.

4.2. The update Action

A PUT request to http://localhost:9000/ hits the StudentController.update method, which updates the student information by calling the updateStudent method of the StudentStore:

public CompletionStage<Result> update(Http.Request request) {
    JsonNode json = request.body().asJson();
    return supplyAsync(() -> {
        if (json == null) {
            return badRequest(Util.createResponse("Expecting Json data", false));
        }
        Optional<Student> 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. The retrieve Action

To retrieve a student, we pass in the id of the student as a path parameter in a GET request to http://localhost:9000/:id. This will hit the retrieve action:

public CompletionStage<Result> retrieve(int id) {
    return supplyAsync(() -> {
        final Optional<Student> 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. The delete Action

The delete action is mapped to http://localhost:9000/:id. We supply the id to identify which record to delete:

public CompletionStage<Result> 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 http://localhost:9000/ as a GET request:

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

5. 映射

在配置好我們的控制器動作之後,我們可以通過打開文件 student-api/conf/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)

/assets 端點 必須始終存在以下載靜態資源。

之後,我們完成了構建 Student API 的工作。

要了解更多關於定義路由映射的信息,請訪問我們的“Play 應用程序中的路由”教程。

6. Testing

我們可以現在通過向 http://localhost:9000/

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

正如我們所見,由於我們尚未添加任何記錄,因此 body 為空。 使用 curl,讓我們運行一些測試(或者我們可以使用像 Postman 這樣的 REST 客户端)。

讓我們打開一個終端窗口並執行 curl 命令來 添加一個學生:

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

這將返回新創建的學生:

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

在運行上述測試後,從瀏覽器中加載 http://localhost:9000> 應該現在給我們:

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

id 屬性將為每個新記錄增加。

刪除一個記錄,我們發送一個 DELETE 請求:

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

在上述測試中,我們刪除了第一個測試中創建的記錄,現在讓我們 update 方法:

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

現在,讓我們通過將firstName 設置為“Andrew”和age 設置為 30 來 更新記錄:

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

上述測試演示了firstNameage 字段的值在更新記錄後發生變化。

讓我們添加兩個額外的佔位記錄:John Doe 和 Sam Baeldung:

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":"Baeldung","age": 25}' \
http://localhost:9000/

現在,讓我們獲取所有記錄:

curl -X GET http://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
        }
    ]
}

通過上述測試,我們正在驗證listStudents 控制器方法的正確功能。

7. 結論

在本文中,我們展示瞭如何使用 Play 框架構建一個完整的 REST API。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.