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 获取。实际生产环境中建议替换内存存储为真实数据库,并添加认证授权等安全机制。


原始标题:REST API with Play Framework in Java