본문으로 건너뛰기
Advertisement

6.3 커스텀 Validator 구현

기본적으로 제공되는 애너테이션(@NotBlank, @Min 등)만으로는 서비스의 복잡한 비즈니스 규칙을 모두 검증하기 어려울 수 있습니다. 예를 들어, "시작일이 종료일보다 이전이어야 한다"거나 "특정 단어가 포함되면 안 된다"와 같은 조건들은 커스텀 애너테이션과 Validator 를 직접 만들어 해결합니다.

1. 커스텀 애너테이션 생성

먼저 @interface를 사용하여 나만의 검증 애너테이션을 생성합니다. 여기서는 비밀번호에 특정 특수문자가 포함되었는지 검사하는 @StrongPassword 애너테이션을 예로 들겠습니다.

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = StrongPasswordValidator.class) // 검증 로직을 수행할 클래스 지정
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface StrongPassword {

String message() default "비밀번호는 영문자, 숫자, 특수문자를 각각 1개 이상 포함해야 합니다.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

Bean Validation을 위한 애너테이션은 필수적으로 message, groups, payload 세 가지 속성을 가져야 합니다.

2. Validator 로직 구현

위에서 validatedBy로 지정한 StrongPasswordValidator 형식을 구현합니다. ConstraintValidator 인터페이스를 상속받아야 합니다.

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class StrongPasswordValidator implements ConstraintValidator<StrongPassword, String> {

@Override
public void initialize(StrongPassword constraintAnnotation) {
// 초기화 로직이 필요하다면 작성
}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null || value.isBlank()) {
return false;
}

// 정규식을 통한 강력한 패스워드 검사 로직
boolean hasUppercase = value.matches(".*[A-Z].*");
boolean hasLowercase = value.matches(".*[a-z].*");
boolean hasNumber = value.matches(".*\\d.*");
boolean hasSpecialChar = value.matches(".*[!@#$%^&*()].*");

return hasUppercase && hasLowercase && hasNumber && hasSpecialChar;
}
}

3. 적용하기

이제 작성한 애너테이션을 DTO 필드에 자연스럽게 붙여서 사용할 수 있습니다.

public class UserRegisterDto {
@NotBlank
private String username;

@StrongPassword
private String password;
}

이렇게 커스텀 Validator를 활용하면, 프레임워크가 제공하는 핵심 검증 기능을 유지하면서도 도메인에 특화된 유연하고 재사용 가능한 로직을 깔끔하게 추가할 수 있습니다.

Advertisement