Ошибки в приложении — к сожалению, штука неизбежная. Некорректные пользовательские входные данные, сбои запроса в базе данных, отказ внешнего сервиса — всё это стандартные ситуации, которые могут привести к исключениям. Для разработчика важно уметь грамотно обрабатывать эти ошибки, чтобы они не "падали" на конечного пользователя в виде пугающих стек-трейсов.
У Spring MVC для этих целей есть встроенные механизмы:
- Локальная обработка ошибок: с помощью методов в контроллерах.
- Глобальная обработка ошибок: с использованием аннотации
@ControllerAdvice.
Обработчики ошибок помогут:
- Скрыть внутреннюю информацию (например, стек-трейсы).
- Показать пользователю понятное и полезное сообщение об ошибке.
- Сформировать соответствующий HTTP-статус код (например,
400 Bad Requestили500 Internal Server Error). - Вести централизованное логирование ошибок.
Локальная обработка ошибок
Локальная обработка ошибок — это обработка исключений в пределах одного контроллера. В Spring для этого используется аннотация @ExceptionHandler. Она позволяет связать исключение с методом, который должен быть вызван, если это исключение возникает.
Пример локального обработчика Рассмотрим пример, в котором мы обрабатываем исключение MethodArgumentNotValidException, возникающее при валидации входных данных.
@RestController
@RequestMapping("/api/example")
public class ExampleController {
@PostMapping("/create")
public String createExample(@Valid @RequestBody ExampleDto dto) {
// Ваш код обработки запроса
return "Example created successfully!";
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
// Извлекаем первую ошибку валидации, чтобы вернуть её пользователю
String errorMessage = exception.getBindingResult()
.getFieldErrors()
.get(0)
.getDefaultMessage();
return ResponseEntity.badRequest().body("Validation Error: " + errorMessage);
}
}
В этом коде:
- Если при валидации объекта
ExampleDtoбудет найдено нарушение (например, полеnameпустое), то выбросится исключениеMethodArgumentNotValidException. - Обработчик
@ExceptionHandlerперехватит его и вернёт пользователю понятное сообщение об ошибке.
Локальная обработка хороша для небольших приложений, но становится недостаточной, если в системе много контроллеров, и они должны одинаково реагировать на одни и те же исключения. В таких случаях удобно использовать глобальную обработку ошибок.
Глобальная обработка ошибок
Глобальная обработка ошибок с помощью аннотации @ControllerAdvice позволяет определить единый набор обработчиков исключений, применимых ко всем контроллерам в приложении.
@ControllerAdvice — это специальная аннотация Spring, которая позволяет создать централизованный обработчик исключений. Все методы, аннотированные @ExceptionHandler внутри класса с @ControllerAdvice, будут вызываться для обработки исключений, возникших в любом контроллере приложения.
Пример глобального обработчика Создадим класс для обработки ошибок:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException exception) {
// Собираем сообщения обо всех ошибках валидации
String errorMessage = exception.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body("Validation Errors: " + errorMessage);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception exception) {
// Логируем ошибку (в реальном проекте можно использовать логгер)
System.err.println("Unexpected error: " + exception.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("An unexpected error occurred. Please try again later.");
}
}
В этом коде:
- Метод
handleValidationExceptionобрабатывает только ошибки валидации (MethodArgumentNotValidException) и возвращает понятное сообщение, агрегирующее все ошибки. - Метод
handleGeneralExceptionперехватывает любые другие исключения и возвращает сообщение о внутренней ошибке сервера.
Как это работает?
При возникновении исключения Spring сначала проверяет, есть ли локальный обработчик в текущем контроллере. Если его нет, он ищет подходящий метод в классе с аннотацией @ControllerAdvice.
Преимущества глобальной обработки ошибок
Использование @ControllerAdvice имеет несколько преимуществ:
- Централизованное управление обработкой исключений.
- Повторное использование кода (не нужно дублировать логику обработки ошибок в каждом контроллере).
- Возможность использовать полиморфизм (можно создать иерархию исключений и обрабатывать их "цепочкой").
Пример: обработка пользовательских исключений
В реальных проектах часто создаются пользовательские исключения для обработки специфических ситуаций. Например, исключение ResourceNotFoundException для случаев, когда сущность не найдена.
Создадим класс исключения:
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Обработка исключения Добавим обработчик в GlobalExceptionHandler:
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException exception) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
}
Использование в контроллере
Теперь можно выбрасывать исключение ResourceNotFoundException в случае, если ресурс не найден:
@GetMapping("/item/{id}")
public Item getItem(@PathVariable Long id) {
return itemRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Item with ID " + id + " not found"));
}
Если элемент с указанным id отсутствует в базе, пользователь получит ответ с кодом 404 Not Found и сообщением "Item with ID {id} not found".
Практические советы
- При создании кастомных исключений избегайте "раздувания" иерархии классов. Используйте чёткую и лаконичную структуру.
- Всегда логируйте ошибки, но не показывайте пользователю внутренние подробности (например, SQL-запросы или стек-трейсы).
- Устанавливайте подходящие HTTP-статус коды в ответах (например, 400 для ошибок валидации, 404 для отсутствия ресурса, 500 для внутренних ошибок).
- Добавляйте причину ошибки в описание ответа, чтобы облегчить диагностику проблем.
Практическая задача
Задача: реализуйте глобальный обработчик ошибок, который:
- Обрабатывает ошибки валидации (
MethodArgumentNotValidException) и возвращает агрегированный список ошибок. - Обрабатывает случаи, когда ресурс не найден (
ResourceNotFoundException). - Ловит все прочие исключения и возвращает стандартное сообщение "Internal Server Error".
Попробуйте интегрировать этот обработчик в свой текущий проект и протестировать различные сценарии ошибок.
На этом этапе ваш код станет более надёжным: вы сможете показать пользователю полезную информацию, а не "красивый" стек-трейс с ошибками. Теперь любое исключение в вашей системе будет обработано элегантно и централизованно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ