Прежде, чем погружаться в код, зададимся простым, но важным вопросом: зачем вообще нужна валидация? Давайте представим, что ваш REST API — это вход на вечеринку, а данные запроса — это гости. Вы не можете позволить каждому пройти: сначала нужно убедиться, что данные соответствуют определённым критериям (например, возраст больше 18, нет подозрительных объектов вроде "NaN" вместо имени). Если на вечеринку попадают неподобающие "гости", приложение может "устроить концерт", вылететь с ошибками или "поджечь сервер".
Валидация решает эту проблему, проверяя данные запроса ещё до их обработки. Это помогает предотвратить бессмысленные ошибки на сервере, обеспечивает целостность данных и упрощает отладку.
Основы Bean Validation API
Spring использует спецификацию Bean Validation API для реализации проверки данных. Эта спецификация была введена в Java EE 6 еще в 2009 году (да, это очень древняя магия в терминах IT). На практике мы используем реализацию Hibernate Validator, который входит в Spring Boot по умолчанию.
Основная идея Bean Validation — использование аннотаций для указания правил проверки на уровне модели данных (тех самых классов DTO или сущностей). Вот список популярных аннотаций:
| Аннотация | Описание |
|---|---|
@NotNull |
Поле не может быть null |
@Size |
Указывает на минимально и максимально допустимую длину строки |
@Min |
Минимальное значение для числовых полей |
@Max |
Максимальное значение для числовых полей |
@Email |
Проверяет формат строки как адрес электронной почты |
@Pattern |
Позволяет задать регулярное выражение для проверки строки |
Эти аннотации цепляются к полям и выполняют магию валидации, но их функциональность раскрывается только благодаря мощи @Valid.
Применение Bean Validation в Spring REST API
Теперь перейдем от теории к практике. Мы будем валидировать входящие данные в запросе. Для примера создадим простое API для управления пользователями (потому что пользователей любят все API).
Создание DTO для пользователя
DTO (Data Transfer Object) — это класс, который передает данные между клиентом и сервером. Сначала создадим DTO с аннотациями валидации:
package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class UserDTO {
@NotNull(message = "Имя пользователя не может быть пустым")
@Size(min = 2, max = 50, message = "Имя пользователя должно быть от 2 до 50 символов")
private String name;
@NotNull(message = "Электронная почта обязательна")
@Email(message = "Неверный формат электронной почты")
private String email;
@NotNull(message = "Пароль не может быть пустым")
@Size(min = 6, message = "Пароль должен содержать минимум 6 символов")
private String password;
// Геттеры и сеттеры...
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Здесь мы задали ограничения для каждого поля. Например, имя пользователя должно быть от 2 до 50 символов, а email должен соответствовать формату.
Добавление валидации в контроллер
Валидация данных происходит автоматически, если вы укажете аннотацию @Valid перед объектом в запросе. Давайте создадим контроллер для создания пользователя:
package com.example.demo.controller;
import com.example.demo.dto.UserDTO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping
public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO user) {
// Переданный объект уже прошел валидацию, если мы здесь!
return new ResponseEntity<>("Пользователь успешно создан: " + user.getName(), HttpStatus.CREATED);
}
}
Итак, что здесь происходит?
- Мы добавили
@Validперед параметромuser. Эта аннотация сигнализирует Spring, что объект нужно проверить перед выполнением метода. - Если данные в
userне соответствуют требованиям, Spring автоматически генерирует ошибку 400 (Bad Request) и возвращает описание ошибки.
Теперь давайте смоделируем ситуацию, где клиент отправляет неправильные данные.
Пример некорректного запроса
Клиент отправляет HTTP POST-запрос на /users с таким телом:
{
"name": "",
"email": "not-an-email",
"password": "123"
}
Spring сработает и отправит в ответ ошибку с описанием:
{
"timestamp": "2023-10-10T12:00:00.000+00:00",
"status": 400,
"errors": [
"Имя пользователя не может быть пустым",
"Неверный формат электронной почты",
"Пароль должен содержать минимум 6 символов"
]
}
Обратите внимание, что сообщения об ошибках берутся из полей message в аннотациях нашего DTO.
Обработка ошибок валидации
Стандартная обработка ошибок не всегда удобна. Давайте кастомизируем её с помощью @ExceptionHandler.
Создадим глобальный обработчик исключений:
package com.example.demo.exception;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
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<>();
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
errors.put(fieldError.getField(), fieldError.getDefaultMessage());
}
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}
Теперь наш ответ на некорректный запрос будет выглядеть так:
{
"name": "Имя пользователя не может быть пустым",
"email": "Неверный формат электронной почты",
"password": "Пароль должен содержать минимум 6 символов"
}
Такое представление ошибок проще читать и обрабатывать клиентам.
Применение в реальной жизни и частые ошибки
Где это пригодится?
- На собеседованиях. Умение валидировать данные — это базовый скилл для любого backend-разработчика. Вас обязательно спросят: "Как вы обрабатываете невалидные данные в API?"
- В реальных проектах. Валидация защищает сервер от мусорных данных и облегчает обслуживание кода.
Частые ошибки
- Забыли
@Valid. Если вы не добавите@Valid, то данные просто не будут проверяться. - Неправильные сообщения об ошибках. Всегда старайтесь писать понятные и дружелюбные сообщения для пользователей.
- Ограничения на уровне базы данных. Помните, что валидация на уровне кода не заменяет ограничения на уровне базы, такие как NOT NULL или CHECK.
Теперь вы не только знаете, как валидировать данные в Spring, но и можете избежать типичных ловушек!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ