[Spring Boot] Custom Annotation으로 유효성 검사하기

왔다 정보리·2025년 6월 22일
0
post-thumbnail

API 개발을 하면서 유효성 검사를 진행하다 보면, 기본 어노테이션으로는 검증이 불가능한 경우가 있다. 그럴 때 직접 어노테이션을 개발해서 유효성 검사를 진행할 수 있다. 이렇게 유효성 검사를 위한 커스텀 어노테이션을 만들어두면 다른 곳에서도 편리하게 사용할 수 있다.

Custom Annotation


Annotation

빈 관리, 의존성 주입, 트랜잭션 관리 등 다양한 기능을 처리할 수 있다. 특히 Spring Boot에서는 어노테이션을 통해 설정을 최소화하고 자동화를 구현할 수 있다. 다만, 어노테이션을 사용하면 로직이 숨겨지기 때문에 무분별하게 사용하는 것은 좋지 않다.
이번에는 어노테이션을 통해 DTO 유효성 검사를 진행하고자 한다.

Annotation 구조

@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Annotation {
   ...
}

어노테이션을 정의할 때는 다음과 같은 메타 어노테이션을 사용한다.

  • @Target : 어노테이션의 적용 대상을 지정한다.
  • @Retention : 어노테이션의 지속 시간을 지정한다.
  • @Inherited : 어노테이션이 상속되도록 설정한다.

Annotation 특징

  1. @interface로 어노테이션 클래스를 정의한다. @interface로 정의하면 컴파일러가 자동으로 java.lang.Annotation을 상속하도록 처리해준다.
  2. default 키워드로 기본값을 설정할 수 있다. 다만, null로는 설정할 수 없다.
  3. 반환 타입은 기본형, String, Class, enum, Annotation만 가능하다. (배열 포함)
  4. Class.getAnnotation(), Method.getAnnotation() 등을 통해 어노테이션 정보를 얻을 수 있다.

ElementType (@Target)

ElementType적용 대상
TYPEClass, Interface
FIELD객체 필드 (enum, 상수 포함)
METHOD메소드
PARAMETER매개변수
CONSTRUCTOR생성자
LOCAL_VARIABLE지역 변수
ANNOTATION_TYPE어노테이션
PACKAGE패키지
TYPE_PARAMETER매개변수의 타입
TYPE_USE매개변수 사용 시

@Target에서 어노테이션 적용 대상을 지정하는 옵션이다.
유효성 검사 시에는 주로 FIELD, METHOD, PARAMETER, TYPE_USE를 많이 사용한다.

RetentionPolicy (@Retention)

RetentionPolicy유지 시점
SOURCE컴파일러 사용 후 삭제
CLASS클래스 파일에는 포함, 런타임에는 접근 불가
RUNTIME런타임까지 유지 (프로그램에서 접근 가능)

@Retention에서 어노테이션 유지 시점을 지정하는 옵션이다.
커스텀 어노테이션을 만들 때는 어플리케이션 동작에 영향을 주는 RUNTIME을 가장 많이 사용한다.

Custom Annotation으로 유효성 검사하기


1. Custom Annotation을 생성한다

@Documented
@Constraint(validatedBy = LevelFormatValidator.class)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface LevelFormat {
    String message() default "점수는 0.5 단위로 입력해 주세요.";

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

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

    boolean nullable() default false;
}
  • @Documented
    • Javadoc 문서에 포함되도록 설정한다.
    • 기본적으로 Java는 Javadoc에 어노테이션을 포함하지 않는다.
  • @Contraint
    • Bean Validation 어노테이션임을 표시한다.
    • validatedBy로 실제 검증 로직을 구현할 클래스를 지정한다.
  • 필수 속성
    • message() : 검증 실패 시 반환할 메시지.
    • groups() : 검증 그룹을 지정한다. (선택적 검증용)
    • payload() : 검증 메타데이터를 전달한다. (심각도, 카테고리 등)
  • 커스텀 속성
    • nullable() : 해당 필드의 null 가능 여부를 지정한다.

2. 유효성 검증 로직을 작성한다

public class LevelFormatValidator implements ConstraintValidator<LevelFormat, Float> {
    private Boolean nullable;

    public void initialize(LevelFormat levelFormat) {
        this.nullable = levelFormat.nullable();
    }

    @Override
    public boolean isValid(Float value, ConstraintValidatorContext context) {
        // nullable이 true이고 value가 null일 경우 바로 검증을 성공 처리한다
        if (value == null) {
            return nullable;  // value가 null이고 nullable이 false라면 검증을 실패 처리한다
        }

        return (value * 2) % 1 == 0; // 0.5 단위인지 확인 (예: 2.5 * 2 = 5, 3.4 * 2 = 6.8)
    }
}

ConstraintValidator 인터페이스를 상속받아서 실제 검증 로직을 구현한다. 검증할 어노테이션 타입과 검증 대상 데이터의 타입을 정의하면, Bean Validation이 이 인터페이스를 통해 검증 로직을 호출한다.

  • initialize() : 어노테이션 속성값을 받아서 Validator를 초기화한다
  • isValid() : 실제 검증 로직을 구현한다

3. 필드에 어노테이션을 적용한다

@LevelFormat(nullable = false)
@Schema(description = "만족도", example = "3.5")
private Float level;

만든 어노테이션을 실제 필드에 적용하면 된다. @Valid 혹은 @Validated를 함께 사용해야 자동으로 필드 유효성 검사를 진행한다.

이렇게 간단하게 커스텀 어노테이션을 만들어서 DTO 유효성 검사를 진행할 수 있다. 다른 유효성 검사를 진행하고 싶다면 또다른 어노테이션을 만들어서 진행하면 된다. 만든 어노테이션은 다른 곳에서도 자유롭게 사용할 수 있다.

➕ 커스텀 메세지 처리

public class LevelFormatValidator implements ConstraintValidator<LevelFormat, Float> {
    @Override
    public boolean isValid(Float value, ConstraintValidatorContext context) {
        if (value == null) return nullable;

        if ((value * 2) % 1 != 0) {
            // 기본 메시지 비활성화
            context.disableDefaultConstraintViolation();

            // 상황에 맞는 커스텀 메시지 생성
            String customMessage = String.format("입력값 %.1f는 0.5 단위가 아닙니다. 예: 1.0, 1.5, 2.0", value);

            context.buildConstraintViolationWithTemplate(customMessage)
                    .addConstraintViolation();
            
            return false;
        }
        
        return true;
    }
}

입력값에 따라 동적으로 메세지를 생성하고 싶은 경우에는 ConstraintValidatorContext를 사용할 수 있다.

  • disableDefaultConstraintViolation() : 어노테이션의 기본 메세지를 비활성화한다
  • buildConstraintViolationWithTemplate() : 동적으로 생성한 메시지를 설정한다
  • addConstraintViolation() : 새로운 제약 조건을 추가한다

참고자료


[Spring Boot] Annotation 개념 이해하기, 주요 어노테이션
[Java] @Retention, @Target에 대하여
Spring Annotation의 원리와 Custom Annotation 만들어보기
커스텀 어노테이션(Custom Annotation) 만들기

profile
왔다 정보리

0개의 댓글