Давайте сделаем шаг вперёд и погружаемся в глобальную обработку ошибок с помощью мощного инструмента 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 или веб-приложения. Вы можете сделать ошибки более информативными, локализовать их для поддержки нескольких языков и логировать их для анализа и устранения проблем.
В следующих лекциях мы затронем эти аспекты: как формировать пользовательские сообщения об ошибках, как их локализовать и как внедрить эффективное логирование.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ