인프런 커뮤니티 질문&답변

IL님의 프로필 이미지
IL

작성한 질문수

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

@ControllerAdvice

@ModelAttribute로 받은 객체의 API 예외 처리

작성

·

882

3

안녕하세요. @ControllerAdvice로 예외처리를 하는 부분에서 궁금증이 생겨 질문남기게 되었습니다.

@RequestBody를 통해 값을 받는 요청들은
@ExceptionHandler(MethodArgumentNotValidException.class) 설정을 통해서 예외처리를 할 수 있는데, @ModelAttribute를 통해서 값을 받는 요청들은 ControllerAdvice에서 어떠한 Exception을 통해 걸러서 예외처리를 해야하는지 감이 잡히지 않습니다.

@ModelAttribute를 통해 값을 받는 경우 어떠한 방식으로 공통예외처리를 할 수 있는 걸까요?

답변 2

2

ModelAttribute의 바인딩 시점에 발생되는 오류시점에는 정수타입의 값이 입력되어야 하는곳에 문자열이 입력되는 부분에서 발생할 수 있습니다.(TypeMismatch)

실제로 쿼리스트링을 통해 int타입 값에 문자열을 입력하게되면 예외가 발생하지 않고 BeanPropertyBindingResult가 생성한 에러 메시지가 로그에 찍히게 됩니다.

이는 사실 직접적인 예외가 발생했다기 보다는 BeanPropertyBindingResult 객체가 예외를 먹고 예외에 관련된 정보를 로그로 출력해준다라고 생각하시면 될 것 같습니다.

스프링 공식문서에서는 기본적으로 @ModelAttribute에 대한 예외 처리는 BindingResult를 이용하여 처리하는 것을 권장하고 있습니다.

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-modelattrib-method-args

더하여 강사님의 MVC1 BindingResult2 강의를 참고해보셔도 좋을것 같습니다!

직접적으로 @ModelAttribute의 예외를 캐치해서 @ExceptionHandler로 처리하는 방법은 찾아봐도 나오지 않아 잘 모르겠습니다만 BindingResult라는 좋은 객체가 존재하므로 해당 객체를 이용해도 좋을것 같다는 생각에 댓글 달아봤습니다!

김영한님의 프로필 이미지
김영한
지식공유자

이호석님 감사합니다^^

1

과거 제가 달았던 답변이 기억이나 다시 답변달아봅니다 ㅎㅎ

@ModelAttribute로 바인딩 하는 객체에 @Valid를 붙여주게 되면 입력값에 대한 검증을 해줍니다.

    @PostMapping("/images")
    public ResponseEntity<Void> uploadImages(@Valid final ImagesUploadRequest imagesUploadRequest) {
        awsS3Service.uploadFiles(imagesUploadRequest);
        return ResponseEntity.status(HttpStatus.CREATED)
                .build();
    }

입력값을 검증받을 객체는 다음과 같습니다.

@Getter
public class ImagesUploadRequest {

    @NotBlank(message = "카테고리는 반드시 입력해야 합니다.")
    private String category;

    @NotNull(message = "이미지는 반드시 첨부해야 합니다.")
    @Size.List(value = @Size(min = 1, max = 10, message = "최대 10장까지 업로드할 수 있습니다."))
    private List<MultipartFile> files;

    @Builder
    private ImagesUploadRequest(final String category, final List<MultipartFile> files) {
        this.category = category;
        this.files = files;
    }
}

이때 입력값 검증이 실패하면 다음과 같이 logging이 되는것을 알 수 있는데요
(제가 고의로 발생시킨 예외는 10장을 초과하는 MultipartFile을 요청으로 보낸 상태입니다.)

2023-08-02 20:11:38.075  WARN 94275 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException:  어쩌구 저쩌구 ~~]

이때 org.springframework.validation.BindException라는 예외가 발생했고, 해당 예외를 스프링부트가 로깅해주는것을 알 수 있습니다.

따라서 @ControllerAdvice 혹은 @RestControllerAdvice가 붙은 advice 클래스에 해당 예외를 잡아주게 되면 입력값에 대한 검증 예외를 커스텀하게 처리할 수 있습니다!

@RestControllerAdvice
public class ControllerAdvice {

    @ExceptionHandler(BindException.class)
    public ResponseEntity<ErrorResponse> handleBindException(final BindingResult bindingResult) {
        String defaultMessage = bindingResult.getFieldErrors()
                .get(0)
                .getDefaultMessage();
        return ResponseEntity.badRequest()
                .body(ErrorResponse.builder()
                        .errorCode(CommonErrorCode.BIND_FILED_NOT_VALUE.value())
                        .message(defaultMessage)
                        .build()
                );
    }
}

더불어서 BindingResult 객체 또한 파라미터로 전달받을 수 있으므로 해당 객체를 통해 우리가 커스텀하게 설정했던 예외 메시지를 출력할 수 있습니다.

 

따라서 다음과 같이 예외 메시지를 커스텀하게 처리할 수 있습니다!

// 예외 처리 전 예외 메시지
{
    "timestamp": "2023-08-02T11:11:38.080+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/images"
}

// 커스텀 예외 처리한 메시지
{
    "message": "최대 10장까지 업로드할 수 있습니다.",
    "errorCode": -3000
}

 

(추가로 @RequestBody를 통해 JSON 데이터를 객체로 매핑하는 경우에는 MethodArgumentNotValidException 예외가 발생하므로 해당 예외를 이용해 커스텀하게 예외 응답을 할 수 있습니다!)

IL님의 프로필 이미지
IL

작성한 질문수

질문하기