그날그날메모

Spring MVC 사용자 지정 유효성 검사

Java

Spring MVC 사용자 지정 유효성 검사

그날그날메모 2021. 4. 19. 18:25

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 부팅을 사용하는 경우, 우리는 단지 추가 할 수 있습니다 스프링 부팅 스타터 웹 , 에 나타납니다 최대 절전 모드 - 유효성 검사기 도 의존성을.

3. 사용자 지정 유효성 검사

사용자 지정 유효성 검사기를 만들려면 자체 어노테이션을 배포하고 모델에서이를 사용하여 유효성 검사 규칙을 적용해야합니다.

이제 전화 번호를 확인하는 맞춤 유효성 검사기를 만들어 보겠습니다 . 전화 번호는 8 자리 이상 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 라는 새 어노테이션을 추가해 보겠습니다 . 어노테이션에는 비교할 필드의 이름을 나타내는 두 개의 매개 변수 fieldfieldMatch 가 있습니다.

@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();
    }
}

커스텀 어노테이션에는 클래스에 여러 FieldsValueMatch 어노테이션 을 정의하기위한 List 하위 인터페이스 도 포함되어 있습니다 .

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. 어노테이션 적용

두 개의 값을 다시 입력하기위한 두 개의 verifyEmailverifyPassword 속성 과 함께 두 개의 이메일비밀번호 속성 이있는 사용자 등록에 필요한 데이터 용 NewUserForm 모델 클래스를 작성해 보겠습니다 .

일치하는 필드에 대해 확인할 두 개의 필드가 있으므로 NewUserForm 클래스 두 개의 @FieldsValueMatch 어노테이션을 추가합니다 . 하나는 이메일 값이고 다른 하나는 암호 값입니다.

@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에서 모델의 유효성을 검사하기 위해 @Valid 어노테이션이 달린 NewUserForm 객체 를 수신하고 유효성 검사 오류가 있는지 확인 하는 / user POST 매핑이 있는 컨트롤러를 만들어 보겠습니다 .

@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. 어노테이션 테스트

커스텀 클래스 레벨 어노테이션을 확인 하기 위해 / user 엔드 포인트에 일치하는 정보를 보내는 JUnit 테스트를 작성 하고 응답에 오류가 없는지 확인합니다.

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());
    }
}

다음으로, 일치하지 않는 정보를 / user 엔드 포인트 로 보내는 JUnit 테스트를 추가 하고 결과에 두 가지 오류가 포함된다는 것을 확인합니다.

@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 의 기사에서 코드를 찾을 수 있습니다 .

참고
  • https://docs.spring.io/spring-framework/docs/current/reference/html
  • https://www.baeldung.com/spring-mvc-custom-validator