스프링 프레임워크가 기본적으로 제공하는 Validator이외의 Validator를 구현해야할 순간 이있습니다.
스프링은 컨트롤러에서 클라이언트에서 넘겨받은 값에 대한 검증을 JSR-303 기반으로 쉽고 강력하게 할 수 있습니다. 또 한 커스텀 한 어노테이션을 쉽게 구현할 수 있고 확장도 용이합니다.
애노테이션을 직접 만들기 위해서는 2가지 클래스가 필요합니다.
@interface 클래스, ConstraintValidator<@interface 클래스, String>의 구현 클래스
아래에서 작성하는 어노테이션은 해당 Password가 유효한지 검사를 하는 애노테이션입니다.
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = PasswordValidator.class)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface Password {
String message() default "Password is not allow";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Documented
자바에는 Javadoc이라고 코드를 문서화 하는 기능이 있습니다. 이 애노테이션을 가지고 있는 애노테이션을 사용하시면 해당 애노테이션 정보를 해당 코드의 문서에 같이 보여줍니다
@Constraint
애노테이션을 Bean Validation Constraint로 만들어주는 애노테이션입니다.
이 애노테이션을 가진 클래스는 반드시 아래와 같은 속성값을 가져야합니다.
속성 | 설명 |
---|---|
String message() default [...]; | 해당 attribute는 default message key 값을 가지고 있어야함 |
Class<?>[] groups() default {}; | 사용자들이 targeted group을 customize하기 위해 사용 |
Class<? extends Payload>[] payload() default {}; | 확장성을 위해 사용 |
@Target
해당 애노테이션이 적용될 수 있는 contexts값을 의미. 즉, 어디에 사용될 수 있는지 정의
속성 | 설명 |
---|---|
ElementType.PACKAGE | package declaration |
ElementType.TYPE | Class, interface(including annotation type), or enum declaration |
ElementType.CONSTRUCTOR | Constructor declaration |
ElementType.FIELD | Field declaration (includes enum constants) |
ElementType.METHOD | Method declaration |
ElementType.ANNOTATION_TYPE | annotation type declaration |
ElementType.LOCAL_VARIABLE | Local variable declaration |
ElementType.PARAMETER | Type parameter declaration |
ElementType.TYPE_USE | Use of a type |
@Retention
해당 애노테이션을 언제까지 유지할 것인가에 대한 설정
속성 | 설명 |
---|---|
SOURCE | Annotaions are to be discarded by the compoiler |
CLASS | Annotaions are to be recorded in the class file by the compiler but need not be retained by th VM at run time. This is the default behaivor |
RUNTIME | Annotaions are to be recorded in the class file by the compiler. retained by th VM at run time. so they may be read reflectively |
import org.domain.user.Password;
import org.springframework.stereotype.Component;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.text.MessageFormat;
@Component
public class PasswordValidator implements ConstraintValidator<Password, String> {
private static final int MIN_SIZE = 8;
private static final int MAX_SIZE = 50;
private static final String regexPassword = "^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&])[A-Za-z[0-9]$@$!%*#?&]{" + MIN_SIZE
+ "," + MAX_SIZE + "}$";
@Override
public void initialize(Password constraintAnnotation) {
}
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
boolean isValidPassword = password.matches(regexPassword);
if (!isValidPassword) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
MessageFormat.format("{0}자 이상의 {1}자 이하의 숫자, 영문자, 특수문자를 포함한 비밀번호를 입력해주세요", MIN_SIZE, MAX_SIZE))
.addConstraintViolation();
}
return isValidPassword;
}
public boolean isValid(String password) {
return password.matches(regexPassword);
}
}
아규먼트로 받은 password값이 지정한 규칙에 맞는지 확인하고, 규칙에 맞지않으면 예외 메세지를 추가후, false를 리턴 합니다.
이제 @Password로 지정된 변수는 해당 애노테이션 로직이 적용되어 regexPassword(지정한 password규칙)에 맞지않으면 false를 리턴할 것입니다.