본문 바로가기

spring

커스텀 Validation annotation 만들기

들어가면서

자주 사용하는 특별한 유효성 검사 로직을 분리해서 관리하고 싶을 때! 커스텀 유효성 어노테이션을 만들면 유용하다.
가독성도 향상되어서 코드를 읽기가 좋아지는 효과도 있어서 커스텀 유효성 검사를 구현하는 방법을 알게되어서 너무 좋았다!

커스텀 유효성 어노테이션을 만들기 위한 절차는 2가지가 있다.

  1. 커스텀 어노테이션 만들기
  2. 커스텀 로직 판별기 만들기

필요한 클래스 만들기

비밀번호를 검사하는 유효성을 구현하고 싶은 경우를 예시로 삼아서 설명하겠다.
우선 구현하고자 하는 클래스를 만들자!

public @interface Password {}
public class PasswordValidator{}

1. 커스텀 어노페이션 만들기

유효성 검사할 대상을 상대로 사용할 어노테이션을 만들자.

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PasswordValidator.class)
public @interface Password {

    String message() default "비밀번호는 최소 8자 이상, 15자 이하이며 알파벳 소문자(a-z), 숫자(0~9)로 구성되어야 합니다.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

위의 예제 로직을 설명하면...

  • 사용할 대상은 메소드와 필드로 선정하고 런타임에서도 필요하기 때문에 런타임으로 정책을 설정했다.
  • 구현한 커스텀 어노테이션의 제약을 구현하는 클래스로는 PasswordValidator.class 로 지정하여 커스텀할 로직을 넣어주었다.
  • message()를 통해서 실패시 표시할 메시지를 커스텀할 수 있다.
  • groups() 는 스프링에서 제공하는 @Validated를 사용할 때, 유용한 속성이다.
  • payload()는 밸리데이션 클라이언트가 사용하는 메타데이터를 전달하기 위해서 사용된다.

groups?

동일한 객체를 사용하는 로직 A, B가 있을 때, A에서는 객체의 일부만 검증하고 싶고 B에서는 모두 검증하고 싶다면 어떻게 구현을 해야할까?
새로운 클래스를 생성해서 A, B로 분리하는 방법이 있겠지만 이 경우에는 동일한 데이터를 나타내는 객체가 2개가 생기는 것이기 때문에 중복이다.

이러한 경우에 group를 사용해서 유효성을 실행하는 경우를 나타내는 것이 좋다.

public record Dto(
    @NotNull(groups = {A.class, B.class})  String test1,
    @NotNull(groups = {B.class}) String test2
 ){}
public void sampleMethodA(@Validated(A.class) Dto dto)();
public void sampleMethodB(@Validated(B.class) Dto dto)();

2. 커스텀 로직 판별기 만들기

비밀번호의 제약을 검사하는 판별기 클래스를 구현하려면 ConstraintValidator를 상속받아서 구현한다.
제네릭 타입으로 유효성 어노테이션과 사용할 대상의 타입을 주어서 상속받는다.

public class PasswordValidator implements ConstraintValidator<Password, String> {

    private static final Pattern pattern = Pattern.compile("^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$");

    @Override
    public boolean isValid(String password, ConstraintValidatorContext context) {
        if (password == null) return false;
        if (password.length() < 8 || password.length() > 15) return false;
        return pattern.matcher(password).matches();
    }
}

예제에서는 정규식으로 비밀번호에 사용되는 문자를 검사하고 길이는 따로 검사하는 식으로 로직을 구현했다.

사용하기

여기까지 준비가 되었다면 이제는 @Notnull과 같은 유효성 어노테이션과 같이 사용을 하면 커스텀 유효성 검사 구현을 적용할 수 있는 것이다!

Reference

 

Validation 어디까지 해봤니? : NHN Cloud Meetup

TOAST Cloud의 메시징 플랫폼 상품인 Notification은 메시지, 이메일 주소 형식, 수신/발신자의 번호 등 클라이언트의 입력값에 대해 많은 검증을 진행합니다.

meetup.nhncloud.com