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

Аннотация @ExceptionHandler для обработки исключений

Модуль 5. Spring
9 уровень , 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" и не побежали к конкурентам! ;)

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ