JSR-303 어노테이션을 커스텀하여 검증해보자.message, groups, payload 메서드는 반드시 존재해야 한다.@Constraint 어노테이션에는 직접 생성할 ConstraintValidator 를 구현하는 Validator를 설정한다.@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AllowedValueValidator.class)
public @interface AllowedValue {
    
  String[] values() default { "S", "M", "L" };
  
  String message() default "{com.validator.constraints.AllowedValue.message}";
  
  Class<?>[] groups() default {};
  
  Class<? extends Payload>[] payload() default {};
}
initialize 메서드에서는 초기화할 데이터가 필요한 경우 설정한다. 앞서 만든 어노테이션의 기본값으로 초기화했다.isValid 메서드에서는 실제 검증할 내용을 작성한다. 검증할 필드에 요청된 값이 values에 정의된 값 중에 존재하는지 확인했다.public class AllowedValueValidator implements ConstraintValidator<AllowedValue, String> {
    
  private String[] values;
  
  @Override
  public void initialize(AllowedValue constraintAnnotation) {
    this.values = constraintAnnotation.values();
  }
  
  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return ArrayUtils.contains(values, value);
  }
}
public class Clothing {
  private String id;
  private String name;
  @AllowedValue
  private String topSize;
  @AllowedValue
  private String bottomSize;
  @AllowedValue(values = { "red", "blue" })
  private String color;
}
@Valid 와 BindingResult 를 표기한다.@PostMapping("/clothing/update")
public void update(@Valid Clothing clothing, BindingResult bindingResult) {
  if (bindingResult.hasErrors()) {
    FieldError fieldError = bindingResult.getFieldError();
    throw new RuntimeException(fieldError.getDefaultMessage());
  }
  
  clothingManager.update(clothing);
}