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

Пользовательская проверка Spring MVC

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

1. Обзор

Как правило, когда нам нужно проверить пользовательский ввод, Spring MVC предлагает стандартные предопределенные валидаторы.

Однако, когда нам нужно проверить более конкретный тип ввода, у нас есть возможность создать собственную пользовательскую логику проверки.

В этом уроке мы сделаем именно это; мы создадим настраиваемый валидатор для проверки формы с полем номера телефона, а затем покажем настраиваемый валидатор для нескольких полей.

В этом руководстве основное внимание уделяется Spring MVC. В нашей статье под названием « Проверка в Spring Boot » описывается, как создавать собственные проверки в Spring Boot.

2. Настройка

Чтобы воспользоваться преимуществами API, мы добавим зависимость в наш файл pom.xml :

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>

Последнюю версию зависимости можно проверить здесь .

Если мы используем Spring Boot, мы можем добавить только spring-boot-starter-web , что также приведет к зависимости от hibernate-validator .

3. Пользовательская проверка

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

Итак, давайте создадим наш собственный валидатор, который проверяет телефонные номера . Номер телефона должен состоять не менее чем из восьми цифр, но не более чем из 11 цифр.

4. Новая аннотация

Давайте создадим новый @interface для определения нашей аннотации:

@Documented
@Constraint(validatedBy = ContactNumberValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ContactNumberConstraint {
String message() default "Invalid phone number";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

С помощью аннотации @Constraint мы определили класс, который будет проверять наше поле. Сообщение () — это сообщение об ошибке, отображаемое в пользовательском интерфейсе. Наконец, дополнительный код в основном представляет собой шаблонный код, соответствующий стандартам Spring.

5. Создание валидатора

Теперь давайте создадим класс валидатора, который применяет правила нашей проверки:

public class ContactNumberValidator implements 
ConstraintValidator<ContactNumberConstraint, String> {

@Override
public void initialize(ContactNumberConstraint contactNumber) {
}

@Override
public boolean isValid(String contactField,
ConstraintValidatorContext cxt) {
return contactField != null && contactField.matches("[0-9]+")
&& (contactField.length() > 8) && (contactField.length() < 14);
}

}

Класс проверки реализует интерфейс ConstraintValidator , а также должен реализовать метод isValid ; именно в этом методе мы определили наши правила проверки.

Естественно, здесь мы используем простое правило проверки, чтобы показать, как работает валидатор.

ConstraintValidator определяет логику для проверки данного ограничения для данного объекта. Реализации должны соответствовать следующим ограничениям:

  • объект должен разрешаться в непараметризованный тип
  • универсальные параметры объекта должны быть неограниченными подстановочными знаками

6. Применение аннотации проверки

В нашем случае мы создали простой класс с одним полем для применения правил проверки. Здесь мы настраиваем наше аннотированное поле для проверки:

@ContactNumberConstraint
private String phone;

Мы определили строковое поле и аннотировали его нашей пользовательской аннотацией @ContactNumberConstraint. В нашем контроллере мы создали сопоставления и обработали все ошибки:

@Controller
public class ValidatedPhoneController {

@GetMapping("/validatePhone")
public String loadFormPage(Model m) {
m.addAttribute("validatedPhone", new ValidatedPhone());
return "phoneHome";
}

@PostMapping("/addValidatePhone")
public String submitForm(@Valid ValidatedPhone validatedPhone,
BindingResult result, Model m) {
if(result.hasErrors()) {
return "phoneHome";
}
m.addAttribute("message", "Successfully saved phone: "
+ validatedPhone.toString());
return "phoneHome";
}
}

Мы определили этот простой контроллер с одной JSP -страницей и использовали метод submitForm для принудительной проверки нашего номера телефона.

7. Вид

Наше представление представляет собой простую JSP-страницу с формой с одним полем. Когда пользователь отправляет форму, поле проверяется нашим пользовательским валидатором и перенаправляется на ту же страницу с сообщением об успешной или неудачной проверке:

<form:form 
action="/${pageContext.request.contextPath}/addValidatePhone"
modelAttribute="validatedPhone">
<label for="phoneInput">Phone: </label>
<form:input path="phone" id="phoneInput" />
<form:errors path="phone" cssClass="error" />
<input type="submit" value="Submit" />
</form:form>

8. Тесты

Теперь давайте протестируем наш контроллер, чтобы проверить, дает ли он нам соответствующий ответ и представление:

@Test
public void givenPhonePageUri_whenMockMvc_thenReturnsPhonePage(){
this.mockMvc.
perform(get("/validatePhone")).andExpect(view().name("phoneHome"));
}

Давайте также проверим, что наше поле проверяется на основе пользовательского ввода:

@Test
public void
givenPhoneURIWithPostAndFormData_whenMockMVC_thenVerifyErrorResponse() {

this.mockMvc.perform(MockMvcRequestBuilders.post("/addValidatePhone").
accept(MediaType.TEXT_HTML).
param("phoneInput", "123")).
andExpect(model().attributeHasFieldErrorCode(
"validatedPhone","phone","ContactNumberConstraint")).
andExpect(view().name("phoneHome")).
andExpect(status().isOk()).
andDo(print());
}

В тесте мы предоставляем пользователю ввод «123», и, как мы и ожидали, все работает, и мы видим ошибку на стороне клиента .

9. Проверка уровня пользовательского класса

Пользовательская аннотация проверки также может быть определена на уровне класса для проверки более чем одного атрибута класса.

Обычный вариант использования для этого сценария — проверка того, имеют ли два поля класса совпадающие значения.

9.1. Создание аннотации

Давайте добавим новую аннотацию под названием FieldsValueMatch , которую впоследствии можно будет применить к классу. Аннотация будет иметь два параметра, field и fieldMatch, которые представляют имена сравниваемых полей:

@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {

String message() default "Fields values don't match!";

String field();

String fieldMatch();

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface List {
FieldsValueMatch[] value();
}
}

Мы видим, что наша пользовательская аннотация также содержит подинтерфейс List для определения нескольких аннотаций FieldsValueMatch в классе.

9.2. Создание валидатора

Далее нам нужно добавить класс FieldsValueMatchValidator , который будет содержать фактическую логику проверки:

public class FieldsValueMatchValidator 
implements ConstraintValidator<FieldsValueMatch, Object> {

private String field;
private String fieldMatch;

public void initialize(FieldsValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
}

public boolean isValid(Object value,
ConstraintValidatorContext context) {

Object fieldValue = new BeanWrapperImpl(value)
.getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value)
.getPropertyValue(fieldMatch);

if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}

Метод isValid() извлекает значения двух полей и проверяет, равны ли они.

9.3. Применение аннотации

Создадим класс модели NewUserForm , предназначенный для данных, необходимых для регистрации пользователя. Он будет иметь два атрибута электронной почты и пароля , а также два атрибута verifyEmail и verifyPassword для повторного ввода двух значений.

Поскольку у нас есть два поля для проверки соответствующих им полей, давайте добавим две аннотации @FieldsValueMatch в класс NewUserForm , одну для значений электронной почты и одну для значений пароля :

@FieldsValueMatch.List({ 
@FieldsValueMatch(
field = "password",
fieldMatch = "verifyPassword",
message = "Passwords do not match!"
),
@FieldsValueMatch(
field = "email",
fieldMatch = "verifyEmail",
message = "Email addresses do not match!"
)
})
public class NewUserForm {
private String email;
private String verifyEmail;
private String password;
private String verifyPassword;

// standard constructor, getters, setters
}

Чтобы проверить модель в Spring MVC, давайте создадим контроллер с POST-сопоставлением /user , который получает объект NewUserForm с аннотацией @Valid и проверяет наличие ошибок проверки:

@Controller
public class NewUserController {

@GetMapping("/user")
public String loadFormPage(Model model) {
model.addAttribute("newUserForm", new NewUserForm());
return "userHome";
}

@PostMapping("/user")
public String submitForm(@Valid NewUserForm newUserForm,
BindingResult result, Model model) {
if (result.hasErrors()) {
return "userHome";
}
model.addAttribute("message", "Valid form");
return "userHome";
}
}

9.4. Тестирование аннотации

Чтобы проверить нашу пользовательскую аннотацию на уровне класса, давайте напишем тест JUnit , который отправляет соответствующую информацию в конечную точку /user , а затем проверяет, что ответ не содержит ошибок:

public class ClassValidationMvcTest {
private MockMvc mockMvc;

@Before
public void setup(){
this.mockMvc = MockMvcBuilders
.standaloneSetup(new NewUserController()).build();
}

@Test
public void givenMatchingEmailPassword_whenPostNewUserForm_thenOk()
throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.post("/user")
.accept(MediaType.TEXT_HTML).
.param("email", "john@yahoo.com")
.param("verifyEmail", "john@yahoo.com")
.param("password", "pass")
.param("verifyPassword", "pass"))
.andExpect(model().errorCount(0))
.andExpect(status().isOk());
}
}

Затем мы также добавим тест JUnit , который отправляет несовпадающую информацию в конечную точку /user и утверждает, что результат будет содержать две ошибки:

@Test
public void givenNotMatchingEmailPassword_whenPostNewUserForm_thenOk()
throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders
.post("/user")
.accept(MediaType.TEXT_HTML)
.param("email", "john@yahoo.com")
.param("verifyEmail", "john@yahoo.commmm")
.param("password", "pass")
.param("verifyPassword", "passsss"))
.andExpect(model().errorCount(2))
.andExpect(status().isOk());
}

10. Резюме

В этой краткой статье мы узнали, как создавать собственные валидаторы для проверки поля или класса, а затем подключать их к Spring MVC.

Как всегда, код из этой статьи доступен на Github .