Как в Spring MVC обработать исключение UserNotFoundException и выполнить redirect на страницу регистрации?

Ответ

В Spring MVC для глобальной обработки исключений и выполнения перенаправления (redirect) используются аннотации @ControllerAdvice и @ExceptionHandler.

1. Создание пользовательского исключения

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
    // Можно добавить дополнительные поля, например, userId
}

2. Создание класса-обработчика исключений (@ControllerAdvice)

Класс, помеченный @ControllerAdvice, перехватывает исключения, выброшенные в любом контроллере.

@ControllerAdvice // Объявляет глобальный обработчик исключений
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class) // Указывает тип обрабатываемого исключения
    public String handleUserNotFoundException(UserNotFoundException ex,
                                               HttpServletRequest request,
                                               RedirectAttributes redirectAttributes) {
        // 1. Логирование исключения (обязательно для отладки)
        logger.error("User not found for request: " + request.getRequestURI(), ex);

        // 2. Добавление flash-атрибута для передачи сообщения на страницу регистрации
        // Flash-атрибуты живут ровно один redirect, что идеально подходит для этой задачи.
        redirectAttributes.addFlashAttribute("errorMessage",
                "Пользователь не найден. Пожалуйста, зарегистрируйтесь.");

        // 3. Выполнение redirect на URL страницы регистрации
        return "redirect:/register"; // Предполагается, что есть маппинг на "/register"
    }
}

3. Контроллер, который бросает исключение

@Controller
public class UserController {
    @GetMapping("/user/{id}")
    public String getUserProfile(@PathVariable Long id, Model model) {
        User user = userService.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User with id " + id + " not found"));
        model.addAttribute("user", user);
        return "profile";
    }
}

4. Контроллер и страница регистрации

@Controller
public class RegistrationController {
    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        // Атрибут "errorMessage" будет автоматически подставлен из flash-атрибута
        // если он был установлен в обработчике исключений.
        if (!model.containsAttribute("userDto")) {
            model.addAttribute("userDto", new UserDto());
        }
        return "registration-page"; // Имя Thymeleaf/HTML шаблона
    }
}

В шаблоне registration-page.html (например, Thymeleaf) можно отобразить сообщение:

<div th:if="${errorMessage}" class="alert alert-danger">
    <span th:text="${errorMessage}"></span>
</div>
<!-- Остальная форма регистрации -->

Ключевые моменты:

  • @ControllerAdvice + @ExceptionHandler — стандартный и мощный механизм для централизованной обработки исключений.
  • RedirectAttributes используется для безопасной передачи данных между запросами при redirect.
  • Flash-атрибуты (addFlashAttribute) идеальны для передачи сообщений об ошибках, так как они не попадают в URL и существуют один запрос.
  • Всегда логируйте исключения для последующего анализа.
  • Этот подход отделяет логику обработки ошибок от бизнес-логики контроллеров, делая код чище и поддерживаемее.

Ответ 18+ 🔞

А, ну ты глянь, какой у нас тут академический разговор про исключения в Spring MVC. Прям как в институте, блядь. Слушай, а давай я тебе на пальцах объясню, как эту хуйню на практике делать, а то смотрю — ты уже глаза закатываешь от этих @ControllerAdvice.

Представь себе, у тебя есть контроллер, который юзеров ищет. Ну, классика:

@Controller
public class UserController {
    @GetMapping("/user/{id}")
    public String getUserProfile(@PathVariable Long id, Model model) {
        // Вот тут мы наивно думаем, что юзер всегда найдётся
        User user = userService.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User with id " + id + " not found"));
        model.addAttribute("user", user);
        return "profile";
    }
}

И вот этот самый UserNotFoundException — это наша самописная граната. Выглядит проще пареной репы:

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

А теперь, внимание, сценарий: заходит какой-нибудь умник по ссылке /user/999999, а юзера с таким айдишником — нихуя. Контроллер херачит исключение, и если ничего не сделать, пользователю в ебучем браузере вывалится стопка трейсов на пол-экрана. Красота, да? Пиздец как профессионально.

Так вот, чтобы не выглядеть полными лузерами, мы делаем глобального "уборщика" за всеми контроллерами. В Spring это зовётся @ControllerAdvice. Это такая сущность, которая ловит все выброшенные исключения, как сетью, и говорит: "Не волнуйся, пацан, я всё уладю".

Создаём класс-спасатель:

@ControllerAdvice // Эта хуйня говорит: "Я тут главный по тарелочкам"
public class GlobalExceptionHandler {

    // Логгер — наш лучший друг, чтобы потом понять, что пошло по пизде
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(UserNotFoundException.class) // Ловим конкретно наше исключение
    public String handleUserNotFoundException(UserNotFoundException ex,
                                               HttpServletRequest request,
                                               RedirectAttributes redirectAttributes) {

        // Шаг 1: Зафиксировать преступление. Логируем, блядь, всегда логируем!
        logger.error("Запрос: {} — юзер не найден. Ошибка: {}", request.getRequestURI(), ex.getMessage());

        // Шаг 2: Готовим "записку" для следующей страницы.
        // Используем flash-атрибут — это как передать бумажку через одного человека.
        // Она живёт ровно один редирект, а потом исчезает. Магия, ёпта!
        redirectAttributes.addFlashAttribute("errorMessage",
                "Э, дружок-пирожок, такого пользователя нет. Давай-ка зарегистрируйся сначала!");

        // Шаг 3: Делаем редирект куда надо, а не показываем белую страницу с ошибкой.
        return "redirect:/register";
    }
}

Теперь, когда наш UserNotFoundException вылетает, этот обработчик его ловит, тихо матерится в лог, и незаметно для пользователя делает редирект на страницу регистрации (/register). Причём вместе с ним летит наше сообщение errorMessage.

На стороне регистрации всё просто. Контроллер даже не парится:

@Controller
public class RegistrationController {
    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        // Flash-атрибут 'errorMessage' автоматом подтянется в модель, если он был передан.
        // Если его нет — ну и хуй с ним, работаем дальше.
        if (!model.containsAttribute("userDto")) {
            model.addAttribute("userDto", new UserDto());
        }
        return "registration-page"; // Показываем шаблон
    }
}

А в шаблоне (registration-page.html, если используем Thymeleaf) просто проверяем:

<!-- Если сообщение об ошибке прилетело — показываем его -->
<div th:if="${errorMessage}" class="alert alert-danger" style="color: red; font-weight: bold;">
    <span th:text="${errorMessage}"></span>
</div>
<!-- А тут сама форма регистрации -->

И вуаля! Пользователь вместо стопки трейсов видит вежливое (или не очень) предложение зарегистрироваться. Всё чисто, аккуратно, и никто не обосрался.

Итоги, чтобы не ебать мозг:

  1. @ControllerAdvice — это глобальный папочка, который за всеми присматривает.
  2. @ExceptionHandler — ловушка для конкретного типа исключений.
  3. RedirectAttributes и addFlashAttribute — твой лучший способ передать данные при редиректе, не палясь в URL.
  4. ЛОГГЕР — святое дело. Без него ты как слепой котёнок в подвале.
  5. Вся эта конструкция позволяет не превращать каждый контроллер в помойку с try-catch блоками. Красота, блядь!