[SpringBoot] Custom Validator 사용하기

BAEJUN SON·2024년 8월 8일

싸피 공통 프로젝트

목록 보기
21/23

프로젝트 진행중 spring validation 이외의 유효성 검증 작업이 필요하여 Custom Validator에 대해 찾아본 내용을 정리하려고 한다.


Custom Validator 적용

custom validator를 적용하려면 두 가지를 구현해야 한다.

  1. 커스텀 어노테이션 객체
  2. 커스텀 어노테이션 객체를 사용하는 Validator

먼저 커스텀 어노테이션 객체를 먼저 만들어보자.

1.Annotation 생성

package com.pitpat.pitterpatter.global.validation;

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

@Documented
@Constraint(validatedBy = LocalDateValidator.class)
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidLocalDate {
    String message() default "생일은 'YYYY-MM-DD' 형식이어야 합니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

  • @Target: 어노테이션을 적용할 위치를 지정해주는 것이다.
    PACKAGE, Type, METHOD 등 적용할 위치에 따라 선언해주면 된다. 여기서는 멤버 변수에 할당할 어노테이션이기 때문에 FIELD로 선언했다.
  • @Retention: 어노테이션의 범위를 설정해주는 것이다.
    SOURCE, CLASS, RUNTIME이 있고 우리는 실행하는 동안에도 유지되어야 하므로 RUNTIME으로 설정해주었다.
  • @Constraint: 검증 클래스를 지정해주는 것이다.
    이제 validator 클래스를 구현하여 지정해주면 된다.
    검증 클래스로는 ConstraintValidator 인터페이스를 구현한 클래스가 지정되어야 한다.
  • @Documented: JavaDoc 생성시 Annotation에 대한 정보도 함께 생성
  • message: 검증에 실패했을 시 표시되는 오류 메시지이다.
  • groups() : 유효성 검증이 진행될 그룹을 지정한다.
  • payload() : 유효성 검증 시에 전달할 메타 정보를 지정한다.

※groups와 payload는 보통 우리는 잘 지정하지 않는다.

2. Validator 구현

public class LocalDateValidator implements ConstraintValidator<ValidLocalDate, String> {

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public void initialize(ValidLocalDate constraintAnnotation) {
    }

    @Override
    public boolean isValid(String dateStr, ConstraintValidatorContext context) {
        if (dateStr == null) {
            return true; // null 값은 다른 검증에서 처리할 수 있음
        }

        try {
            LocalDate.parse(dateStr, DATE_FORMATTER);
            return true;
        } catch (DateTimeParseException e) {
            return false;
        }
    }
}

ConstraintValidator<ValidLocalDate, String>

  • 첫번째 값으로 Validator를 적용할 어노테이션 객체가, 두번째 값으로 검증을 진행할 필드의 데이터 유형을 넣어주면 된다.

@Override
public void initialize(ValidLocalDate constraintAnnotation)

  • initialize엔 초기화 해주어야 할 값을 넣으면 된다.

@Override
public boolean isValid(String dateStr, ConstraintValidatorContext context)

  • isValid가 유효성을 검증하는 메소드이다.
  • 첫번째 인자는 검증할 데이터이다.
  • 여기에 우리가 검증할 로직을 구현하면 된다.

3. DTO에서 적용

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChildRequestDTO {

    private String profileImage;

    @NotNull(message = "닉네임은 필수 값입니다.")
    @Size(max = 10, message = "닉네임은 최대 10자까지 입력 가능합니다.")
    private String nickname;

    @NotNull(message = "성별은 필수 값입니다.")
    private Gender gender;

    @NotNull(message = "생일은 필수 값입니다.")
    @ValidLocalDate
    private String birth;

    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
}

@ValidLocalDate
private String birth;

  • 이제 검증이 필요한 필드에 구현한 custom validation annotation을 적용해주면 된다.

이렇게, 구현한 검증 로직을 통과하지 못한다면, 구현 validator에서 지정한 메세지가 출력되는 것을 확인할 수 있다 !!


처음 써보는 custom validator이지만 간단한 로직이라 쉽게 따라할 수 있었다.

단순히 마구잡이로 생성하지 말고, 이 또한 효율을 고민하고 custom validation을 관리한다면 조금 더 좋은 코드로 관리될 것 같다.

0개의 댓글