통상적으로 작성해둔 Validation은 Controller딴에서 ReqeustDto로 역직렬화 할 때 Spring Boot가 자동으로 해준다.
하지만 나는 RequestDto뿐만 아니라 ResposneDto나 내부적으로 사용하는 Class에서도 사용할 수 있게끔 하고 싶었다.
Validator를 사용해서 생성자 호출시에 유효성 검사를 하게끔 했다.
Exception Advice에서 처리 하던 BindException과 형태가 달라서 문제가 생겼다.
기존의 방식과 똑같이 Exception을 만들기 위한 코드도 함께 첨부한다.
BindException
잘못된 변수명과 그 이유에 대한 Message를 함께 출력하는 코드다.
아래 코드는 디버깅을 통해 변수명과 메세지를 찾은 뒤 맵핑시켰다. 관련 포스팅
@ExceptionHandler(BindException.class)
public ResponseEntity<CommonResDto<Map<String, String>>> handleBindException(BindException e) {
Map<String, String> data = new HashMap<>();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
data.put(((DefaultMessageSourceResolvable) Objects.requireNonNull(error.getArguments())[0]).getDefaultMessage()
, error.getDefaultMessage());
}
log.warn(data + " | EndPoint : " + e.getStackTrace()[0].toString());
e.printStackTrace();
return ResponseEntity.status(ResponseCode.BINDING_FAILED.getStatus())
.body(CommonResDto.error(ResponseCode.BINDING_FAILED.getCode(), ResponseCode.BINDING_FAILED.getDefaultMessage(), data));
}
RuntimeException
BindException을 throw시키면 처리되지 않은 예외라면서 귀찮게 군다..
메소드 옆에 예외를 명시하는 방법은 해당 메소드를 사용할 때마다 예외를 명시해야하는 귀찮음이 있다.
그러므로 RuntimeException으로 try catch 한 뒤 아래처럼 분기처리했다.
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handleRuntimeException(RuntimeException e) {
if (e.getCause() instanceof BindException bindException) {
return this.handleBindException(bindException);
}
this.loggingTextError(e);
e.printStackTrace();
return ResponseEntity.status(ResponseCode.INTERNAL_SERVER_ERROR.getStatus()).body(CommonResDto.error(ResponseCode.INTERNAL_SERVER_ERROR.getCode(), ResponseCode.INTERNAL_SERVER_ERROR.getDefaultMessage()));
}
Utills Class
이제 Utils 클래스에 Validation 메서드를 생성한다.
단순히 BindException만 생성하면 그 안에 어떤 변수에서 문제가 발생했는지 ExceptionAdvice에서 알 수 없으므로 잘못된 변수를 직접 넣어주었다.
public class Utils {
private Utils() {
throw new APIException(ResponseCode.INTERNAL_SERVER_LOGIC_ERROR, "Utils class 생성자 호출 금지");
}
private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
public static final Validator validator = factory.getValidator();
public static <T>void validate(T object) {
Set<ConstraintViolation<T>> violations = Utils.validator.validate(object);
BindingResult bindingResult = new BindException(object, object.getClass().getName());
// 유효성 검사 결과 처리
if (!violations.isEmpty()) {
for (ConstraintViolation<T> violation : violations) {
String propertyPath = violation.getPropertyPath().toString();
String message = violation.getMessage();
// BindingResult에 오류 추가
String[] emptyArray = {};
DefaultMessageSourceResolvable resolvable = new DefaultMessageSourceResolvable(emptyArray,propertyPath);
FieldError error = new FieldError(object.getClass().getName(), propertyPath, null, false, null, new DefaultMessageSourceResolvable[]{resolvable}, message);
bindingResult.addError(error);
}
if (bindingResult.hasErrors()) {
throw new RuntimeException(new BindException(bindingResult));
}
}
}
}
ResponseDto
나는 아래 처럼 생성자에서 유효성 검사를 진행해서 Controller의 @Valid가 처리하는 것과 동일하게 동작하게끔 했다.
@Getter
@NoArgsConstructor
public class TestResDto {
@NotNull
private String test;
@Builder
public TestResDto(String test) {
this.test = test;
Utils.validate(this);
}
}
'JAVA > Spring Boot' 카테고리의 다른 글
Compile(API), Implement 차이점 (0) | 2023.05.14 |
---|---|
Annotation) 커스텀 어노테이션으로 Enum 유효성 검사하기 (0) | 2023.03.13 |
Join을 Map으로 최적화 (0) | 2023.02.23 |
JPA) [수정]@DynamicUpdate, @DynamicInsert (0) | 2023.02.07 |
[Validation] 누락된 값 전역 처리, 클라이언트에 상세히 표시 (0) | 2023.01.25 |