티스토리 뷰

반응형

개발을 하다 보면 JSR에서 제공해주는 어노테이션으로는 처리가 부족하여 직접 우리가 원하는대로 예외를 처리해주어야 하는 경우가 있다. 이번에는 커스텀 애노테이션(Custom Annotation)을 만들어 개발자의 입맛에 맞게 직접 유효성 검사를 하는 Validation을 구현해보도록 하자.

 

 

 

 

1. 커스텀 애노테이션(Custom Annotation) 만들어 직접 유효성 검사하기


개발을 하다 보면 사용자의 정보를 받을 때 연락처를 받아야 하는 경우가 있다. 이번에는 사용자의 연락처를 검증하는 어노테이션과 검증기(Validator)를 구현해보도록 하자. 

 

 

[ 검증을 위한 커스텀 어노테이션 생성 ]

먼저 우리는 javax.validation에서 제공해주는 어노테이션들과 비슷한 어노테이션을 만들어주어야 한다. 우리는 연락처가 올바른 포맷인지 검증할 계획이므로 다음과 같은 어노테이션을 만들어줄 수 있다.

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
@Documented
public @interface Phone {

    String message() default "Invalid Phone";

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

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

}

 

위에 있는 어노테이션들은 각각 다음의 역할을 한다.

  • @Target(FIELD): 해당 어노테이션을 필드에만 선언 가능함
  • @Retention(RUNTIME): 해당 어노테이션이 유지되는 시간으로써 런타임까지 유효함
  • @Constraint(validatedBy = PhoeValidator.class): PhoeValidator를 통해 유효성 검사를 진행함
  • @Documented: JavaDoc 생성시 Annotation에 대한 정보도 함께 생성

 

아래의 3가지 속성들(message, groups, payload)은 JSR-303 표준 어노테이션들이 갖는 공통 속성들이다. 해당 속성들은 각각 다음의 역할을 한다.

  • message: 유효하지 않을 경우 반환할 메세지
  • groups: 유효성 검증이 진행될 그룹
  • payload: 유효성 검증 시에 전달할 메타 정보

 

message는 유효성 검증에 실패한 경우에 반환할 메세지를 정의한다. 또한 groups는 앞선 포스팅에서 살펴보았던 @Validated의 기능 중 하나인 그룹 묶기에 사용된다. payload는 제약 사항과 관련된 메타 정보를 정의하는데 사용된다. 예를 들어 해당 유효성 검증의 중요도 같은 것들을 전달하기 위해 사용될 수도 있는데 거의 사용되지 않는다.

이제 다음으로 PhoneValidator를 구현해주어야 한다.

 

 

[ 검증을 위한 Validator 구현 ]

우리가 구현할 Validator는 JSR에서 제공하는 javax.validation의 ConstraintValidator 인터페이스를 구현해주어야 한다. 해당 인터페이스는 다음과 같이 생성되어 있다.

public interface ConstraintValidator<A extends Annotation, T> {

    default void initialize(A constraintAnnotation) {
    }

    boolean isValid(T value, ConstraintValidatorContext context);
}

 

위의 인터페이스는 2가지 제네릭 타입을 받고 있는데, 순서대로 적용될 어노테이션과 적용될 타입에 해당된다. 또한 ConstraintValidator가 갖는 메소드들은 각각 다음의 역할을 한다.

  • initialize: Validator를 초기화하기 위한 메소드
  • isValid: 유효성을 검증하는 메소드

 

intialize는 기본적으로 default 메소드로 구현되어 있으므로 초기화할 작업이 없다면 따로 구현해주지 않아도 된다. initialize는 isValid가 처음 호출될 때 1회 호출된다. isValid에는 우리가 검증할 로직을 구현해주면 된다.

이제 연락처를 검증하기 위한 Validator를 구현하면 다음과 같다. 아래에서는 예제이므로 xxx-xxxx-xxxx 포맷인지만 검사하도록 작성해두었다.

public class PhoneValidator implements ConstraintValidator<Phone, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        Pattern pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}");
        Matcher matcher = pattern.matcher(value);
        return matcher.matches();
    }
}

 

 

[ 테스트 및 검증 ]

이제 위에서 작성한 유효성 검증이 올바르게 동작하는지 확인하기 위해 DTO와 컨트롤러를 추가해줄 차례이다.

먼저 입력을 DTP에 @Phone 어노테이션을 다음과 같이 적용해서 구현할 수 있다.

@Getter
@RequiredArgsConstructor
public class AddPhoneRequest {

    @Phone
    private final String phone;

}

 

그러면 이제 해당 입력을 받는 컨트롤러를 개발해주어야 한다. 검증을 위해서는 @Valid를 반드시 붙여주어야 한다.

@RestController
@RequiredArgsConstructor
public class PhoneController {

    @PostMapping("/phone")
    public ResponseEntity<String> temp(@Valid AddPhoneRequest addPhoneRequest) {
        return ResponseEntity.ok(addPhoneRequest.getPhone());
    }

}

 

그리고 해당 API를 호출해보면 에러가 발생함을 확인할 수 있다.

 

 

Validator를 주입받아 프로그래밍적으로 유효성 검증을 진행할 수도 있다. 하지만 Spring이 제공해주는 핵심 가치 중 하나는 관심사의 분리를 통해 비지니스 로직에 집중할 수 있게 해주는 것이라고 생각한다. 굳이 코드 상으로 검증 로직을 넣고, 에러 처리를 해줄 필요는 거의 없어 보인다. 어노테이션을 통해 비지니스 로직이 아닌 유효성 검증과 같은 것들은 분리해내도록 하자.

 

 

 

 

관련 포스팅

  1. @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)
  2. 커스텀 애노테이션(Custom Annotation) 만들어 직접 Validation 유효성 검사하기 - (2/2)

 

 

 

반응형
댓글
댓글쓰기 폼
반응형
공지사항
Total
2,491,227
Today
264
Yesterday
5,743
TAG
more
«   2022/05   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
글 보관함