스프링에서 일반적으로 RequestBody 의 값을 Validation 하는 방법
스프링 MVC 에서 @Valid 어노테이션을 명시해주면
컨트롤러에서 해당 값에 대해 미리 Validation 해줘서 값이 들어오게 된다.
JAVAX 에서 @NotBlank, @NotNull, @Positive 등등
원시 타입에 대해서는 여러 가지 기본 검증을 지원한다.
하지만 Enum 클래스에 대한 Validation 은 기본으로 지원하지 않는다.
그러면 어떤 식으로 처리해야 할까?
📌 Enum Validation 처리를 하지 않았을 경우
Enum 필드에 대해 Validation 처리하지 않는다면
Enum 클래스에 속하지 않은 값이 Request 필드에 포함되면
JSON 값을 Enum 으로 올바르게 파싱하지 못하여
위 처럼 JSON parse error 가 응답으로 오게 된다.
하지만 백엔드의 입장에서도 그렇고 500 에러와 더불어 이런 에러는 상당히 보기가 싫다
그러니까 이러한 예외 처리를 더불어
에러 메세지까지 보내주는게 정신건강에 좋을 거 같지 않은가?
📌 Enum Validation 적용 과정
📝 EventStatus
@Getter
@AllArgsConstructor
public enum EventStatus {
// 준비중
PREPARING("PREPARING", "준비중"),
// 진행중
OPEN("OPEN", "진행중"),
// 정산중
CALCULATING("CALCULATING", "정산중"),
// 지난 공연
CLOSED("CLOSED", "지난공연"),
// 삭제된 공연
DELETED("DELETED", "삭제된공연");
private final String name;
@JsonValue private final String value;
// Enum Validation 을 위한 코드, enum 에 속하지 않으면 null 리턴
@JsonCreator
public static EventStatus fromEventStatus(String val) {
return Arrays.stream(values())
.filter(type -> type.getName().equals(val))
.findAny()
.orElse(null);
}
}
Validation 을 사용하고 싶은 Enum Class 에
다음과 같이 @JsonCreator 어노테이션을 명시한 정적 메소드를 추가하자
@JsonCreator 는 페이로드에서 JSON -> Enum 필드로 파싱하는 방법을 직접 명시해주게 된다.
fromEventStatus 메소드에서는 Enum Class 에 속하지 않는 값이 들어올 때 null 을 리턴하게 되서
JSON parsing error 가 발생하지 않도록 해줬다.
이렇게만 해도 JSON parsing error 는 응답으로 오지 않지만
잘못된 Enum 값에 대한 예외 처리도 해주고 싶다.
📝 Enum
@Constraint(validatedBy = {EnumValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Enum {
String message() default "Invalid Enum Value.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends java.lang.Enum<?>> target();
}
커스텀 어노테이션 인터페이스를 작성해준다
- @Constraint 는 Validation 하려는 메소드가 정의된 클래스
- @Target 은 이 어노테이션이 붙을 수 있는 범위
- @Retention 은 이 어노테이션의 생명주기를 지정해준다
또한 Custom Constraint Annotation 을 지정할 때는 다음 3개를 꼭 정의해야 한다
- message 는 예외 발생 시 응답 메세지 지정
- group 은 Validation 그룹 지정
- payload 는 추가 정보를 위해 지정할 수 있으며 주로 심각도를 나타내는 필드
마지막으로 target 은 이 Validator 가 적용해줄 범위를 지정한다
Class<? extends Enum> 은 java.lang.Enum 클래스를 상속받는 모든 인자라는 뜻을 제네릭으로 표현한 것이다
즉, 이 인자에는 Enum 클래스만 올 수 있다는 뜻이 된다
target 필드는 Request 에 무조건 Enum 타입만 넣는다고 가정하면 없어도 되지만,
다른 타입의 값을 Request 로 받아 Enum 으로 넣어주고 싶은 상황이 생길 수도 있으니
범용성을 위해 추가하였다.
📝 EnumValidator
public class EnumValidator implements ConstraintValidator<Enum, java.lang.Enum> {
private Enum annotation;
@Override
public void initialize(Enum constraintAnnotation) {
this.annotation = constraintAnnotation;
}
@Override
public boolean isValid(java.lang.Enum value, ConstraintValidatorContext context) {
Object[] enumValues = this.annotation.target().getEnumConstants();
if (enumValues != null) {
for (Object enumValue : enumValues) {
if (value.equals(enumValue.toString())) {
return true;
}
}
}
return false;
}
}
EnumValidator 에서는 Enum 의 value 값들을 가져오기 위해 멤버 변수로 지정하고
initailize 메소드를 오버라이딩 하여 이 값을 할당한다.
이후 isValid 메소드를 오버라이딩 하여 검증 로직을 실행한다.
target 클래스에서 Enum 값들을 가져와 검증하려는 value 와 비교하게 된다.
무조건 Enum 값이 들어온다고 가정하고 작성하면 아래와 같이 줄일 수 있다.
public class EnumValidator implements ConstraintValidator<Enum, java.lang.Enum> {
@Override
public boolean isValid(java.lang.Enum value, ConstraintValidatorContext context) {
return value != null;
}
}
@JsonCreator 에서 해당 Enum 클래스에 없는 값이 들어오면
NULL 로 파싱해주기 때문에 이 로직을 적용할 수 있다
📝 UpdateEventStatusRequest
@Getter
@RequiredArgsConstructor
public class UpdateEventStatusRequest {
@Schema(defaultValue = "OPEN", description = "오픈 상태")
@Enum(target = EventStatus.class, message = "올바른 값을 입력해주세요.")
private EventStatus status;
}
이제 Request 에서 원하는 Enum 필드에 적용해보자.
target 에는 내가 검증하고자 하는 Enum 클래스를 지정하면 된다.
처음에는 이런 식으로 예외처리가 안되어서 에러 메세지가 그대로 노출이 될텐데
@RestControllerAdvice 에서 MethodArgumentNotValidException 핸들링을 해줘
적절하게 에러 메세지를 가공하면
이런 식으로 각 필드별로 메세지를 깔끔하게 정리할 수 있다
🌎 구현 과정 관련 게시글
📎 Custom Enum Validator 구현하기
https://gengminy.tistory.com/47
📎 Reflection 을 이용하여 Enum Validator 개선하기
https://gengminy.tistory.com/48
📎 Custom Enum Deserializer 구현하여 Enum 에 없는 값 null 로 파싱하기
https://gengminy.tistory.com/49
'🚀 프로젝트 > 🥁 두둥' 카테고리의 다른 글
[Spring] 스프링 500 에러 발생 시 Slack 알림 전송하기 (슬랙 봇) (0) | 2023.03.15 |
---|---|
[Spring] 스프링 Slack 메세지 전송하기 (Incoming WebHooks 활용하여 슬랙봇 만들기) (0) | 2023.03.03 |
[Spring] 스프링 날짜 타입 JSON 변환 및 포맷팅하기 - @JsonFormat, @JacksonAnnotationsInside (0) | 2023.02.27 |
[Spring] 스프링 Custom Enum Deserializer 구현으로 JSON Enum null 로 파싱하기 (0) | 2023.02.21 |
[Spring] 스프링 Enum Validator Reflection 으로 개선 및 구현하기 (0) | 2023.02.21 |