본문 바로가기

Spring Boot

[Spring Boot] DTO 유효성 검사를 위한 커스텀 어노테이션 만들기

728x90

Spring에서 DTO 유효성 검사

스프링에서는 DTO를 받을 때 기본적으로 제공하는 유효성 검사를 사용합니다. 그러나 기본 유효성 검사 외에 모든 유효성을 만족시키기에는 부족할 수 있습니다. 이를 해결하기 위해 필요에 따라 커스텀 어노테이션을 만들어 사용할 수 있습니다.

커스텀 어노테이션의 필요성

  • DTO 내에서의 유효성 검증: 기본적으로 제공되는 유효성 검사로는 충분하지 않을 수 있습니다. 예를 들어, 여러 필드를 동시에 검사해야 하는 경우 @AssertTrue와 같은 방법을 사용할 수 있지만, 이는 코드 가독성을 저하시킬 수 있습니다.
  • 추상화: 커스텀 어노테이션을 사용하면 유효성 검사 로직을 추상화하여 재사용성과 가독성을 높일 수 있습니다.

커스텀 어노테이션을 만들기 전 알아야 할 어노테이션

1. @Target

  • @Target은 어노테이션이 적용될 수 있는 대상을 지정합니다. 설정할 수 있는 값은 ElementType enum으로 정의되어 있습니다.
    • ElementType.FIELD: 필드에 적용
    • ElementType.METHOD: 메서드에 적용
    • ElementType.TYPE: 클래스, 인터페이스, 열거형에 적용
    • ElementType.PARAMETER: 매개변수에 적용
    • ElementType.CONSTRUCTOR: 생성자에 적용
    • ElementType.LOCAL_VARIABLE: 지역 변수에 적용
    • ElementType.ANNOTATION_TYPE: 다른 어노테이션에 적용

예시

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)  // 필드에 적용
public @interface MyFieldAnnotation {
}

@Target(ElementType.METHOD)  // 메서드에 적용
public @interface MyMethodAnnotation {
}

@Target(ElementType.TYPE)  // 클래스에 적용
public @interface MyClassAnnotation {
}

 

2. @Retention

  • @Retention은 어노테이션이 언제까지 유지될지를 지정합니다. 설정할 수 있는 값은 RetentionPolicy enum으로 정의되어 있습니다.
    • RetentionPolicy.SOURCE: 컴파일 타임에만 유지
    • RetentionPolicy.CLASS: 컴파일된 클래스 파일에 포함되지만, JVM에 로드될 때는 존재하지 않음
    • RetentionPolicy.RUNTIME: 실행 중에도 유지 (JVM에 로드되고 리플렉션을 통해 접근 가능)

예시

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)  // 컴파일 타임에만 유지
public @interface MySourceAnnotation {
}

@Retention(RetentionPolicy.CLASS)  // 클래스 파일에 포함되지만 런타임에 존재하지 않음
public @interface MyClassAnnotation {
}

@Retention(RetentionPolicy.RUNTIME)  // 실행 중에도 유지
public @interface MyRuntimeAnnotation {
}

 

3. @Constraint 설명

  • @Constraint 어노테이션은 커스텀 유효성 검증 어노테이션을 정의할 때 사용됩니다.
  • 이 어노테이션을 사용하여 해당 커스텀 어노테이션이 검증할 클래스를 지정합니다. 검증 클래스는 ConstraintValidator 인터페이스를 구현해야 합니다.
  • 검증 클래스에서는 커스텀 어노테이션의 규칙을 정의하고, 어떤 필드가 유효한지를 판단하는 로직을 구현합니다.
  • 유효성 검증을 위한 커스텀 어노테이션을 만들 때 필수적으로 사용하는 어노테이션입니다.
@Constraint(validatedBy = MyValidator.class)  // MyValidator 유효성 검증을 담당
public @interface CustomAnnotation {
}

결합된 예시

어노테이션을 정의할 때 @Target과 @Retention을 함께 사용할 수 있습니다.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  // 메서드에 적용
@Retention(RetentionPolicy.RUNTIME)  // 실행 중에도 유지
public @interface MyAnnotatedMethod {
    String value();
}

 

 

인자 설정하기

groups

  • 의미: 검증 그룹을 정의합니다. 여러 검증을 그룹으로 묶어 특정 상황에서만 적용할 수 있습니다. 예를 들어, 사용자 등록과 수정 시에 각각 다른 검증 그룹을 적용하고 싶을 때 유용합니다.

payload

  • 의미: 이 어노테이션에 대한 추가 메타데이터를 담을 수 있는 필드입니다. 일반적으로 사용자가 이 값을 통해 검증 로직을 확장할 수 있도록 합니다. 특정 검증 요구 사항에 따라 추가 정보를 제공할 수 있습니다.

추가 설명

  • groups와 payload는 일반적으로 커스텀 검증 로직에서 사용되는 고급 기능입니다. 특히 groups를 활용하면, 여러 상황에 따라 다른 검증 로직을 적용할 수 있어 유연한 검증이 가능합니다.

 

간단한 커스텀 어노테이션 만들기: CustomEmailAnnotation

아래는 간단한 커스텀 이메일 어노테이션 @CustomEmail을 정의하는 예시입니다.

1. 커스텀 어노테이션 정의

CustomEmail.java

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = EmailValidator.class)  // 유효성 검증을 담당할 클래스 지정
@Target({ElementType.FIELD, ElementType.METHOD})  // 필드와 메서드에 적용
@Retention(RetentionPolicy.RUNTIME)  // 실행 중에도 유지
// @NotBlank // 이것도 custom 어노테이션에서 사용이 가능합니다.
// @Size(min = 1, max = 100) // 이것도 가능합니다.
public @interface CustomEmail {
    String message() default "유효한 이메일 주소가 아닙니다.";  // 기본 오류 메시지
    String regexp() default "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z]{2,}$";  // 기본 정규 표현식
    Class<?>[] groups() default {};  // 그룹 정보
    Class<? extends Payload>[] payload() default {};  // 추가 정보
}

 

2. 유효성 검증 로직

EmailValidator.java

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;

public class EmailValidator implements ConstraintValidator<CustomEmail, String> {
    
    private String regexp;

    @Override
    public void initialize(CustomEmail constraintAnnotation) {
        this.regexp = constraintAnnotation.regexp();
    }

    @Override
    public boolean isValid(String email, ConstraintValidatorContext context) {
        boolean result = Pattern.matches(regexp, email);  // 정규 표현식 수정
        return result;
    }
}

 

3. DTO에서 사용하기

이제 @CustomEmail 어노테이션을 DTO에 적용할 수 있습니다.

UserRegistrationRequest.java

import jakarta.validation.constraints.NotBlank;

public class UserRegistrationRequest {
    
    @NotBlank(message = "이메일은 필수입니다.")
    @CustomEmail(message = "유효한 이메일 주소를 입력하세요.")
    private String email;
}

 

이렇게 간단한 커스텀 어노테이션을 정의하고 사용할 수 있습니다. 이를 통해 코드의 가독성을 높이고 재사용성을 증가시킬 수 있습니다.

728x90