Введение

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

  1. Проверка Spring Framework
  2. Стартер проверки загрузки Spring
  3. Документация по валидатору Hibernate

Понравилось чтение? Еще не являетесь участником Medium? Вы можете поддержать мою работу напрямую, зарегистрировавшись по моей реферальной ссылке здесь. Это быстро, легко и не требует дополнительных затрат. Спасибо за вашу поддержку!

Спасибо, что дочитали до конца. Пожалуйста, подумайте о том, чтобы подписаться на автора и эту публикацию. Посетите Stackademic, чтобы узнать больше о том, как мы демократизируем бесплатное образование в области программирования во всем мире.