Это заключительная часть разбора REST. В предыдущих частях:
![Обзор REST. Часть 3: создание RESTful сервиса на Spring Boot - 1]()
Нажимаем кнопку Next. В следующем окне указываем тип проекта Maven, указываем Group и Artifact:
Нажимаем кнопку Next. В следующем окне нам необходимо выбрать нужные для проекта компоненты Spring Framework. Нам будет достаточно Spring Web:
Нажимаем кнопку Next. Далее осталось указать только наименование проекта и его расположение в файловой системе:
Нажимаем кнопку Finish. Проект создан, теперь мы можем увидеть его структуру:
IDEA сгенерировала за нас дескриптор развертывания системы сборки Maven — pom.xml и главный класс приложения: ![Обзор REST. Часть 3: создание RESTful сервиса на Spring Boot - 7]()
Нажимаем кнопку New в левом верхнем углу.
Далее выбираем Request:
Далее задаем ему имя и сохраняем его.
Попробуем теперь отправить POST запрос на сервер и создать первого клиента:
Создаем таким образом несколько клиентов. Затем меняем тип запроса на GET и отправляем его на сервер:
![Обзор REST. Часть 3: создание RESTful сервиса на Spring Boot - 12]()

Создание проекта
В данном разделе мы создадим небольшое RESTful приложение на Spring Boot. В нашем приложении будут реализованы CRUD (Create, Read, Update, Delete) операции над клиентами из примера из прошлой части разбора. Для начала создадим новое Spring Boot приложение через меню File -> New -> Project... В открывшимся окне выбираем Spring Initializr и указываем Project SDK:




RestExampleApplication
. Приведем их код:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.javarush.lectures</groupId>
<artifactId>rest_example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rest_example</name>
<description>REST example project</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
RestExampleApplication:
@SpringBootApplication
public class RestExampleApplication {
public static void main(String[] args) {
SpringApplication.run(RestExampleApplication.class, args);
}
}
Создание REST функционала
Наше приложение управляет клиентами. Поэтому первым делом нам необходимо создать сущность клиента. Это будет POJO класс. Создадим пакетmodel
внутри пакета com.javarush.lectures.rest_example
. Внутри пакета model
создадим класс Client
:
public class Client {
private Integer id;
private String name;
private String email;
private String phone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
В сервисе будут реализованы CRUD операции над клиентом. Следующим шагом мы должны создать сервис, в котором будут реализованы эти операции. В пакете com.javarush.lectures.rest_example
создадим пакет service
, внутри которого создадим интерфейс ClientService
.
Приведем код интерфейса с комментариями:
public interface ClientService {
/**
* Создает нового клиента
* @param client - клиент для создания
*/
void create(Client client);
/**
* Возвращает список всех имеющихся клиентов
* @return список клиентов
*/
List<client> readAll();
/**
* Возвращает клиента по его ID
* @param id - ID клиента
* @return - объект клиента с заданным ID
*/
Client read(int id);
/**
* Обновляет клиента с заданным ID,
* в соответствии с переданным клиентом
* @param client - клиент в соответсвии с которым нужно обновить данные
* @param id - id клиента которого нужно обновить
* @return - true если данные были обновлены, иначе false
*/
boolean update(Client client, int id);
/**
* Удаляет клиента с заданным ID
* @param id - id клиента, которого нужно удалить
* @return - true если клиент был удален, иначе false
*/
boolean delete(int id);
}
Далее нам необходимо создать реализацию этого интерфейса. Сейчас в роли хранилища клиентов будет выступать Map<Integer, Client>
. Ключом карты будет id клиента, а значением — сам клиент. Сделано это для того, чтобы не перегружать пример спецификой работы с БД. Однако в будущем мы сможем написать другую реализацию интерфейса, в которой можно будет подключить реальную базу данных.
В пакете service
создадим реализацию интерфейса ClientService
:
@Service
public class ClientServiceImpl implements ClientService {
// Хранилище клиентов
private static final Map<Integer, Client> CLIENT_REPOSITORY_MAP = new HashMap<>();
// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
@Override
public void create(Client client) {
final int clientId = CLIENT_ID_HOLDER.incrementAndGet();
client.setId(clientId);
CLIENT_REPOSITORY_MAP.put(clientId, client);
}
@Override
public List<Client> readAll() {
return new ArrayList<>(CLIENT_REPOSITORY_MAP.values());
}
@Override
public Client read(int id) {
return CLIENT_REPOSITORY_MAP.get(id);
}
@Override
public boolean update(Client client, int id) {
if (CLIENT_REPOSITORY_MAP.containsKey(id)) {
client.setId(id);
CLIENT_REPOSITORY_MAP.put(id, client);
return true;
}
return false;
}
@Override
public boolean delete(int id) {
return CLIENT_REPOSITORY_MAP.remove(id) != null;
}
}
Аннотация @Service
говорит спрингу, что данный класс является сервисом. Это специальный тип классов, в котором реализуется некоторая бизнес логика приложения. Впоследствии, благодаря этой аннотации Spring будет предоставлять нам экземпляр данного класса в местах, где это, нужно с помощью Dependency Injection.
Теперь пришло время для создания контроллера. Специального класса, в котором мы реализуем логику обработки клиентских запросов на эндпоинты (URI).
Чтобы было понятней, будем создавать данный класс по частям. Сначала создадим сам класс и внедрим в него зависимость от ClientService
:
@RestController
public class ClientController {
private final ClientService clientService;
@Autowired
public ClientController(ClientService clientService) {
this.clientService = clientService;
}
}
Поясним аннотации:
@RestController — говорит спрингу, что данный класс является REST контроллером. Т.е. в данном классе будет реализована логика обработки клиентских запросов
@Autowired — говорит спрингу, что в этом месте необходимо внедрить зависимость. В конструктор мы передаем интерфейс ClientService
. Реализацию данного сервиса мы пометили аннотацией @Service
ранее, и теперь спринг сможет передать экземпляр этой реализации в конструктор контроллера.
Далее мы пошагово будем реализовывать каждый метод контроллера, для обработки CRUD операций. Начнем с операции Create. Для этого напишем метод create
:
@PostMapping(value = "/clients")
public ResponseEntity<?> create(@RequestBody Client client) {
clientService.create(client);
return new ResponseEntity<>(HttpStatus.CREATED);
}
Разберем данный метод:
@PostMapping(value = "/clients")
— здесь мы обозначаем, что данный метод обрабатывает POST запросы на адрес /clients
Метод возвращает ResponseEntity<?>
. ResponseEntity
— специальный класс для возврата ответов. С помощью него мы сможем в дальнейшем вернуть клиенту HTTP статус код.
Метод принимает параметр @RequestBody Client client
, значение этого параметра подставляется из тела запроса. Об этом говорит аннотация @RequestBody
.
Внутри тела метода мы вызываем метод create у ранее созданного сервиса и передаем ему принятого в параметрах контроллера клиента.
После чего возвращаем статус 201 Created, создав новый объект ResponseEntity
и передав в него нужное значение енума HttpStatus
.
Далее реализуем операцию Read
:
Для начала реализуем операцию получения списка всех имеющихся клиентов:
@GetMapping(value = "/clients")
public ResponseEntity<List<Client>> read() {
final List<Client> clients = clientService.readAll();
return clients != null && !clients.isEmpty()
? new ResponseEntity<>(clients, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Приступим к разбору:
@GetMapping(value = "/clients")
— все аналогично аннотации @PostMapping
, только теперь мы обрабатываем GET запросы.
На этот раз мы возвращаем ResponseEntity<List<Client>>
, только в этот раз, помимо HTTP статуса, мы вернем еще и тело ответа, которым будет список клиентов.
В REST контроллерах спринга все POJO объекты, а также коллекции POJO объектов, которые возвращаются в качестве тел ответов, автоматически сериализуются в JSON, если явно не указано иное. Нас это вполне устраивает.
Внутри метода, с помощью нашего сервиса мы получаем список всех клиентов. Далее, в случае если список не null и не пуст, мы возвращаем c помощью класса ResponseEntity
сам список клиентов и HTTP статус 200 OK. Иначе мы возвращаем просто HTTP статус 404 Not Found.
Далее реализуем возможность получать клиента по его id:
@GetMapping(value = "/clients/{id}")
public ResponseEntity<Client> read(@PathVariable(name = "id") int id) {
final Client client = clientService.read(id);
return client != null
? new ResponseEntity<>(client, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Из нового, у нас тут появилась переменная пути. Переменная, которая определена в URI. value = "/clients/{id}"
. Мы указали ее в фигурных скобках. А в параметрах метода принимаем её в качестве int
переменной, с помощью аннотации @PathVariable(name = "id")
.
Данный метод будет принимать запросы на uri вида /clients/{id}
, где вместо {id}
может быть любое численное значение. Данное значение, впоследствии, передается переменной int id
— параметру метода.
В теле мы получаем объект Client
с помощью нашего сервиса и принятого id
. И далее, по аналогии со списком, возвращаем либо статус 200 OK и сам объект Client
, либо просто статус 404 Not Found, если клиента с таким id
не оказалось в системе.
Осталось реализовать две операции — Update и Delete. Приведем код этих методов:
@PutMapping(value = "/clients/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Client client) {
final boolean updated = clientService.update(client, id);
return updated
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
@DeleteMapping(value = "/clients/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
final boolean deleted = clientService.delete(id);
return deleted
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
Чего-то существенно нового в данных методах нет, поэтому подробное описание пропустим. Единственное, о чем стоит сказать: метод update
обрабатывает PUT запросы (аннотация @PutMapping
), а метод delete
обрабатывает DELETE запросы (аннотация DeleteMapping
).
Приведем полный код контроллера:
@RestController
public class ClientController {
private final ClientService clientService;
@Autowired
public ClientController(ClientService clientService) {
this.clientService = clientService;
}
@PostMapping(value = "/clients")
public ResponseEntity<?> create(@RequestBody Client client) {
clientService.create(client);
return new ResponseEntity<>(HttpStatus.CREATED);
}
@GetMapping(value = "/clients")
public ResponseEntity<List<Client>> read() {
final List<client> clients = clientService.readAll();
return clients != null && !clients.isEmpty()
? new ResponseEntity<>(clients, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@GetMapping(value = "/clients/{id}")
public ResponseEntity<Client> read(@PathVariable(name = "id") int id) {
final Client client = clientService.read(id);
return client != null
? new ResponseEntity<>(client, HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@PutMapping(value = "/clients/{id}")
public ResponseEntity<?> update(@PathVariable(name = "id") int id, @RequestBody Client client) {
final boolean updated = clientService.update(client, id);
return updated
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
@DeleteMapping(value = "/clients/{id}")
public ResponseEntity<?> delete(@PathVariable(name = "id") int id) {
final boolean deleted = clientService.delete(id);
return deleted
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
}
}
В итоге, структура нашего проекта выглядит следующим образом:

Запуск и тестирование
Чтобы запустить наше приложение, достаточно запустить методmain
в классе RestExampleApplication
.
А для того, чтобы тестировать RESTful веб сервисы, нужно скачать новое ПО )
Дело в том, что GET запросы довольно просто отправлять из обычного браузера, а вот для POST, PUT и DELETE обычным браузером не обойтись.
Не переживай: чтобы отправлять любые HTTP запросы, можно воспользоваться программой Postman. Скачать её можно отсюда.
После скачивания и установки, приступаем к тестированию нашего приложения.
Для этого открываем программу и создаем новый запрос:




Общие итоги
Поздравляю: мы рассмотрели довольно тему REST. Весь материал получился объемным, но, надеемся, полезным для тебя:Мы узнали, что такое REST.
Познакомились с историей возникновения REST.
Поговорили об ограничениях и принципах данного архитектурного стиля:
- приведение архитектуры к модели клиент-сервер;
- отсутствие состояния;
- кэширование;
- единообразие интерфейса;
- слои;
- код по требованию (необязательное ограничение).
Разобрали преимущества которые дает REST
Подробно рассмотрели, как сервер и клиент взаимодействуют друг с другом по HTTP протоколу.
Поближе познакомились с запросами и ответами. Разобрали их составные части.
Наконец, мы перешли к практике и написали свое небольшое RESTful приложение на Spring Boot. И даже научились его тестировать с помощью программы Postman.
Домашнее задание
Попробуй сделать следующее:- Следуя описанию выше, создай самостоятельно Spring Boot проект и реализуй в нем ту же логику, что и в лекции. Повтори все 1 в 1.
- Запусти. приложение.
- Скачай и настрой Postman (либо любой другой инструмент для отправки запросов, хоть curl).
- Протестируй запросы POST и GET так же, как было указано в лекции.
- Протестируй запросы PUT и DELETE самостоятельно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ