Spring & Spring boot

[Spring Boot] Validation - 4. 커스텀 Validation 설정하기

jh4dev 2024. 8. 15. 15:36
<목차>

1. 커스텀 Validation 의 필요성
2. 커스텀 Validation 어노테이션 설정
3. 테스트

 

[커스텀 Validation 의 필요성]

실제 업무에서 유효성 검사 시, JAVA 나 Spring 에서 어노테이션으로 제공하지 않는 내용을 체크해야할 경우가 있다.

좀 더 자세한 케이스를 살펴보자면,

  • 같은 검증 로직이 여러 곳에서 반복된다면, 이 로직을 커스텀 어노테이션으로 만들어 코드의 중복을 줄일 수 있다.
    Ex) 전화번호
  • 특정 도메인에만 적용되는 내용이라면 커스텀 어노테이션을 사용하는 것이 좋다.
    Ex) 계좌번호
  • 여러 필드의 값을 조합하여 검증해야하는 경우 커스텀 어노테이션을 사용하는 것이 좋다.
    Ex) 종료 날짜가 시작 날짜보다 커야한다는 조건

[커스텀 Validation 어노테이션 설정]

그렇다면, 이러한 커스텀 Validation 어노테이션을 생성하고 적용하는 방법에 대해 알아보자.

기존 @Pattern 어노테이션과 정규식을 사용해 유효성 검사를 진행하였던 "전화번호" 를 커스텀 어노테이션으로 생성해보겠다.

 

1. Validator 클래스 생성

ConstraintValidator 인터페이스를 구현하는 클래스를 생성하고, isValid() 메서드를 오버라이딩한다.

null 을 허용하지 않고, 정규식을 통해 검사할 내용을 설정하였다.

<TelephoneValidator> 

    public class TelephoneValidator implements ConstraintValidator<Telephone, String> {

        @Override
        public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
            if(s == null) return false;
            return s.matches("01(?:0|1|[6~9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$");
        }
    }

2. 커스텀 어노테이션 인터페이스 작성

<Telephone> Annotation Interface

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = TelephoneValidator.class)
    public @interface Telephone {

        String message() default "전화번호 형식이 올바르지 않습니다.";
        Class[] groups() default {};
        Class[] payload() default {};
    }

 

이 어노테이션 인터페이스에 대해 자세히 살펴보자.

  • 1번 라인 : @Target
    • 생성하는 어노테이션을 어디서 선언할 수 있는지 정의하는데 사용된다.
    • 위 소스에서는 필드에 선언하도록 설정하였다.
    • 이 외에도 FIELD 대신,
      PACKAGE / TYPE / CONSTRUNTOR / FIELD / METHOD / ANNOTATION_TYPE / LOCAL_VARIABLE / PARAMETER / TYPE_PARAMETER / TYPE_USE
      를 사용할 수 있다.
  • 2번 라인 : @Retention
    • 생성하는 어노테이션이 실제로 적용되고 유지되는 범위를 의미한다.
    • 위 소스에서는 RUNTIME 으로 설정하여,컴파일 이후에도 JVM에 의해 계속 참조하도록 설정하였다.
    • 이 외에도, 아래 설정이 더 있다.
      • CLASS : 컴파일러가 클래스를 참조할 때까지 유지
      • SOURCE : 컴파일 전까지만 유지
  • 3번 라인 : @Constraint
    • 앞서 작성한 TelephoneValidator 와 매핑
  • Methods
    • message() : 유효성 검사에 실패했을 경우 반환되는 메시지
    • groups() : 유효성 검사를 사용하는 그룹으로 설정
    • payload() : 사용자가 추가 정보를 위해 전달하는 값

3. 유효성 검사를 진행할 모델에 어노테이션 적용

 

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    @Builder
    public class ValidatedRequestDto {

        @NotBlank
        String name;

        @Email
        String email;

        //@Pattern(regexp = "01(?:0|1|[6~9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
        @Telephone //커스텀 유효성 검사 어노테이션
        String phoneNumber;

        ... 이하 생략
    }

 


[테스트]

REQUEST 
{
  "name": "string",
  "email": "string",
  "phoneNumber": "111", // 전화번호 형식에 맞지 않는 request 로 호출
  "age": 40,
  "description": "string",
  "count": 0,
  "booleanCheck": true
}

호출 결과

위와 같이 에러가 발생한 것을 확인할 수 있다.

로그를 확인해보자.

 

[2024-08-15 15:31:35.568] [WARN ] [http-nio-8080-exec-5] org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<cohttp://m.springboot.valid.data.dto.ValidatedRequestDto> cohttp://m.springboot.valid.controller.ValidatedController.checkValidationByValidatedAnnoGroup1(com.springboot.valid.data.dto.ValidatedRequestDto): [Field error in object 'validatedRequestDto' on field 'phoneNumber': rejected value [111]; codes [Telephone.validatedRequestDto.phoneNumber,Telephone.phoneNumber,Telephone.java.lang.String,Telephone]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validatedRequestDto.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber]]; default message [전화번호 형식이 올바르지 않습니다.]] ]

 

위와 같이 설정해준 메시지도 잘 생성된 것을 확인할 수 있다.

 

다음 포스핑에서는 위와 같이 400 에러가 아닌, Exception 을 사용하여 예외 처리 하는 내용에 대해 알아보겠다.