Ошибки неизбежны везде и повсюду, а в программировании — и подавно. Однако, вместо того, чтобы позволить исключениям "выплёскиваться" на пользователя в виде непонятных сообщений об ошибках или, что еще хуже, стек-трейсов, мы можем перехватывать их и аккуратно обрабатывать. Вы уже так делали в чистой 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).
Ключевые моменты:
- Метод с
@ExceptionHandlerможет принимать в качестве аргумента объект исключения, чтобы получить дополнительную информацию. - Вы можете вернуть любой подходящий тип данных (
ResponseEntity,String,Map<String, Object>, и т.д.). - Методу ничего не мешает обрабатывать несколько типов исключений сразу (например, с использованием обобщений или суперклассов).
Пример использования @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
- Локальная обработка: метод обрабатывает исключения только для данного контроллера. Это удобно, если разные контроллеры имеют свои специфические сценарии обработки.
- Чистота кода: вынос обработки исключений в отдельные методы помогает поддерживать основной код контроллера более компактным.
- Расширяемость: легко добавлять обработку новых исключений по мере необходимости.
Обработка нескольких исключений с использованием @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
- Не перехваченные исключения: если исключение не обработано в текущем контроллере, оно "всплывает" наверх, где может быть перехвачено глобальным обработчиком или вовсе остаться необработанным.
- Переопределение стандартной логики Spring: некоторые исключения, такие как
MethodArgumentNotValidException, уже обрабатываются Spring по умолчанию. Если вы напишете собственный обработчик для таких исключений, убедитесь, что он покрывает все возможные случаи. - Неправильный HTTP статус: убедитесь, что возвращаемый статус соответствует химии REST API. Например,
NOT_FOUNDдля отсутствующих ресурсов иBAD_REQUESTдля ошибок валидации.
Практическое задание
- Создайте контроллер для управления пользователями, где будет метод, возвращающий информацию о пользователе по его ID.
- Реализуйте пользовательское исключение
UserNotFoundException. - Добавьте обработчик
@ExceptionHandlerдля этого исключения, который будет возвращать HTTP статус 404 и сообщение об ошибке. - Убедитесь, что обработчик корректно работает, отправив запрос с ID несуществующего пользователя.
Вот какой примерный результат должен получиться:
Запрос:
GET /users/99
Ответ:
HTTP Status: 404 Not Found
Body: "Пользователь с ID 99 не найден."
Заключительные замечания
@ExceptionHandler — мощный инструмент для обработки исключений на уровне контроллеров. Он позволяет писать чистый и лаконичный код, упрощая диагностику и улучшая UX. Используйте его с умом, чтобы ваши пользователи никогда не увидели пресловутое "Internal Server Error" и не побежали к конкурентам! ;)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ