스프링

Spring Boot, Custom Annotation 만들기

o늘do 2022. 1. 25. 20:54

스프링에서 개발을 하다 보면 요청, 응답 dto 값에 validation을 적용하는 경우가 많다.
예를 들어 Request 요청이 다음과 같고 내부 필드에는 title 이 존재할 때 title의 null 여부와 size에 대해서 유효성 검사를 추가하고 싶은 경우가 존재한다. title 은 null 이 아니었으면 좋겠고 길이는 1 ~ 255 자 사이였으면 좋겠다.
다행히도 스프링은 기본적으로 다양한 유효성을 검사해주는 어노테이션을 제공한다.

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class Request {
    // title 은 null 이 아니여야하고 길이는 1 ~ 255 자 사이여야한다. 그렇지 않으면 validation error 를 발생시키겠다. 
    @NotNull()
    @Size(min = 1, max = 255)
    private String title;

}

 

만약에 위 Request 필드에 추가로 emails라는 데이터 필드를 추가해야 하는 경우가 생겼고 데이터의 조건은 아래와 같다고 생각해보자

1. emails 는 email 형식의 데이터가 들어오는데 email 이 , 로 구분되어 들어오는 형태여야한다. 
2. , 로 구분된 이메일은 유효한 이메일 형식이여야한다.
ex) "yhy7132@naver.com,dgd@google.com,junjun@naver.com"

위와 같은 필드에 대해서 올바르게 email 이 요청으로 들어오는지를 validation 해주는 어노테이션은 없다(적어도 내가 찾기에는)

 

위와 같은 경우에는 일단 요청을 받고 비즈니스 로직에서 처리를 해줄 수 도 있으나 anotation을 이용하면 비즈니스 로직과 분리도 가능하고 조건에 변경이 생겼을 때 해당 어노테이션 부분만 수정하면 되기 때문에 매우 깔끔하게 코드를 작성할 수 있다. 

 

그럼 위에서 말한 emails 필드에 조건을 검사하는 커스텀한 어노테이션을 만들어 보자. 첫 번째는 어노테이션을 정의하는 인터페이스 클래스이다. 

@Target(ElementType.FIELD)
@Constraint(validatedBy = IsAllEmailValidator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface IsAllEmail {
    String message() default "recvAddrs are required and email format";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

 

@Target(ElementType.FIELD): 적용하려는 타입은 Request 객체의 emails 필드(변수)이기때문에 target을 field로 설정한다.

 

@Constraint(validatedBy = IsAllEmailValidator.class): 실제로 해당 어노테이션으로 필드를 어떻게 검증할 건지에 대한 대한 클래스를 선언한다. IsAllEmailValidator이고 밑에서 설명한다.

 

@Retention(RetentionPolicy.RUNTIME): 어플리케이션 실행 시점 동안만 해당 어노테이션이 유효하다는 설정으로 RUNTIME을 지정해준다.

 

String message() default "recvAddrs are required and email format": 기본적으로 해당 어노테이션으로 필드를 검증하고 통과하지 못했을 때 표시해주는 default 메시지이다.

 

그다음은 @Constraint(validatedBy = IsAllEmailValidator.class)에서 검증 로직이 들어갈 클래스인 IsAllEmailValidator를 생성한다. 

public class IsAllEmailValidator implements ConstraintValidator<IsAllEmail,String> {

    public static final String EMAIL_REGEX = "[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-.]?[0-9a-zA-Z])*\\.[a-zA-Z]{2,3}$";
    public static final Integer MAX_EMAIL_LENGTH = 255;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        if(value == null || value == "")
            return false;

        List<String> emails = Arrays.asList(value.split(";"));
        if(emails.isEmpty()) return false;

        return emails.stream()
                .allMatch((email) -> Pattern.matches(EMAIL_REGEX, email) && email.length() <= MAX_EMAIL_LENGTH);

    }
}

IsAllEmailValidator 클래스는 ConstraintValidator 클래스를 상속받게 되고 제네릭의 첫 번째 인자로는 어노테이션, 두 번째 인자로는 string을 지정해준다. string 을 지정하는 이유는 적용하려는 emails 필드가 string 타입이기 때문이다.


isValid 함수를 Override 해서 검증 로직을 구현한다. 첫 번째 필드인 value 가 해당 어노테이션을 추가한 필드 값이 들어오게 된다. 위에 예시에서는 emails 필드가 된다. 내부 로직은 value 생략한다 대충 해당 어노테이션이 적용된 string 필드는 , 로 구문 된 문자열이고 , 로 구문된 문자열은 email 형태의 값이어야 한다는 뜻이다.

 

적용
위에서 생성한 IsAllEmail 어노테이션을 적용하고자 하는 필드에 붙여주기만 하면 된다.

public class Request {
    // title 은 null 이 아니여야하고 길이는 1 ~ 255 자 사이여야한다. 그렇지 않으면 validation error 를 발생시키겠다. 
    @NotNull()
    @Size(min = 1, max = 255)
    private String title;

    @IsAllEmail() 
    private String recvAddrs;
}

 

'스프링' 카테고리의 다른 글

스프링 IOC, Bean  (0) 2022.01.17