Перейти к основному содержимому

Различия в аннотациях @Valid и @Validated в Spring

· 5 мин. чтения

Задача: Сумма двух чисел

Напишите функцию twoSum. Которая получает массив целых чисел nums и целую сумму target, а возвращает индексы двух чисел, сумма которых равна target. Любой набор входных данных имеет ровно одно решение, и вы не можете использовать один и тот же элемент дважды. Ответ можно возвращать в любом порядке...

ANDROMEDA

1. Обзор

В этом кратком руководстве мы сосредоточимся на различиях между аннотациями @Valid и @Validated в Spring.

Проверка ввода пользователей — обычная функция в большинстве наших приложений. В экосистеме Java мы специально используем Java Standard Bean Validation API для поддержки этого, который хорошо интегрирован со Spring, начиная с версии 4.0. Аннотации @Valid и @Validated основаны на этом API Standard Bean .

В следующих разделах мы рассмотрим их более подробно.

2. @Valid и @Validated аннотации

В Spring мы используем аннотацию JSR-303 @Valid для проверки уровня метода . Мы также используем его, чтобы пометить атрибут члена для проверки . Однако эта аннотация не поддерживает групповую проверку.

Группы помогают ограничить ограничения, применяемые во время проверки. Одним из конкретных вариантов использования являются мастера пользовательского интерфейса. На первом этапе у нас может быть определенная подгруппа полей. На следующем шаге может быть другая группа, принадлежащая тому же компоненту. Поэтому нам нужно применять ограничения к этим ограниченным полям на каждом этапе, но @Valid этого не поддерживает.

В этом случае для группового уровня мы должны использовать Spring @Validated, который является вариантом JSR-303 @Valid . Это используется на уровне метода. Для маркировки атрибутов членов мы продолжаем использовать аннотацию @Valid .

Теперь давайте углубимся и посмотрим на использование этих аннотаций на примере.

3. Пример

Рассмотрим простую форму регистрации пользователя, разработанную с использованием Spring Boot. Для начала у нас будут только атрибуты имени и пароля :

public class UserAccount {

@NotNull
@Size(min = 4, max = 15)
private String password;

@NotBlank
private String name;

// standard constructors / setters / getters / toString

}

Далее рассмотрим контроллер. Здесь у нас будет метод saveBasicInfo с аннотацией @Valid для проверки ввода пользователя:

@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
@Valid @ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}

Теперь давайте проверим этот метод:

@Test
public void givenSaveBasicInfo_whenCorrectInput_thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}

Убедившись, что тест проходит успешно, мы расширим функциональность. Следующим логическим шагом будет преобразование этой формы регистрации в многоступенчатую, как это делается в большинстве мастеров. Первый шаг с именем и паролем остается без изменений. На втором этапе мы получим дополнительную информацию, например возраст и номер телефона . Затем мы обновим наш объект домена следующими дополнительными полями:

public class UserAccount {

@NotNull
@Size(min = 4, max = 15)
private String password;

@NotBlank
private String name;

@Min(value = 18, message = "Age should not be less than 18")
private int age;

@NotBlank
private String phone;

// standard constructors / setters / getters / toString

}

Однако на этот раз мы заметим, что предыдущий тест не проходит. Это связано с тем, что мы не пропускаем поля возраста и телефона , которых по-прежнему нет на картинке в пользовательском интерфейсе . Для поддержки такого поведения нам потребуется групповая проверка и аннотация @Validated .

Для этого нам нужно сгруппировать поля, создав две отдельные группы. Во-первых, нам нужно создать два интерфейса маркеров, отдельный для каждой группы или каждого шага. Мы можем обратиться к нашей статье о групповой проверке для точной реализации этого. Здесь давайте сосредоточимся на различиях в аннотациях.

У нас будет интерфейс BasicInfo для первого шага и AdvanceInfo для второго шага. Кроме того, мы обновим наш класс UserAccount , чтобы использовать эти интерфейсы маркеров:

public class UserAccount {

@NotNull(groups = BasicInfo.class)
@Size(min = 4, max = 15, groups = BasicInfo.class)
private String password;

@NotBlank(groups = BasicInfo.class)
private String name;

@Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
private int age;

@NotBlank(groups = AdvanceInfo.class)
private String phone;

// standard constructors / setters / getters / toString

}

Кроме того, мы обновим наш контроллер, чтобы использовать аннотацию @Validated вместо @Valid :

@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
@Validated(BasicInfo.class)
@ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result, ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}

В результате этого обновления наш тест теперь проходит успешно. Мы также протестируем этот новый метод:

@Test
public void givenSaveBasicInfoStep1_whenCorrectInput_thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}

Это тоже работает успешно. Таким образом, мы видим, насколько важно использование @Validated для групповой проверки.

Далее давайте посмотрим, как @Valid необходим для запуска проверки вложенных атрибутов.

4. Использование аннотации @Valid для маркировки вложенных объектов

Аннотация @Valid используется для обозначения вложенных атрибутов, в частности . Это запускает проверку вложенного объекта. Например, в нашем текущем сценарии мы можем создать объект UserAddress :

public class UserAddress {

@NotBlank
private String countryCode;

// standard constructors / setters / getters / toString
}

Чтобы обеспечить проверку этого вложенного объекта, мы украсим атрибут аннотацией @Valid :

public class UserAccount {

//...

@Valid
@NotNull(groups = AdvanceInfo.class)
private UserAddress useraddress;

// standard constructors / setters / getters / toString
}

5. Плюсы и минусы

Давайте рассмотрим некоторые плюсы и минусы использования аннотаций @Valid и @Validated в Spring.

Аннотация @Valid обеспечивает проверку всего объекта. Важно отметить, что он выполняет проверку всего графа объектов. Однако это создает проблемы для сценариев, требующих только частичной проверки.

С другой стороны, мы можем использовать @Validated для групповой проверки, включая приведенную выше частичную проверку. Однако в этом случае проверенные объекты должны знать правила проверки для всех групп или вариантов использования, в которых они используются. Здесь мы смешиваем проблемы, что может привести к анти-шаблону.

6. Заключение

В этой краткой статье мы рассмотрели ключевые различия между аннотациями @Valid и @Validated .

В заключение, для любой базовой проверки мы будем использовать аннотацию JSR @Valid в наших вызовах методов. С другой стороны, для любой групповой проверки, включая групповые последовательности , нам нужно будет использовать аннотацию Spring @Validated в вызове нашего метода. Аннотация @Valid также необходима для запуска проверки вложенных свойств.

Как всегда, код, представленный в этой статье, доступен на GitHub .