글로벌 범위 Validator와 컨트롤러 범위 Validator
스프링 MVC는 모든 컨트롤러에 적용할 수 있는 글로벌 Validator와 단일 컨트롤러에 적용할 수 있는 Validator를 설정하는 방법을 제공한다. 이를 사용하면 @Valid 어노테이션을 사용해서 커맨드 객체에 검증 기능을 적용할 수 있다!
글로벌 범위 Validator는 모든 컨트롤러에 적용할 수 있는 Validator이다. 글로벌 범위 Validator를 적용하려면 다음 두가지를 설정하면 된다.
일단 설정파일에 모듈을 추가해주고
// @Valid 어노테이션을 사용하기 위해 validation-api 모듈을 추가해야함(Bean Validation API)
implementation 'org.springframework.boot:spring-boot-starter-validation'
@Override
public Validator getValidator() {
return new RegisterRequestValidator();
}
@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 타입의 객체만 검증할 수 있기 때문이다.
@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