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 .