Зробімо крок уперед і пориньмо в глобальну обробку помилок за допомогою потужного інструменту Spring — @ControllerAdvice. У цій лекції ти дізнаєшся, як централізувати обробку всіх помилок додатку, зробити її більш гнучкою і акуратною.
Що таке @ControllerAdvice?
@ControllerAdvice — це анотація Spring Framework, яка дозволяє тобі централізовано керувати обробкою виключень для всіх контролерів твого додатку. Якщо раніше ми могли обробляти виключення локально на рівні одного контролера за допомогою @ExceptionHandler, то з @ControllerAdvice ти отримуєш можливість винести логіку обробки помилок в окремий клас, який буде застосовуватись до всіх контролерів.
Уяви, що в тебе є додаток з великою кількістю контролерів, де різні ендпоінти можуть кидати однакові виключення (наприклад, MethodArgumentNotValidException при помилках валідації). Якщо писати обробники в кожному контролері, код почне дублюватись і ним буде важко керувати. @ControllerAdvice вирішує цю проблему: один раз оголосивши глобальний обробник, ти можеш уніфікувати логіку обробки помилок.
Як це працює?
Spring автоматично реєструє класи з анотацією @ControllerAdvice і застосовує їх до всіх контролерів в твоєму додатку. Ти можеш комбінувати це з анотацією @ExceptionHandler, щоб визначити, які виключення обробляти.
Як налаштувати глобальний обробник з використанням @ControllerAdvice?
Крок 1. Створення класу з анотацією @ControllerAdvice
Створюємо новий клас, який буде обробляти виключення глобально. Наприклад, назвемо його GlobalExceptionHandler.
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
// Обробка загального виключення
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return new ResponseEntity<>("Сталася помилка: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Що тут відбувається?
- Ми використовуємо
@ControllerAdvice, щоб повідомити Spring, що цей клас буде обробляти виключення для всіх контролерів. - У методі
handleGenericExceptionми ловимо будь-яке виключення (Exception.class) і повертаємо просте повідомлення про помилку з HTTP-статусом500 Internal Server Error.
Крок 2. Обробка конкретних виключень
Часто нам потрібно обробляти не всі виключення підряд, а конкретні типи, наприклад помилки валідації або ситуації, коли ресурс не знайдено. Додамо обробники для таких випадків.
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
@ControllerAdvice
public class GlobalExceptionHandler {
// Обробка помилок валідації
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException ex) {
return new ResponseEntity<>("Помилка валідації: перевірте передані дані", HttpStatus.BAD_REQUEST);
}
// Обробка ресурсу, який не знайдено
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
Зверни увагу:
- Для обробки помилок валідації ми додали обробник, який ловить
MethodArgumentNotValidException. Це виключення генерується автоматично при використанні@Valid. - Для помилок ресурсу ми припускаємо, що в нас є власне виключення
ResourceNotFoundException. Це приклад кастомного виключення, яке може бути викинуте з сервісного шару.
Крок 3. Створення кастомного виключення
Якщо в тебе є ситуації, які вимагають власної логіки обробки, краще створити своє виключення. Ось приклад:
package com.example.demo.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Тепер це виключення можна викидати, наприклад, у сервісі:
if (resource == null) {
throw new ResourceNotFoundException("Ресурс з ID " + id + " не знайдено");
}
Приклад: Спільна обробка помилок
Зберемо все разом. Нехай у нас є простий контролер, де є метод для отримання ресурсу по ID. Якщо ресурс не знайдено, ми викидаємо ResourceNotFoundException. Якщо дані некоректні, Spring автоматично викине MethodArgumentNotValidException.
Контролер
package com.example.demo.controller;
import com.example.demo.exception.ResourceNotFoundException;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
@RestController
@RequestMapping("/api/resources")
public class ResourceController {
@GetMapping("/{id}")
public String getResource(@PathVariable @Min(1) Long id) {
// Імітація пошуку ресурсу
if (id == 999) {
throw new ResourceNotFoundException("Ресурс з ID " + id + " не знайдено");
}
return "Ресурс знайдено: ID = " + id;
}
}
Особливості:
- Ми застосовуємо анотацію
@Min(1)для перевірки ID. Якщо ID менше 1, Spring автоматично викинеMethodArgumentNotValidException. - Якщо ресурс з ID
999не знайдено, викидається наше кастомне виключення.
Результати
- Якщо ID менше 1:
HTTP 400 BAD REQUEST Помилка валідації: перевірте передані дані - Якщо ресурс не знайдено (ID = 999):
HTTP 404 NOT FOUND Ресурс з ID 999 не знайдено Якщо відбувається будь-яке інше виключення:
HTTP 500 INTERNAL SERVER ERROR Сталася помилка: [текст виключення]
Переваги використання @ControllerAdvice
- Централізація обробки помилок: увесь код обробки помилок знаходиться в одному місці, що спрощує підтримку додатку.
- Однорідність: усі помилки обробляються однаково, що робить API більш передбачуваним і зручним для клієнтів.
- Гнучкість: ти можеш додавати обробники для різних типів виключень і налаштовувати їх під свої потреби.
Покращення користувацького досвіду
Глобальна обробка помилок — це не тільки про комфорт програміста, але й про зручність для користувачів твого API або веб-додатку. Ти можеш зробити помилки більш інформативними, локалізувати їх для підтримки кількох мов і логувати їх для аналізу та усунення проблем.
У наступних лекціях ми торкнемося цих аспектів: як формувати користувацькі повідомлення про помилки, як їх локалізувати і як впровадити ефективне логування.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ