1. 概述
本教程将带你探索 Play Framework,学习如何使用 Java 构建完整的 REST 服务。我们将实现一个学生信息管理的 REST API,支持创建、查询、更新和删除(CRUD)操作。
通常这类应用会使用数据库存储数据。Play Framework 内置了 H2 数据库,并支持 JPA/Hibernate 等持久化框架。但为了聚焦核心功能,我们采用简单的内存 Map 存储学生数据。
2. 创建新应用
确保已安装 Play Framework(参考Play Framework 入门指南),然后使用以下命令创建名为 student-api
的新项目:
sbt new playframework/play-java-seed.g8
3. 数据模型
在 student-api/app/models
目录下创建学生信息实体类:
public class Student {
private String firstName;
private String lastName;
private int age;
private int id;
// 标准构造方法、getter 和 setter
}
接着创建基于 HashMap
的简单数据存储类 StudentStore
,实现 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
。我们将逐步实现各功能模块。
4.1. 初始化配置
首先需要注入 HttpExecutionContext
。Play 的异步非阻塞特性要求 Action 方法返回 CompletionStage<Result>
而非普通 Result
,这能避免长时间任务阻塞线程。⚠️ 注意:必须提供 HTTP 执行上下文,否则会触发经典错误:"There is no HTTP Context available from here"。
private HttpExecutionContext ec;
private StudentStore studentStore;
@Inject
public StudentController(HttpExecutionContext ec, StudentStore studentStore) {
this.studentStore = studentStore;
this.ec = ec;
}
✅ 同时注入了 StudentStore
。Play 内置 Jackson 处理 JSON,可直接导入相关类。
创建工具类 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;
}
}
4.2. 创建学生接口
处理 POST 请求,新增学生记录:
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());
}
关键点:
- 从请求体获取 JSON 数据
- 使用
supplyAsync
实现异步处理 - 通过
Json.fromJson()
转换 JSON 到 Java 对象 - 返回 HTTP 201 状态码(
created()
)
4.3. 更新学生接口
处理 PUT 请求更新学生信息:
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.4. 查询学生接口
通过 GET 请求获取指定 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.5. 删除学生接口
处理 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.6. 学生列表接口
获取所有学生记录:
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
接口用于加载静态资源。
6. 接口测试
启动应用后访问 http://localhost:9000/
,初始返回空列表:
{
"isSuccessful":true,
"body":[]
}
使用 curl 或 Postman 测试各接口:
6.1. 创建学生
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
}
}
6.2. 查询列表
访问 http://localhost:9000/
:
{
"isSuccessful":true,
"body":[
{
"firstName":"John",
"lastName":"Baeldung",
"age":18,
"id":0
}
]
}
6.3. 删除学生
curl -X DELETE http://localhost:9000/0
{
"isSuccessful":true,
"body":"Student with id:0 deleted"
}
6.4. 更新学生
先重新创建学生:
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"Baeldung","age": 18}' \
http://localhost:9000/
更新记录:
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
}
}
6.5. 批量测试
添加更多测试数据:
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
}
]
}
7. 总结
本文展示了如何使用 Play Framework 快速构建完整的 REST API。核心要点包括:
- ✅ 异步非阻塞编程模型
- ✅ 内置 JSON 处理能力
- ✅ 简洁的路由配置
- ✅ 模块化的控制器设计
完整源代码可在 GitHub 获取。实际生产环境中建议替换内存存储为真实数据库,并添加认证授权等安全机制。