Введение
Проверка данных является важной частью любого приложения. Неправильные или искаженные данные могут привести к неожиданному поведению, уязвимостям безопасности и неточным расчетам. Spring, одна из самых популярных платформ для создания Java-приложений, предоставляет отличный набор инструментов для обеспечения целостности данных с помощью аннотаций @Valid
и @Validated
. В этой статье мы углубимся в эти аннотации и поймем, как их можно эффективно использовать для проверки данных.
Понимание @Valid
и @Validated
И @Valid
, и @Validated
являются неотъемлемыми аннотациями в Spring, когда дело доходит до проверки данных. Однако, чтобы полностью раскрыть их потенциал, необходимо понимать нюансы каждого из них.
@Действительный
- Происхождение:
@Valid
является частью спецификации проверки Java Bean (JSR 380), которую поддерживает Spring. Следовательно, он не специфичен для Spring и может использоваться и в других контекстах Java. - Применение. В основном используется для запуска проверки объекта, особенно вложенных объектов. При размещении перед аргументом метода механизм проверки Spring проверяет объект на наличие любых ограничений (например,
@NotNull
,@Size
и т. д.) и гарантирует, что они удовлетворены.
Пример:
public class UserProfile { @Valid private Address address; }
В приведенном выше примере, когда объект UserProfile
проверяется, вложенный объект Address
также подвергается проверке.
- Ограничения. Несмотря на свою мощь,
@Valid
не позволяет выполнять групповую проверку, т. е. применять определенные правила проверки при определенных условиях.
@Проверено
- Происхождение. В отличие от
@Valid
,@Validated
зависит от Spring. Он расширяет функциональность@Valid
и обеспечивает большую гибкость. - Применение. Помимо запуска проверки, как и
@Valid
,@Validated
предоставляет возможность групповой проверки. Это особенно полезно, если вы хотите иметь разные правила проверки для одного и того же объекта на основе разных контекстов (например, операции создания и обновления).
Пример:
public class User { @NotBlank(groups = Creation.class) private String username; @Size(min = 8, groups = Update.class) private String password; } public interface Creation {} public interface Update {}
Теперь при создании пользователя проверяется только ограничение username
. При обновлении проверяется только ограничение password
.
- Расширенные варианты использования.Помимо основного использования в контроллерах,
@Validated
можно использовать в компонентах, конфигурациях и службах Spring. Например, при проверке параметров метода или даже свойств конфигурации.
Пример:
@Service @Validated public class UserService { public void registerUser(@Validated(Creation.class) User user) { // business logic } }
В приведенном выше сервисе метод registerUser
явно проверяет объект User
на основе группы Creation
.
- Ограничения:
@Validated
нельзя использовать во вложенных полях внутри объекта. Для вложенной проверки все равно следует использовать@Valid
. Кроме того, поскольку@Validated
специфичен для Spring, он может быть не распознан за пределами экосистемы Spring.
Настройка проверки данных
Прежде чем углубляться в аннотации, убедитесь, что у вас есть необходимые зависимости:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.5.Final</version> </dependency>
Создание модели с ограничениями
Рассмотрим простую модель User
:
import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; public class User { @NotBlank(message = "Name is mandatory") private String name; @Email(message = "Email should be valid") private String email; @Size(min = 6, message = "Password must be at least 6 characters long") private String password; // Getters, Setters, etc. }
Использование @Valid
в контроллере
Теперь, когда вы создаете конечную точку REST для приема объекта User, используйте @Valid
:
@RestController @RequestMapping("/users") public class UserController { @PostMapping("/") public ResponseEntity<String> createUser(@Valid @RequestBody User user) { // Save user or perform some other operations return ResponseEntity.ok("User created successfully"); } }
Если объект User
не удовлетворяет ограничениям, будет выброшен MethodArgumentNotValidException
.
Обработка ошибок проверки
Обеспечение проверки данных — это только половина дела. Другая половина — корректная обработка ошибок и передача их обратно клиенту. Если правила проверки не соблюдаются, Spring генерирует исключения. Эффективный перехват и обработка этих исключений может значительно улучшить взаимодействие с пользователем.
Поведение по умолчанию
Когда в приложении Spring возникает ошибка проверки, выдается MethodArgumentNotValidException
. По умолчанию Spring возвращает общий ответ об ошибке со статусом 400 Bad Request. Этот ответ содержит подробную информацию об ошибках, но он может быть более подробным, чем необходимо, и не всегда удобен для пользователя.
Настройка ответа на ошибку
Вместо того, чтобы полагаться на структуру ошибок по умолчанию, вы можете вернуть собственное сообщение об ошибке или объект, который соответствует потребностям вашего приложения или потребителей API.
Использование ExceptionHandler
Аннотация @ExceptionHandler
может использоваться для определения пользовательского поведения при возникновении исключений.
Пример:
@RestControllerAdvice public class CustomExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach((error) -> { String fieldName = ((FieldError) error).getField(); String errorMessage = error.getDefaultMessage(); errors.put(fieldName, errorMessage); }); return ResponseEntity.badRequest().body(errors); } }
Этот обработчик захватывает MethodArgumentNotValidException
и возвращает более краткую карту имен полей и связанных с ними сообщений об ошибках проверки.
Предоставление большего контекста
Для более полных API может быть полезно предоставить дополнительный контекст в ответе об ошибке.
Пример:
public class ApiValidationError { private String field; private String message; private Object rejectedValue; // Constructors, Getters, Setters }
Используя вышеуказанный класс:
@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<List<ApiValidationError>> handleDetailedValidationExceptions(MethodArgumentNotValidException ex) { List<ApiValidationError> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(err -> new ApiValidationError(err.getField(), err.getDefaultMessage(), err.getRejectedValue())) .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errors); }
Теперь ответ об ошибке также включает фактическое значение, вызвавшее ошибку проверки, что дает потребителям API больше информации.
Глобальные и специфичные для контроллера обработчики
Хотя @RestControllerAdvice
предоставляет глобальный механизм обработки исключений, иногда вам может потребоваться определенное поведение для определенных контроллеров. Использование @ExceptionHandler
внутри @RestController
гарантирует, что обработчик активен только для этого конкретного контроллера.
Учитывайте пользовательский опыт
При составлении сообщений об ошибках всегда учитывайте интересы конечного пользователя. Убедитесь, что сообщения понятны, и подскажите пользователям, как устранить проблему. Например, вместо «Размер должен быть от 8 до 30» более удобным для пользователя может быть сообщение «Ваш пароль должен содержать от 8 до 30 символов».
Групповая проверка с помощью @Validated
Хотя @Valid
идеально подходит для стандартной проверки, существуют сценарии, в которых могут потребоваться специальные проверки на основе определенных условий. Вот где сияет @Validated
.
Определить группы:
public interface OnCreate {} public interface OnUpdate {}
Настройте модель User
для проверки на основе групп:
public class User { @NotBlank(message = "Name is mandatory", groups = OnCreate.class) private String name; @Email(message = "Email should be valid", groups = OnCreate.class) private String email; @Size(min = 6, message = "Password must be at least 6 characters long", groups = OnUpdate.class) private String password; // Getters, Setters, etc. }
Теперь в вашем контроллере:
@PostMapping("/") public ResponseEntity<String> createUser(@Validated(OnCreate.class) @RequestBody User user) { //... } @PutMapping("/{id}") public ResponseEntity<String> updateUser(@PathVariable Long id, @Validated(OnUpdate.class) @RequestBody User user) { //... }
Метод createUser
проверит name
и email
, а updateUser
проверит password
.
Проверка Spring Service Beans
В типичном приложении Spring проверка часто происходит на уровне контроллера. Однако иногда важно обеспечить соблюдение правил проверки на уровне обслуживания, чтобы обеспечить целостность данных и соответствие бизнес-правилам. Этого можно достичь с помощью мощной аннотации @Validated
даже за пределами классов контроллера.
Зачем проверять на уровне обслуживания?
Во-первых, давайте поймем, почему можно рассмотреть возможность проверки на уровне сервиса:
- Повторное использование. Службы можно вызывать из разных частей приложения, а не только из контроллеров. Обеспечение проверки на уровне службы гарантирует согласованность данных независимо от того, откуда вызывается служба.
- Развязка. Перенос проверки с контроллера на службу отделяет логику проверки от веб-уровня, делая службы более модульными и независимыми.
- Детальный контроль.Проверка уровня сервиса позволяет разработчикам внедрять тонкую логику проверки, принимая во внимание различные бизнес-сценарии, которые могут быть не сразу очевидны на уровне контроллера.
Базовая проверка с помощью @Validated
Как и в контроллерах, аннотацию @Validated
можно использовать в служебных bean-компонентах Spring.
Пример:
@Service @Validated public class UserService { public void registerUser(@Validated User user) { // Handle user registration } }
В приведенном выше примере при вызове registerUser
Spring проверит объект User
. Если проверка не удалась, будет выдано исключение нарушения ограничения.
Групповая проверка в сервисах
Групповую проверку также можно легко интегрировать в сервисы. Например, предположим, что у вас есть разные группы проверки для создания и обновления пользователя.
Пример:
@Service @Validated public class UserService { public void createUser(@Validated(OnCreate.class) User user) { // Handle user creation } public void updateUser(@Validated(OnUpdate.class) User user) { // Handle user update } }
Здесь метод createUser
проверяет объект User
на основе группы OnCreate
, а updateUser
проверяет на основе группы OnUpdate
.
Обработка исключений проверки в службах
Хотя обработка исключений проверки в контроллерах является обычным явлением, их обработка внутри служб позволяет более конкретно обрабатывать ошибки и отправлять сообщения. Например, преобразование исключения проверки в пользовательское бизнес-исключение, которое смогут понять и обработать потребители уровня обслуживания.
Пример:
@Service @Validated public class UserService { public void createUser(@Validated(OnCreate.class) User user) { try { // Logic } catch (ConstraintViolationException e) { throw new CustomBusinessException("User data is not valid.", e); } } }
Интеграция с другими компонентами
Проверка сервиса предназначена не только для объектов домена. Вы также можете проверить параметры метода, свойства конфигурации и многое другое. Это гарантирует, что любые данные, обрабатываемые службой, независимо от их источника, являются правильными и соответствуют требованиям.
Заключение
Spring предлагает мощные инструменты для проверки данных, обеспечивающие целостность данных во всем вашем приложении. Освоив использование @Valid
и @Validated
, разработчики могут легко налагать ограничения на данные, корректно обрабатывать ошибки и даже вводить правила проверки на основе групп. Включение этих аннотаций обеспечивает надежное и отказоустойчивое приложение, защищая от ошибочного ввода данных.
Понравилось чтение? Еще не являетесь участником Medium? Вы можете поддержать мою работу напрямую, зарегистрировавшись по моей реферальной ссылке здесь. Это быстро, легко и не требует дополнительных затрат. Спасибо за вашу поддержку!
Спасибо, что дочитали до конца. Пожалуйста, подумайте о том, чтобы подписаться на автора и эту публикацию. Посетите Stackademic, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.