오늘은 Custom Validator 사용 방법을 정리해보려고 한다!
사이드 프로젝트를 진행하면서 spring validation 라이브러리에서 지원해주지 않는
유효성 검증이 필요한 상황이 있었다.
(( 상황 설명을 잠깐 해보자면,,
게시글 내용을 @Size를 사용해서 최대 3000자까지 입력할 수 있도록 제한해 두었는데,
줄바꿈이 CRLF로 들어와서 문자열 길이가 2로 잡히는 문제가 있었다.
(포스트맨으로 요청을 보내면 LF로 오는 것 같은데, 프론트에서 요청을 보내면 CRLF로 들어오는 것 같았다🤔)
그래서 팀원들과 논의해본 후.. Custom Validator로 "\r"(CR)을 ""로 바꾸고 문자열 길이를 검증하기로 했다.
뭔가 Custom Validator를 사용하는 것보다 더 좋은 방법이 있었을 것 같지만..
누군가 답을 알고 있다면 댓글을 남겨주세요 ㅎ.ㅎ
그래서 이때 찾아봤던 내용을 정리해보려고 한다.
Custom Validator를 적용하려면 두 가지를 구현해야 한다.
(1) 커스텀 어노테이션 객체, (2) 커스텀 어노테이션 객체를 사용하는 Validator
그럼 커스텀 어노테이션 객체를 먼저 만들어보자.
1. Creating a New Annotation
LengthWithoutCR.java
/**
* The annotated element length must be between the specified boundaries (included).
* The length will be calculated without the escape sequence character "/r" a.k.a. CR.
* Supported types are:
* CharSequence (length of character sequence is evaluated)
* null elements are considered valid.
* Author:
* Minji Kim
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = LengthWithoutCRValidator.class)
public @interface LengthWithoutCR {
String message() default "size must be between {min} and {max}";
Class[] groups() default {};
Class[] payload() default {};
/**
* @return size the element must be higher or equal to
*/
int min() default 0;
/**
* @return size the element must be lower or equal to
*/
int max() default Integer.MAX_VALUE;
}
- @Target: 어노테이션을 적용할 위치를 지정해주는 것이다.
- PACKAGE, TYPE, METHOD 등 여러가지가 있지만
여기서는 멤버 변수에 할당할 어노테이션이기 때문에 FIELD로 해주었다.
- PACKAGE, TYPE, METHOD 등 여러가지가 있지만
- @Retention: 어노테이션의 유지 범위를 설정해주는 것이다.
- SOURCE, CLASS, RUNTIME이 있고
우리는 실행하는 동안에도 유지되어야 하므로 RUNTIME으로 설정해주었다.
- SOURCE, CLASS, RUNTIME이 있고
- @Constraint: 검증 클래스를 지정해주는 것이다.
- 위에서 언급한 (2)번 validator 클래스를 지정해주면 된다.
- 검증 클래스로는 ConstraintValidator 인터페이스를 구현한 클래스가 지정되어야 한다.
- message(): 검증에 실패했을 때 표시되는 오류 메시지이다.
- groups() & payload(): Spring 표준을 준수하는 상용구 코드인데, 우리는 사용하지 않는다.
- min() & max(): 추가로 내가 어노테이션에서 받고 싶은 값을 정의한 것이다!
참고로 min() max() 이거는 아무리 찾아봐도 어떻게 하는건지 안나와서 @Size 어노테이션 보고 따라했다..🙃
2. Creating a Validator
LengthWithoutCRValidator.java
public class LengthWithoutCRValidator implements
ConstraintValidator<LengthWithoutCR, String> {
int min;
int max;
@Override
public void initialize(LengthWithoutCR constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null)
return false;
String replacedValue = value.replaceAll("\\r", "");
return min <= replacedValue.length() && replacedValue.length() <= max;
}
}
ConstraintValidator를 구현한 클래스로,
제네릭의 첫 번째 값에 Validator를 적용할 어노테이션 객체가 들어간다.
두 번째 값은 해당 어노테이션으로 검증을 진행할 필드의 데이터 유형을 넣어주면 된다.
우리는 String 필드에 어노테이션을 사용할 것이므로 String을 넣어주었다.
ConstraintValidator 인터페이스를 구현할 때는 isValid()를 필수로 오버라이딩해 주어야 한다!
isValid()의 첫 번째 매개변수인 value가 우리가 어노테이션을 사용한 필드값이다.
따라서 이 value 값을 가지고 어떤 검증을 수행할건지 코드를 작성해주면 된다!
그리고 나는 min과 max값을 추가로 넣어주었기 때문에
얘네를 멤버변수로 선언해주고, initialize()를 오버라이딩해서 초기화를 진행해주었다.
3. Applying Validation Annotation
이제 우리가 만든 validator 어노테이션을 사용해보자.
@Getter
@Setter
@AllArgsConstructor
public class ArticleRequestDto {
@NotNull(message = "게시판 id가 없습니다.")
private Long forumId;
@LengthWithoutCR(min = 1, max = 3000, message = "내용은 1자 이상 3000자 이하만 가능합니다.")
private String content;
private List<MultipartFile> images;
}
이렇게 추가로 넣어준 min, max값과 에러 메시지 값을 넣어서 적용해보았다.
아주아주 간단한 내용이지만,,
언젠가 또 써먹을 일이 있을 것 같아서 정리해 보았다!
오늘 정리한 내용은 단일 필드에 적용하는 커스텀 어노테이션이었는데,
다중 필드에 적용하는 방법도 있는 걸로 알고 있다.
그것은 아래 링크를 참조해보면 좋을 것 같다.
🔗 참고 링크
https://www.baeldung.com/spring-mvc-custom-validator
https://techblog.woowahan.com/2684/ <- 이건 아직 안 읽어봤는데 읽어보면 좋을 것 같다 ^.^