JavaRush /Курсы /Модуль 5. Spring /Глобальная обработка ошибок через @ControllerAdvice

Глобальная обработка ошибок через @ControllerAdvice

Модуль 5. Spring
9 уровень , 5 лекция
Открыта

Давайте сделаем шаг вперёд и погружаемся в глобальную обработку ошибок с помощью мощного инструмента 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 не найден, выбрасывается наше кастомное исключение.

Результаты

  1. Если ID меньше 1:
    
    HTTP 400 BAD REQUEST
    Ошибка валидации: проверьте переданные данные
    
  2. Если ресурс не найден (ID = 999):
    
    HTTP 404 NOT FOUND
    Ресурс с ID 999 не найден
    
  3. Если происходит любое другое исключение:

    
    HTTP 500 INTERNAL SERVER ERROR
    Произошла ошибка: [текст исключения]
    

Преимущества использования @ControllerAdvice

  1. Централизация обработки ошибок: весь код обработки ошибок находится в одном месте, что упрощает поддержку приложения.
  2. Единообразие: все ошибки обрабатываются одинаково, что делает API более предсказуемым и удобным для клиентов.
  3. Гибкость: вы можете добавлять обработчики для разных типов исключений и настраивать их под свои нужды.

Улучшение пользовательского опыта

Глобальная обработка ошибок — это не только про комфорт программиста, но и про удобство для пользователей вашего API или веб-приложения. Вы можете сделать ошибки более информативными, локализовать их для поддержки нескольких языков и логировать их для анализа и устранения проблем.

В следующих лекциях мы затронем эти аспекты: как формировать пользовательские сообщения об ошибках, как их локализовать и как внедрить эффективное логирование.

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