JavaRush /Java блог /Random UA /Обробка винятків у контролерах Spring Boot
Павел
11 рівень

Обробка винятків у контролерах Spring Boot

Стаття з групи Random UA
ЗМІСТ ЦИКЛУ СТАТТІВ І знову привіт! Настав час струсити пил з клавіатури. Створюємо spring-boot проект. Із залежностей мавену нам потрібні:
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <java.version>1.8</java.version>
</properties>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.0.RELEASE</version>
    <relativePath/><!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
Перш ніж читати далі, створіть структуру проекту: Обробка винятків у контролерах Spring Boot - 1 BusinessException та CustomException:
public class BusinessException extends Exception{
    public BusinessException(String message) {
        super(message);
    }
}

public class CustomException extends Exception{
    public CustomException(String message) {
        super(message);
    }
}
та клас Response
public class Response {

    private String message;

    public Response() {
    }

    public Response(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
А тепер, я зроблю фінт вухами, і передам слово Олексію Кутепову, у своїй статті Обробка винятків у контролерах Spring він розповість нам, як ці файли наповнити правильним вмістом. Читайте не кваплячись, усі приклади акуратно переписуєте до себе в проект, запускайте та тестуйте у постмані. Якщо у статті Олексія, у вас викликала питання наступний рядок: produces = APPLICATION_JSON_VALUE , то знайте, що до обробки винятків вона не має відношення, вона каже, що за замовчуванням всі методи цього контролера будуть віддавати JSON. При необхідності в конкретному методі це значення можна перевизначити на інший MediaType. Якщо ви прочитали, йдемо далі. У запропонованій вище статті розглянуто різні варіанти обробників. Найбільш гнучкий з них: @ControllerAdvice – він дозволяє змінити як код, так і тіло стандартної відповіді за помилки. Крім того, він дозволяє в одному методі опрацювати відразу кілька винятків. Але це ще не все, якщо ви прочитаєте далі, то отримаєте покращений @ControllerAdvice абсолютно безкоштовно. Проведемо підготовчі роботи: хочу щоб у відповіді виводабося як кастомне так і стандартне повідомлення про помилку. Для цього внесемо зміну до класу Response : додамо ще одне поле
private String debugMessage;
Створимо додатковий конструктор:
public Response(String message, String debugMessage) {
    this.message = message;
    this.debugMessage = debugMessage;
}
і не забудьте створити Getter і Setter для нового поля. Тепер до діла. Напишемо ще один контролер:
@RestController
public class Example7Controller {
    @GetMapping(value = "/testExtendsControllerAdvice")
    public ResponseEntity<?> testExtendsControllerAdvice(@RequestBody Response response) {
        return  ResponseEntity.ok(response);
    }
}
Протестуємо у постман: На ​​http://localhost:8080/testExtendsControllerAdvice відправимо JSON
{
    "message": "message"
}
У відповідь отримаємо статус 200 і тіло
{
    "message": "message",
    "debugMessage": null
}
Тепер пошлемо свідомо неправильний JSON
{
    11"message": "message"
}
У відповідь отримаємо статус 400 (якщо забули, що він означає, гляньте в інтернеті) та порожнє тіло відповіді. Звичайно, нікого це не влаштовує, давайте з цим боротися. Раніше ми створювали @ControllerAdvice з нуля, але в Spring Boot існує заготовка - ResponseEntityExceptionHandler . У ній вже опрацьовано багато винятків, наприклад: NoHandlerFoundException , HttpMessageNotReadableException , MethodArgumentNotValidException та інші. Цей клас займається обробкою помилок. Він має купу методів, назва яких побудована за принципом handle + назва виключення. Якщо ми хочемо обробити якийсь базовий виняток, то успадковуємось від цього класу та перевизначаємо потрібний метод. Доопрацюємо клас дефолтного едвайсу
@ControllerAdvice
public class DefaultAdvice extends ResponseEntityExceptionHandler {//унаследовались от обработчика-заготовки

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        Response response = new Response(e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
//Небольшое отступление: В обработчике выше, обратите внимание на HttpStatus.OK,
//он может быть и HttpStatus.BAD_REQUEST або другим, тут ограничений нет,
//попробуйте поменять статусы и потестить этот обработчик


    @Override//переопределабо метод родительского класса
    protected ResponseEntity<Object> handleHttpMessageNotReadable
            (HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Response response = new Response("Не правильный JSON",ex.getMessage());
        return new ResponseEntity<>(response, status);
    }
}
Як ви помітабо, був перевизначений обробник, який відповідає за HttpMessageNotReadableException . Цей виняток виникає тоді, коли тіло запиту, що приходить у метод контролера, нечитане – наприклад, некоректний JSON. За цей виняток відповідає метод handleHttpMessageNotReadable () . Ще раз зробимо запит з некоректним JSON: http://localhost:8080/testExtendsControllerAdvice
{
    11"message": "message"
}
Отримуємо відповідь з кодом 400 (Bad Request) та тілом:
{
    "message": "Не правильный JSON",
    "debugMessage": "JSON parse error: Unexpected character ('1' (code 49)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('1' (code 49)): was expecting double-quote to start field name\n at [Source: (PushbackInputStream); line: 2, column: 6]"
}
Тепер відповідь містить як коректний код, а й тіло з інформативними повідомленнями. Перевіримо як працює з коректним JSON Запит:
{
    "message": "message"
}
Отримали відповідь:
{
    "message": "message",
    "debugMessage": null
}
Якщо чесно, мені не подобається що у відповіді є поле зі значенням null , зараз швиденько це виправимо. Переходимо в клас Response і ставимо інструкцію над потрібним полем
@JsonInclude(JsonInclude.Include.NON_NULL)
private String debugMessage;
Перезапускаємо проект, робимо ще раз попередній запит, у відповіді отримуємо:
{
    "message": "message"
}
Завдяки анотації @JsonInclude(JsonInclude.Include.NON_NULL) це поле буде включено у відповідь тільки в тому випадку, якщо ми його поставимо. @JsonInclude входить до бібліотеки анотацій Jackson , дуже корисно знати, що вона може. Ось дві статті на вибір: Джексон анотації. Автор перекладав, але не доперекладав , гугл трансліт відмінно впоратися. Валідація Потрібно доповнити цю тему таким поняттям як валідація. Просто кажучи, це перевірка що об'єкт є той об'єкт що ми очікуємо. Наприклад: якщо ми в додатку "Телефонний довідник" маємо перевіряти наявність телефонних номерів у БД, то перш ніж лізти в базу, логічно перевіряти, а чи не ввів користувач замість цифр літери. Три статті з валідації, зростання складності: Валідація бінів у Spring Налаштування валідації DTO у Spring Framework Валідація даних у Spring Boot З теорією, на сьогодні закінчабо. Для тренування пропоную наступне завдання: Необхідно реалізувати програму NightclubBouncer (Вибивала нічного клубу). Вимоги: 1) Програма повинна приймати на вхід JSON і робити запис до бази даних. Приклад JSON:
{
    "name": "Katy Perry"
    “status”:super star”
}
І в тілі відповіді має бути напис: Welcome + name ! 2) У додатку повинні бути реалізовані методи: - Виведення запису по id з БД в клієнт (Postman). - Видалення запису по полю: name . 3) Має бути реалізований мапінг із шару dto в entity і назад. 4) Додаток повинен викидати помилку KickInTheAssException (її потрібно розробити самим) якщо поле status у вході JSON не дорівнює: super star 5) Помилка KickInTheAssException повинна оброблятися ControllerAdvice, і в тілі відповіді має бути повідомлення: «Don't let me see you here again!». Статус відповіді має бути 400. 6) Стандартна помилка EntityNotFoundException , що виникає, наприклад, якщо до клубу зайшла тільки Кеті Пері та збереглася в базу з id = 1 , а ви викликали метод «виведення запису з id» і захотіли вивести запис з id = 2 , Якої немає в базі. Цю помилку необхідно обробити перевизначеним методом класу ResponseEntityExceptionHandler, яким саме - розібратися вам самим. Відповідь має мати відповідний статус. 7) Зробіть валідацію: простий варіант - поля JSON повинні бути не null, складніше поле "name" має складатися з двох слів латинського алфавіту і вони обидва повинні починатися з великої літери. Невалідні значення повинні викликати виняток, обробіть його будь-яким способом, виведіть відповідний код помилки та повідомлення про помилку: No validate. І реалізуйте це все без використання бібліотеки Lombok, не включайте її залежно від проекту 😅
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ