[Spring] 커스텀 어노테이션을 만들어서 유효성 검증 쉽게 하기

Java에서 커스텀 유효성 검증 어노테이션 만들기

일반적으로 @Email, @Pattern, @NotBlank 등의 표준 유효성 검증 어노테이션을 사용하지만, 프로젝트 내부에서 DTO를 만들어줄 때 전부 동일한 유효성 검사를 반복해서 하다보니 한 군데서 모아서 관리할 수 없을까 하다가 찾아보게 되었습니다.

 

1. 커스텀 유효성 검증 어노테이션 만들기

먼저, @ValidEmail@ValidPassword라는 커스텀 어노테이션을 만들어 보겠습니다. 이 어노테이션을 사용하면 이메일 형식비밀번호 규칙을 한 번에 검증할 수 있습니다.

UserDTO 클래스:

public class UserDTO {

    @Documented
    @Constraint(validatedBy = {UserValidator.EmailValidator.class})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Size(max = 255)
    @Schema(description = "회원 이메일", example = "test1234@naver.com")
    public @interface ValidEmail {
        String message() default "유효하지 않은 이메일 형식입니다.";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    }

    @Documented
    @Constraint(validatedBy = {UserValidator.PasswordValidator.class})
    @Target({ElementType.FIELD, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Schema(description = "회원 비밀번호", example = "Test1234test!")
    public @interface ValidPassword {
        String message() default "유효하지 않은 비밀번호 형식입니다.";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    }

    @Schema(description = "회원가입 요청")
    public record RegisterRequest(
            @NotBlank(message = "이름은 비어 있을 수 없습니다.")
            @Size(min = 2, max = 6)
            @Schema(description = "회원 이름", example = "홍길동")
            String name,

            @ValidEmail
            String email,

            @ValidPassword
            String rawPassword
    ) {}

    public record RegisterResponse(
            UUID id,
            @ValidEmail
            String email,
            String name,
            String studyGoal
    ) {}

    @Schema(description = "로그인 요청")
    public record LoginRequest(
            @ValidEmail
            String email,
            @ValidPassword
            String rawPassword
    ) {}
}

 

위 코드에서는 @ValidEmail@ValidPassword라는 커스텀 어노테이션을 정의하고, 회원가입 및 로그인 시 해당 어노테이션을 사용하여 필드 검증을 쉽게 할 수 있습니다.


2. 검증 로직 구현:

UserValidator 클래스 안에 실제 검증 로직을 정의합니다. 이메일과 비밀번호는 각각의 정규식을 기반으로 검증할 수 있습니다.

UserValidator 클래스:

public class UserValidator {

    private static final String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
    private static final String PASSWORD_REGEX = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{10,20}$";

    // 이메일 검증
    public static class EmailValidator implements ConstraintValidator<UserDTO.ValidEmail, String> {
        @Override
        public boolean isValid(String email, ConstraintValidatorContext context) {
            return email != null && email.matches(EMAIL_REGEX);
        }
    }

    // 비밀번호 검증
    public static class PasswordValidator implements ConstraintValidator<UserDTO.ValidPassword, String> {
        @Override
        public boolean isValid(String password, ConstraintValidatorContext context) {
            return password != null && password.matches(PASSWORD_REGEX);
        }
    }
}

UserValidator 클래스는 내부 클래스EmailValidatorPasswordValidator를 정의하고 있습니다. 각 클래스는 정규식을 이용해 이메일과 비밀번호의 유효성을 검증합니다.


3. 커스텀 유효성 검증의 장점

  1. 재사용성: 한 번 정의한 커스텀 어노테이션은 여러 DTO나 엔티티에서 재사용할 수 있습니다.
  2. 가독성: 검증 로직이 각 필드에 직접 포함되지 않고 커스텀 어노테이션으로 분리되므로, 코드의 가독성이 크게 향상됩니다.
  3. 유지보수성: 이메일 형식이나 비밀번호 규칙이 변경될 경우, 커스텀 어노테이션만 수정하면 모든 검증 로직이 자동으로 적용됩니다.
  4. 비즈니스 로직 분리: 검증 로직이 비즈니스 로직과 분리되어 코드를 더 깔끔하고 관리하기 쉽게 만듭니다.