Рубрики
Без рубрики

REST API с Play Framework на Java

Краткое и практическое руководство по созданию API REST с помощью Play на Java

Автор оригинала: baeldung.

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 Map students = 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 CompletionStage create(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 CompletionStage update(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 CompletionStage retrieve(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 CompletionStage 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. Список Действий Студентов

Наконец, действие list Students возвращает список всех студентов, которые были сохранены до сих пор. Он сопоставлен с http://localhost:9000/ как ПОЛУЧИТЬ запрос:

public CompletionStage listStudents() {
    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 .