Validation의 동작
- DispatcherServlet → RequestMappingHandlerAdapter
- 각 파라미터를 처리할 HandlerMethodArgumentResolver가 선택된다. 이때 Reflection을 통해 결정된다.
- @RequestBody → RequestResponseBodyMethodProcessor
- @ModelAttribute(기본) → ModelAttributeMethodProcessor
- 이 Resolver 들은 파라미터를 객체로 변환한 뒤 validateIfApplicable() 을 호출하며, 여기서 파라미터에 @Valid가 붙어 있는지 Reflection 으로 확인한다.
- 확인 후 Validator 구현체(spring-boot-starter-validation 의존성 받을 시 자동 생성 및 주입)는
- 전달된 객체(또는 메서드 파라미터) → 그 안의 필드·게터·파라미터 …
- 거기에 붙어 있는 모든 어노테이션을 살펴본다.
- 그 어노테이션의 타입(클래스)이 @Constraint를 달고 있으면 즉시 “제약 조건”으로 간주하여 해당 ConstraintValidator 인스터스를 생성 후 실행한다.
- initialize(A annotation) 호출: 해당 어노테이션의 attribute 값을 내부 필드에 저장하는 단계
- 대상 값(value)을 매개변수로 isValid(value, context) 호출: 결과가 false이면 message 값을 담은 ConstraintViolation 객체를 생성
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy = EnumMatchValidator.class)
public @interface EnumMatch {
String message() default "Not Match Enum Value";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] includes() default {};
String[] excludes() default {};
}
- @Constraint(validatedBy = ~): 실질적인 유효성 검사를 진행할 클래스 지정
- 아래는 모든 제약(Constraint) 애노테이션이 반드시 제공해야 하는 기본 구성 요소로 생략하면 에러가 발생한다.
- message(): 유효성 검사에서 false가 발생할 경우 반환할 메세지
- payload(): 유효성 검사에서 사용할 부가 정보로 일반적으로 잘 쓰이지 않는다.
- groups(): @Validated 어노테이션에서 지정한 인자와 동일한 것만 유효성 검사를 해주는 역할이다. 코드가 복잡해져서 자주 사용되지 않는다.
class EnumMatchValidator implements ConstraintValidator<EnumMatch, Enum<?>> {
private String[] includes;
private String[] excludes;
@Override
public void initialize(EnumMatch constraintAnnotation) {
includes = constraintAnnotation.includes();
excludes = constraintAnnotation.excludes();
}
@Override
public boolean isValid(Enum<?> value, ConstraintValidatorContext context) {
if (includes.length == 0 && excludes.length == 0) {
return true;
}
if (includes.length != 0) {
for (String include : includes) {
if (include.equals(value.name())) {
return true;
}
}
return false;
}
for (String exclude : excludes) {
if (exclude.equals(value.name())) {
return false;
}
}
return true;
}
}
- includes와 excludes 모두 비어있으면 무조건 유효성 검사를 통과시킨다.
- includes가 존재할 때는 무조건 includes를 기반으로 유효성 검사를 진행한다. 즉, excludes가 존재해도 includes로만 유효성 검사를 진행한다.
- 이후 excludes로 유효성 검사를 진행한다.
- validation 어노테이션과 ConstraintValidator 구현 클래스는 밀접한 관련이 있으므로 package-private으로 한 파일 내에 정의해줬다.
- 만약 다른 validation 어노테이션에서도 사용한다면 따로 정의하자.