작심십일일러의 스프링 시작하기(11)-2

서은경·2022년 9월 1일
0

Spring

목록 보기
20/43

글로벌 범위 Validator와 컨트롤러 범위 Validator

스프링 MVC는 모든 컨트롤러에 적용할 수 있는 글로벌 Validator와 단일 컨트롤러에 적용할 수 있는 Validator를 설정하는 방법을 제공한다. 이를 사용하면 @Valid 어노테이션을 사용해서 커맨드 객체에 검증 기능을 적용할 수 있다!

1. 글로벌 범위 Validator 설정과 @Valid 어노테이션

글로벌 범위 Validator는 모든 컨트롤러에 적용할 수 있는 Validator이다. 글로벌 범위 Validator를 적용하려면 다음 두가지를 설정하면 된다.
일단 설정파일에 모듈을 추가해주고

    // @Valid 어노테이션을 사용하기 위해 validation-api 모듈을 추가해야함(Bean Validation API)
    implementation 'org.springframework.boot:spring-boot-starter-validation'
  • 설정 클래스에서 WebMvcCongifurer 의 getValidator() 메서드가 Validator 구현 객체를 리턴하도록 구현
	@Override
    public Validator getValidator() {
        return new RegisterRequestValidator();
    }
  • 글로벌 범위 Validator가 검증할 커맨드 객체에 @Valid 어노테이션 적용
	@PostMapping("/register/step3")
    public String handleStep3(@Valid RegisterRequest regReq, Errors errors) {
        // @Valid 어노테이션을 쓰려면 Errors 타입 파라미터는 필수이다
        //new RegisterRequestValidator().validate(regReq, errors);
        if (errors.hasErrors()) {
            return "register/step2";
        }
        try {
            memberRegisterService.regist(regReq);
            return "register/step3";
        } catch (DuplicateMemberException e) {
            // 특정 프로퍼티가 아닌 커맨드 객체 자체에 에러코드 추가
            errors.rejectValue("email", "duplicate");
            return "register/step2";
        }
    }

커맨드 객체에 해당하는 파라미터에 @Valid 어노테이션을 붙이면 글로벌 범위 Validator가 해당 타입을 검증할 수 있는지 확인한다. 검증 가능하면 실제 검증을 수행하고 그 결과를 Errors에 저장한다. 이는 요청 처리 메서드 실행 전에 적용된다.

‼️ 이 실습은 예시일 뿐 , 글로벌 Validator로 적합하지 않다. 왜냐면 RegisterRequest 타입의 객체만 검증할 수 있기 때문이다.

2. @InitBinder 어노테이션을 이용한 컨트롤러 범위 Validator

@InitBinder 어노테이션을 이용하면 컨트롤러 범위의 Validator를 이용할 수 있다.

    // 컨트롤러의 요청 처리 메서드를 실행하기 전에 매번 실행됨(handle~ 메서드 실행 전에 매번 호출된다)
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // 컨트롤러 범위에 적용할 Validator 설정
        binder.setValidator(new RegisterRequestValidator());
    }

어떤 Validator가 커맨드 객체를 검증할 지는 initBinder() 메서드가 결정한다. @InitBinder 어노테이션을 적용한 메서드는 WebDataBinder 타입 파라미터를 갖는데 WebDataBinder의 setValidator() 메서드를 이용해서 컨트롤러 범위에 적용할 Validator를 설정할 수 있다.

@InitBinder가 붙은 메서드는 컨트롤러의 요청 처리 메서드를 실행하기 전에 매번 실행된다.

🙋‍♀️글로벌 범위 Validator도 설정했고 컨트롤러 범위 Validator도 설정했는데 우선순위가 어떻게 되나요?
💡@InitBinder 어노테이션을 붙인 메서드에 전달되는 WebDataBinder는 내부적으로 Validator 목록을 갖는데 이 목록에는 글로벌 범위 Validator가 기본적으로 포함된다. setValidator() 메서드를 실행하면 WebDataBinder가 갖고 있는 Validator를 목록에서 삭제하고 파라미터로 전달받은 Validator를 목록에 추가한다.
= 즉 !! setValidator() 메서드를 사용하면 글로벌 범위 Validator대신 컨트롤러 범위 Validator를 사용하게 된단 말씀 (글로벌 < 컨트롤러 범위)

글로벌 범위를 먼저 적용하고 싶다면 WebDataBinder의 addValidator() 메서드를 사용하면 된다. 이 메서드는 기존 Validator 목록에 새로운 Validator를 추가하므로 글로벌 범위 Validator가 존재하는 상태에서 해당 메서드를 실행하면 순서상 글로벌 범위 뒤에 새로 추가한 컨트롤러 범위가 추가 된다.

Bean Validation을 이용한 값 검증 처리

Bean Validation은 JavaBean 유효성 검증을 위한 메타데이터 모델과 API에 대한 정의이다.특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다.

@Valid 어노테이션은 Bean Validation 스펙에 정의되어 있다. 이 스펙은 @Valid 어노테이션뿐만 아니라 @NotNull, @Digits, @Size 등의 어노테이션을 정의하고 있다.

❕ Java에는 javax(자바 확장 패키지)에 포함되는 Bean Validation의 구현체가 있고 Hibernate에서 만든 Hibernate Validator라는 구현체도 있다. 실습에선 hibernate validator를 사용했는데 직접 해보니 deprecated가 선언됐다. 검색해보니 공식버전에 새 어노테이션이 추가되면서 하이버네이트 어노테이션이 사용안되도록 조정했다고 한다.

무튼 난 javax가 제공하는 Bean Validation으로 실습했다.
커맨드 객체에 검증이 필요한 어노테이션을 붙여준다.

package spring;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

public class RegisterRequest {

    @NotBlank
    @Email
    private String email;
    @Size(min=6)
    private String password;
    @NotEmpty
    private String confirmPassword;
    @NotEmpty
    private String name;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isPasswordEqualToConfirmPassword() {
        return password.equals(confirmPassword);
    }
}

그 다음으론 Bean Validation 어노테이션을 적용한 커맨드 객체를 검증할 수 있는 OptionalValidatorFactoryBean 클래스를 빈으로 등록해야한다. 하지만 ! @EnableWebMvc 어노테이션을 사용하면 해당 클래스를 글로벌 범위 Validator로 등록하므로 추가 설정은 필요없다.

남은 작업은 @Valid 어노테이션을 붙여 글로벌 범위 Validator로 검증하는 것이다. 이것 역시 전에 붙여뒀으므로 추가 설정은 없다.

    @PostMapping("/register/step3")
    public String handleStep3(@Valid RegisterRequest regReq, Errors errors) {

만약 글로벌 범위 Validator를 따로 설정했다면 해당 설정은 삭제해주어야한다.

	// 이부분 삭제!
	@Override
    public Validator getValidator() {
        return new RegisterRequestValidator();
    }

@EnableWebMvc를 사용하면 별도로 설정한 글로벌 범위 Validator가 없을 때에 OptionalValidatorFactoryBean을 글로벌 범위 Validator로 사용하는데 저렇게 직접 설정해주게 되면 OptionalValidatorFactoryBean을 사용하지 않게되기 때문이다.

주요 어노테이션은 공식문서 사이트를 적어놓을테니 틈틈이 찾아보기!
https://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-summary.html

0개의 댓글