1. Обзор
Цель этого урока-изучить платформу Play и узнать, как создавать с ее помощью службы REST с использованием Java.
Мы создадим REST API для создания, извлечения, обновления и удаления записей учащихся.
В таких приложениях у нас обычно есть база данных для хранения записей студентов. Платформа Play имеет встроенную базу данных H2, а также поддержку JPA с Hibernate и другими фреймворками персистентности.
Однако, чтобы все было просто и сосредоточиться на самых важных вещах, мы будем использовать простую карту для хранения объектов учащихся с уникальными идентификаторами.
2. Создайте новое приложение
Как только мы установили платформу Play , как описано в нашем введении в платформу Play, мы готовы создать наше приложение.
Давайте используем команду sbt для создания нового приложения под названием student-api с помощью play-java-seed :
sbt new playframework/play-java-seed.g8
3. Модели
Теперь, когда наши строительные леса приложений установлены, давайте перейдем к student-api/app/models и создадим Javabean для обработки информации о студентах:
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 Mapstudents = 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. Контроллеры
Давайте перейдем к student-api/app/controllers и создадим новый контроллер под названием StudentController.java . Мы будем постепенно проходить через код.
Во-первых, нам нужно настроить HttpExecutionContext . Мы будем реализовывать наши действия с помощью асинхронного, неблокирующего кода. Это означает, что наши методы действий будут возвращать CompletionStage вместо просто Result . Это имеет то преимущество, что позволяет нам писать длительные задачи без блокировки.
Есть только одно предостережение при работе с асинхронным программированием в контроллере Play Framework: мы должны предоставить HttpExecutionContext. Если мы не предоставим HTTPexecutioncontext, мы получим печально известную ошибку “Здесь нет контекста HTTP” при вызове метода действия.
Давайте введем его:
private HttpExecutionContext ec; private StudentStore studentStore; @Inject public StudentController(HttpExecutionContext ec, StudentStore studentStore) { this.studentStore = studentStore; this.ec = ec; }
Обратите внимание, что мы также добавили Student Store и ввели оба поля в конструктор контроллера с помощью аннотации @Inject . Сделав это, мы теперь можем приступить к реализации методов действий.
Обратите внимание, что Play поставляется с Джексоном, чтобы обеспечить обработку данных – поэтому мы можем импортировать любые классы Джексона, которые нам нужны, без внешних зависимостей.
Давайте определим класс утилиты для выполнения повторяющихся операций. В этом случае построение 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. Действие создать
Сопоставленный как действие POST , этот метод обрабатывает создание объекта Student :
public CompletionStagecreate(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()); }
Мы используем вызов из введенного класса Http.Request , чтобы получить тело запроса в класс JsonNode Джексона. Обратите внимание, как мы используем метод утилиты для создания ответа, если тело null .
Мы также возвращаем CompletionStage , который позволяет нам писать неблокирующий код, используя метод Completed Future.supplyAsync .
Мы можем передать ему любую строку или JsonNode вместе с логическим флагом для указания статуса.
Обратите внимание также, как мы используем Json.из Json() для преобразования входящего объекта JSON в объект Student и обратно в JSON для ответа.
Наконец, вместо ok () , к которому мы привыкли, мы используем созданный вспомогательный метод из пакета play.mvc.results . Идея состоит в том, чтобы использовать метод, который дает правильный статус HTTP для действия, выполняемого в определенном контексте. Например, ok() для состояния HTTP OK 200 и created() когда HTTP CREATED 201-это состояние результата, как указано выше. Эта концепция будет возникать на протяжении всех остальных действий.
4.2. Действие по обновлению
A PUT запрос на http://localhost:9000/ попадает в контроллер Student. update метод, который обновляет информацию о студенте, вызывая updateStudent метод StudentStore :
public CompletionStageupdate(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. Действие извлечения
Чтобы получить студента, мы передаем идентификатор студента в качестве параметра пути в запросе GET to http://localhost:9000/:id . Это приведет к действию retrieve :
public CompletionStageretrieve(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. Действие удаления
Действие удалить сопоставляется с http://localhost:9000/:id . Мы указываем id , чтобы определить, какую запись следует удалить:
public CompletionStagedelete(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. Список Действий Студентов
Наконец, действие list Students возвращает список всех студентов, которые были сохранены до сих пор. Он сопоставлен с http://localhost:9000/ как ПОЛУЧИТЬ запрос:
public CompletionStagelistStudents() { 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. Сопоставления
Настроив наши действия контроллера, мы теперь можем сопоставить их, открыв файл 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)
То /активы конечная точка всегда должна присутствовать для загрузки статических ресурсов.
После этого мы закончим с созданием API Student .
Чтобы узнать больше об определении сопоставлений маршрутов, посетите наш учебник по маршрутизации в приложениях Play.
6. Тестирование
Теперь мы можем запускать тесты на нашем API, отправляя запросы в http://localhost:9000/ и добавление соответствующего контекста. Запуск базового пути из браузера должен вывести:
{ "isSuccessful":true, "body":[] }
Как мы видим, тело пусто, так как мы еще не добавили никаких записей. Используя curl , давайте проведем несколько тестов (в качестве альтернативы мы можем использовать клиент REST, такой как Postman).
Давайте откроем окно терминала и выполним команду 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 будет увеличиваться для каждой новой записи, которую мы добавим.
Чтобы удалить запись мы отправляем запрос на УДАЛЕНИЕ :
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 } }
Давайте теперь обновим запись , установив имя “Андрей” и возраст до 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 } }
Приведенный выше тест демонстрирует изменение значения полей Имя и возраст после обновления записи.
Давайте создадим несколько дополнительных фиктивных записей, мы добавим две: Джон Доу и Сэм Баельдунг:
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 } ] }
С помощью приведенного выше теста мы устанавливаем правильное функционирование действия list Students controller.
7. Заключение
В этой статье мы показали, как создать полноценный REST API с помощью платформы Play.
Как обычно, исходный код этого учебника доступен на GitHub .