JavaRush /Курси /Модуль 5. Spring /Анотація @ExceptionHandler для обробки виключень

Анотація @ExceptionHandler для обробки виключень

Модуль 5. Spring
Рівень 8 , Лекція 4
Відкрита

Помилки — невід’ємна частина будь-якої системи, а в програмуванні тим більше. Замість того, щоб дозволяти виняткам «виплескуватися» на користувача у вигляді незрозумілих повідомлень про помилку або, що ще гірше, стек-трейсів, ми можемо перехоплювати їх і акуратно обробляти. Ви вже робили так у чистій Java. А у Spring для цього є інструмент під назвою @ExceptionHandler.

@ExceptionHandler дозволяє обробляти винятки на рівні конкретного контролера. Це значно спрощує керування помилками і допомагає забезпечити напрацьовану поведінку додатка коли виникають виключення. Наприклад, замість того, щоб показувати користувачу лякаюче повідомлення NullPointerException, ми можемо повернути акуратну JSON-відповідь з описом проблеми.

Основи роботи з @ExceptionHandler

Анотація @ExceptionHandler застосовується до методу в контролері і вказує, які виключення цей метод повинен обробляти. Ось як це виглядає:


@RestController
@RequestMapping("/api")
public class ExampleController {

    @GetMapping("/example")
    public String exampleEndpoint() {
        // Кинемо виняток для демонстрації
        throw new IllegalArgumentException("Невірний параметр!");
    }

    // Обробка виключення IllegalArgumentException
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                             .body("Помилка: " + ex.getMessage());
    }
}

Тут ми визначили метод, помічений @ExceptionHandler, який перехоплює виключення IllegalArgumentException і повертає користувачу читабельне повідомлення про помилку з HTTP-статусом 400 (BAD_REQUEST).

Ключові моменти:

  1. Метод з @ExceptionHandler може приймати як аргумент об'єкт виключення, щоб отримати додаткову інформацію.
  2. Ви можете повернути будь-який підходящий тип даних (ResponseEntity, String, Map<String, Object> тощо).
  3. Методу нічого не заважає обробляти кілька типів виключень одразу (наприклад, через суперкласи або масив класів).

Приклад використання @ExceptionHandler

А тепер розберемо реальний приклад. Уявімо, що у нас є додаток для керування книгами. Воно має повертати інформацію про книгу за її ID.

Контролер:


@RestController
@RequestMapping("/books")
public class BookController {

    private final Map<Long, String> books = Map.of(
            1L, "Java для початківців",
            2L, "Просунута Java",
            3L, "Spring в дії");

    @GetMapping("/{id}")
    public String getBook(@PathVariable Long id) {
        if (!books.containsKey(id)) {
            throw new BookNotFoundException("Книга з ID " + id + " не знайдена.");
        }
        return books.get(id);
    }

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                             .body(ex.getMessage());
    }
}

Користувацький клас виключення:


public class BookNotFoundException extends RuntimeException {
    public BookNotFoundException(String message) {
        super(message);
    }
}

Тепер, якщо користувач відправить GET-запит до /books/10, він отримає акуратну відповідь:


HTTP Status: 404 Not Found
Body: "Книга з ID 10 не знайдена."

Переваги використання @ExceptionHandler

  1. Локальна обробка: метод обробляє виключення лише для цього контролера. Це зручно, якщо різні контролери мають свої специфічні сценарії обробки.
  2. Чистота коду: винесення обробки виключень в окремі методи допомагає тримати основний код контролера компактним.
  3. Розширюваність: легко додавати обробку нових виключень у міру потреби.

Обробка кількох виключень з використанням @ExceptionHandler

Іноді один метод може обробляти кілька типів виключень одночасно. Це можливо, бо анотація @ExceptionHandler приймає масив класів виключень.

Приклад:


@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
public ResponseEntity<String> handleMultipleExceptions(Exception ex) {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                         .body("Сталася помилка: " + ex.getMessage());
}

Тут один метод перехоплює і IllegalArgumentException, і NullPointerException, повертаючи однакову відповідь для обох ситуацій.


Типові помилки при використанні @ExceptionHandler

  1. Неперехоплені виключення: якщо виключення не оброблене в поточному контролері, воно "спливе" вгору, де може бути перехоплене глобальним обробником або взагалі лишитися необробленим.
  2. Перекривання стандартної логіки Spring: деякі виключення, такі як MethodArgumentNotValidException, вже обробляються Spring за замовчуванням. Якщо ви напишете власний обробник для таких виключень, переконайтеся, що він покриває всі можливі випадки.
  3. Невірний HTTP статус: переконайтеся, що повернутий статус відповідає логіці REST API. Наприклад, NOT_FOUND для відсутніх ресурсів і BAD_REQUEST для помилок валідації.

Практичне завдання

  1. Створіть контролер для керування користувачами, де буде метод, що повертає інформацію про користувача за його ID.
  2. Реалізуйте користувацьке виключення UserNotFoundException.
  3. Додайте обробник @ExceptionHandler для цього виключення, який повертає HTTP-статус 404 і повідомлення про помилку.
  4. Переконайтеся, що обробник коректно працює, відправивши запит з неіснуючим ID користувача.

Ось який прикладний результат повинен вийти:

Запит:


GET /users/99

Відповідь:


HTTP Status: 404 Not Found
Body: "Користувач з ID 99 не знайдений."

Підсумкові зауваження

@ExceptionHandler — потужний інструмент для обробки виключень на рівні контролерів. Він дозволяє писати чистий і лаконічний код, полегшуючи діагностику і покращуючи UX. Використовуйте його з розумом, щоб ваші користувачі ніколи не бачили пресловуте "Internal Server Error" і не побігли до конкурентів! ;)

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ