Представьте, что вы пытаетесь заполнить форму на сайте, а система в ответ выдаёт: "Ошибка 400. Неправильный запрос. Поле user_input не соответствует требованиям валидации." Чувствуете этот холодный роботизированный тон? Пользователю сложно понять, что пошло не так и как это исправить.
Правильно оформленные сообщения об ошибках помогают:
- Направить пользователя на правильный путь, указав, что именно он сделал неправильно.
- Снизить уровень фрустрации и повысить удовлетворённость пользователя вашим приложением.
- Помочь разработчикам быстрее находить проблемы, используя логирование ошибок.
Теперь, когда мы знаем, зачем это нужно, приступим к делу.
Автоматическое формирование сообщений с Bean Validation API
В прошлых лекциях мы уже добавляли аннотации для валидации данных. Напомним простой пример:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDto {
@NotNull(message = "Имя пользователя не может быть пустым")
@Size(min = 2, max = 30, message = "Имя пользователя должно быть от 2 до 30 символов")
private String username;
@NotNull(message = "Электронная почта обязательна")
@Size(max = 50, message = "Электронная почта не может содержать более 50 символов")
private String email;
// Геттеры и сеттеры
}
Здесь message передаёт строку, которая будет отображена пользователю при нарушении правила. Это отличный старт, но мы можем сделать ещё лучше.
Локализация сообщений с использованием MessageSource
Если вы разрабатываете приложение для разных языков (например, русский и английский), было бы здорово поддерживать локализацию. Смотрите, как это делается.
Настройка MessageSource
Добавим в проект класс конфигурации для локализации:
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
@Configuration
public class MessageConfig {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages/validation");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
Этот MessageSource будет читать файлы локализации. Например, создадим файл validation.properties (по умолчанию для английского) и validation_ru.properties для русского языка:
validation.properties
username.notNull=Username is required
username.size=Username must be between 2 and 30 characters
email.notNull=Email is required
email.size=Email cannot exceed 50 characters
validation_ru.properties
username.notNull=Имя пользователя не может быть пустым
username.size=Имя пользователя должно быть от 2 до 30 символов
email.notNull=Электронная почта обязательна
email.size=Электронная почта не может содержать более 50 символов
Изменение DTO для использования локализованных сообщений
Теперь мы можем использовать message с ключами из файлов локализации:
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDto {
@NotNull(message = "{username.notNull}")
@Size(min = 2, max = 30, message = "{username.size}")
private String username;
@NotNull(message = "{email.notNull}")
@Size(max = 50, message = "{email.size}")
private String email;
// Геттеры и сеттеры
}
Вуаля! Теперь сообщения автоматически подтягиваются в зависимости от языка пользователя.
Валидация и формирование сообщений в контроллерах
Допустим, у нас есть контроллер для обработки запроса на создание пользователя:
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
@Validated
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDto userDto) {
// Логика сохранения пользователя...
return ResponseEntity.ok("Пользователь успешно создан!");
}
}
Если пользователь отправит некорректные данные, например, пустое имя, Spring выбросит ошибку MethodArgumentNotValidException. Чтобы обработать её и вернуть красивое сообщение, добавим следующий обработчик:
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Если пользователь отправит запрос:
{
"username": "",
"email": "anInvalidEmail@verylongemailthatexceedsfiftycharacters.com"
}
На выходе мы получим структурированный и понятный JSON:
{
"username": "Имя пользователя не может быть пустым",
"email": "Электронная почта не может содержать более 50 символов"
}
Локализация через HTTP-заголовки
Spring позволяет менять язык в запросах через заголовок Accept-Language. Например:
Accept-Language: en
В таком случае сообщения об ошибках будут отображаться на английском. Если указать ru, то сообщения автоматически переключатся на русский. Это делается через встроенный LocaleResolver.
Добавим LocaleResolver:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import java.util.Locale;
@Configuration
public class LocaleConfig {
@Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(Locale.ENGLISH);
return localeResolver;
}
}
Теперь язык будет определяться автоматически на основании заголовка запроса.
Полезные советы
- Пишите понятные сообщения. Избегайте аббревиатур и технического жаргона. Пользователь должен понять, что ему делать.
- Логируйте ошибки. Если пользователь видит только часть информации, остальное логируйте на сервере — это поможет в поиске и устранении проблем.
- Тестируйте локализацию. Убедитесь, что сообщения корректно отображаются на всех поддерживаемых языках.
Теперь у нас есть мощный инструмент для вывода сообщений об ошибках, которые понятны и пользователю, и разработчику. Такие подходы делают приложение более профессиональным и дружественным. В следующей лекции мы рассмотрим, как настроить локализацию сообщений ещё глубже и подключить обработку ошибок на глобальном уровне.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ