<목차>
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;
... 이하 생략
}
[테스트]
"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 을 사용하여 예외 처리 하는 내용에 대해 알아보겠다.
'Spring & Spring boot' 카테고리의 다른 글
[Spring Boot] 예외 처리 - 2. 유효성 검사 Exception 처리 (0) | 2024.08.16 |
---|---|
[Spring Boot] 예외 처리 - 1. ExceptionHandler 사용하기 (0) | 2024.08.15 |
[Spring Boot] Validation - 2. @Validated 어노테이션 사용방법 (0) | 2024.08.14 |
[Spring Boot] Validation - 1. @Valid 어노테이션 사용방법 (0) | 2024.08.14 |
[Spring Boot] JPA - 8. 연관관계 매핑 (Relation) (0) | 2024.08.13 |