개요
이번 글에서는 Request시에 @Vaild를 사용하여 @RequestBody와 @ModelAttribute로 DTO 또는 객체를 검증할 때 커스텀 예외처리를 적용하는 방법에 대해 이야기해 보려고 합니다.
1. @Valid 사용시에 @RequestBody에 대한 검증 예외가 발생하는 케이스
위 경우에는 POST 요청시에 총 3가지 예외 케이스가 발생하는 것을 확인할 수 있었습니다.
- MethodArgumentNotValidException(400 Bad Request)
- HttpMessageNotReadableException(400 Bad Request)
- HttpMediaTypeNotSupportedException(415 Unsupported Media Type)
첫번째 MethodArgumentNotValidException은 요청시에 보낸 Body가 정상적으로 파싱은 됐지만(HttpMessageConverter를 통해 파싱됩니다.) DTO의 Validation(@NotNull, @NotBlank...)에서 검증 예외에 발생하는 경우에 발생합니다.
두번째 HttpMessageNotReadableException은 요청시에 보낸 Body가 서버에서 허용하는 미디어 유형은 맞지만 어딘가 불완전하게 요청되었을 때 발생합니다.
세번째 HttpMediaTypeNotSupportedException은 요청시에 보낸 Body가 서버에서 허용하는 미디어 유형이 아닌 경우에 발생합니다.
예를들어 Content-Type을 application/json으로 보내야 하지만 text/plain으로 요청한 경우가 있습니다.
2. @Valid 사용시에 @ModelAttribute에 대한 검증 예외가 발생하는 케이스
위 경우에는 GET 요청시에 총 1가지 예외 케이스가 발생하는 것을 확인할 수 있었습니다.
- BindException
첫번째 BindException은 요청시에 보낸 파라미터를 바인딩 하는 시점에 발생합니다.
3. BindException vs MethodArgumentNotValidException
@Valid와 @RequestBody 또는 @ModelAttribute 조합을 사용하여 커스텀 예외처리를 할 때 서로 다른 예외가 발생하는 것을 확인할 수 있습니다.
@Valid + @RequestBody 조합은
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ApiResponse invalidRequestHandler(MethodArgumentNotValidException e) {
return ApiResponse.builder()
.statusCode(StatusCode.BAD_REQUEST.getStatusCode())
.data(null)
.build();
}
위 코드와 같이 MethodArgumentNotValidException을 커스텀 예외처리 해줬지만 @Valid + @ModelAttribute 사용시에는 커스텀 예외를 타지 않고 스프링의 기본 에러 응답 클래스인 DefaultErrorAttributes가 반환되는 것을 확인할 수 있을 것입니다.
이는 위에 설명한 1, 2번 내용을 참고하시면 됩니다.
@Valid + @RequestBody 사용시에는 MethodArgumentNotValidException가 발생하고 @Valid + @ModelAttribute 사용시에는 BindException이 발생하기 때문에 위 코드에서는 @ControllerAdvice를 통한 예외처리가 발생하지 않고 DefaultErrorAttributes가 반환되는 것입니다.
@RequestBody는 요청 본문이 HttpMessageConverter를 통해 변환됩니다.
예를 들어 contentType이 application/json 이면 json에서 object 로 변환해줍니다.
이 때문에 referenceType이 Null값을 포함하더라도 캐스팅이 정상적으로 수행됩니다.
캐스팅은 정상적으로 수행됐으니 @Valid를 통한 검증 과정에서 MethodArgumentNotValidException이 발생하는 것입니다.
@ModelAttribute에서는 캐스팅 과정에서 발생하는 문제로 referenceType이 Null인 경우, 자료형이 다른 경우 등에 BeanPropertyBindingResult가 예외를 먹어 BindException이 발생하는 것입니다.
4. 커스텀 예외처리 적용하기
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public ApiResponse invalidRequestHandler(BindException e) {
return ApiResponse.builder()
.statusCode(StatusCode.BAD_REQUEST.getStatusCode())
.data(null)
.build();
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse invalidRequestHandler(MethodArgumentNotValidException e) {
return ApiResponse.builder()
.statusCode(StatusCode.BAD_REQUEST.getStatusCode())
.data(null)
.build();
}
MethodArgumentNotValidException던 BindException던 두 예외 모두 API 파라미터에 대한 검증이 실패한 경우로 동일하게 400 Bad Request를 내려줘야 하는 경우가 존재합니다.
이 경우에는 위와 같이 코드를 작성하면 두 케이스에 대해 같은 400 Bad Request 응답을 내려줄 수 있습니다.
이렇게 처리하면 클라이언트에서는 서버에 Query Params로 요청을 하던, Body로 요청을 하던 API 파라미터에 대한 잘못된 요청시 동일하게 판단하고 처리할 수 있을 것입니다.
'Spring' 카테고리의 다른 글
[Spring] Spring Data JPA에서 @ManyToOne 관계 N + 1 문제 해결하기 (0) | 2023.07.31 |
---|---|
[Spring] JPA NativeQuery 사용시 @Param으로 객체 사용하기 (0) | 2022.12.16 |
[Spring] JPA에서 findBy 매개변수 없이 사용하기 (0) | 2022.12.07 |
[Spring] SpringBoot에서 환경에 따른 Properties 사용하기(Spring Profiles) (0) | 2022.11.12 |
[Spring] SpringBoot 프로젝트 WAS 배포 후 @PathVariable을 사용하는 Controller에서 MethodArgumentTypeMismatchException이 발생할 때 해결하는 방법 (0) | 2022.11.05 |