1. 概述
本教程旨在探索 Play 框架並學習如何使用 Java 構建 RESTful 服務。
我們將構建一個 REST API,用於創建、檢索、更新和刪除學生記錄。
在諸如此類應用程序中,通常會有數據庫來存儲學生記錄。Play 框架內置了 H2 數據庫,並支持與 JPA 和 Hibernate 等持久化框架的集成。
為了保持簡單並專注於最重要的內容,我們將使用簡單的映射來存儲學生對象,並帶有唯一的 ID。
2. 創建新應用程序
安裝了 Play Framework,如介紹中所述,我們現在可以創建我們的應用程序。
使用 <em sbt</em> 命令,創建一個名為 <em student-api</em> 的新應用程序,並使用 <em play-java-seed</em>:
sbt new playframework/play-java-seed.g83. 模型
有了我們的應用程序腳手架搭建完成,請導航到 student-api/app/models 並創建一個 Java Bean,用於處理學生信息:
public class Student {
private String firstName;
private String lastName;
private int age;
private int id;
// standard constructors, getters and setters
}我們現在將創建一個簡單的存儲數據 – 採用 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. 控制器
讓我們前往 student-api/app/controllers 並創建一個名為 StudentController.java 的新控制器。 我們將逐步通過代碼進行分析。
首先,我們需要配置 HttpExecutionContext. 我們將使用異步、非阻塞的代碼實現我們的操作。 這意味着我們的操作方法將返回 CompletionStage<Result> 而不是僅僅返回 Result。 這有益於我們能夠編寫長時間運行的任務,而無需阻塞。
在處理 Play Framework 控制器中的異步編程時,有一個需要注意的限制:我們必須提供 HttpExecutionContext。 如果我們沒有提供 HTTP 執行上下文,在調用操作方法時,將會收到“此處沒有可用的 HTTP 上下文”的臭名昭著的錯誤。
讓我們注入它:
private HttpExecutionContext ec;
private StudentStore studentStore;
@Inject
public StudentController(HttpExecutionContext ec, StudentStore studentStore) {
this.studentStore = studentStore;
this.ec = ec;
}請注意,我們還添加了 StudentStore 並使用 @Inject 註解在控制器構造函數中注入了這兩個字段。 經過此操作,我們現在可以繼續實現 action 方法的實現。
請注意, Play 附帶 Jackson 以允許進行數據處理 – 因此,我們可以導入我們需要的任何 Jackson 類,而無需外部依賴。
讓我們定義一個實用類來執行重複操作。 在這種情況下,構建 HTTP 響應。
因此,讓我們創建 student-api/app/utils 包並在此處添加 Util.java :
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;
}
}使用這種方法,我們將創建標準的 JSON 響應,其中包含一個布爾值 isSuccessful 鍵以及響應體。
現在我們可以逐步跟蹤控制器類的操作。
4.1. 創建操作
映射為 操作,此方法處理 對象的創建:
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());
}我們使用注入的 <em>Http.Request</em> 類獲取請求體,並將其轉換為 Jackson 的 <em>JsonNode</em> 類。請注意,我們使用該實用方法來創建響應,如果體為空值 <em>null</em> 時,會創建響應。
我們還返回一個 <em>CompletionStage<Result></em> 反應,這使得我們能夠使用CompletedFuture.supplyAsync` 方法編寫非阻塞代碼。
我們可以將任何 <em>String</em> 或 <em>JsonNode</em> 傳遞給它,以及一個 <em>boolean</em> 標誌,用於指示狀態。
請注意,我們使用 <em>Json.fromJson()</em> 將傳入的 JSON 對象轉換為 <em>Student</em> 對象,並將結果轉換為 JSON 以供響應使用。
最後,我們不再使用 <em>ok()</em> 方法,而是使用 <em>play.mvc.results</em> 包中的 <em>created</em> 輔助方法。 這種方法的想法是使用一個方法,該方法為特定上下文中執行的動作提供正確的 HTTP 狀態。 例如,對於 HTTP OK 200 狀態,使用 <em>ok()</em> ;對於 HTTP CREATED 201 狀態(如上所示),使用created()。 這種概念將在後續操作中出現。
4.2. 更新操作
當對 HTTP 請求(PUT 請求)發送到 http://localhost:9000/</em/> 時,它會觸發 StudentController</em/> 中的 update</em/> 方法,該方法通過調用 updateStudent</em/> 方法來更新 StudentStore</em/> 中的學生信息。
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. 檢索操作
為了檢索一名學生,我們將學生的 ID 作為路徑參數傳遞到 GET 請求中,請求地址為 http://localhost:9000/:id。 這將觸發 檢索操作:
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. 刪除操作
“刪除”操作映射到 http://localhost:9000/:id。 我們通過提供 id 來標識要刪除的記錄:
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. listStudents 操作
最後,listStudents 操作返回迄今為止存儲的所有學生的列表。它被映射到 http://localhost:9000/ 作為一個 GET 請求:
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. 測試
我們可以通過向 http://localhost:9000/ 發送請求來對我們的 API 進行測試,並添加適當的上下文。 從瀏覽器中運行基本路徑應會輸出:
{
"isSuccessful":true,
"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"
}
在上述測試中,我們刪除了第一個測試中創建的記錄,現在我們來再次創建它,以便測試更新方法:
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
}
}現在,讓我們通過將名字(first name)設置為“Andrew”和年齡設置為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
}
}上述測試演示了在更新記錄後,firstName 和 age 字段值的變化。
讓我們創建一些額外的佔位記錄,我們將添加兩個: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。