트러블 슈팅 221029

u-nij·2022년 10월 29일
3

트러블 슈팅

목록 보기
5/6
post-thumbnail
post-custom-banner

실행 환경

Spring Boot 2.7.3
Java 11.0.9

상황

  • Controller에서 @Valid 어노테이션을 사용해서
  • Bean Validation(@NotNull)를 이용해 요청으로 받은 application/json 데이터를 검사
// 예시
public ResponseEntity<String> postComment(@RequestBody @Valid CommentDto commentDto) {
		// ...
}
// 예시
public class RequestDto {

    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public static class CommentDto {
        @NotNull
        private Boolean isPublic;
        private String text;
    }
}
  • 아래처럼 요청을 보내게 됐을 때, MethodArgumentNotValidException 예외가 발생하기 때문에
{
    "text": "abc"
}

.m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [1] in public {Response_Return_Type} {Controller_Method}: [Field error in object '{DTO}' on field '{Field_Name}': rejected value [null]; codes [NotNull.requestDto.mobileIsPublic,NotNull.{Field_Name},NotNull.java.lang.Boolean,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [{DTO}.{Field_Name}]; arguments []; default message [{Field_Name}]]; default message [널이어서는 안됩니다]] ]

  • Response_Return_Type: org.springframework.http.ResponseEntity<java.lang.String>
  • Controller_Method: postComment(CommentDto)
  • DTO: CommentDto
  • Field_Name: isPublic(예외 발생 원인)
  • @RestControllerAdvice 어노테이션을 사용한 GlobalExceptionHandler를 이용해 예외 처리를 해준 상태였다.
// 예시
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleArgumentNotValidException(MethodArgumentNotValidException ex) {
        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST); // 400 상태 코드 반환
    }
}

발생한 에러

  • 스프링부트 어플리케이션 실행시 BeanCreationException 발생

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'handlerExceptionResolver' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerExceptionResolver]: Factory method 'handlerExceptionResolver' threw exception; nested exception is java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {public org.springframework.http.ResponseEntity {프로젝트명}.GlobalExceptionHandler.handleArgumentNotValidException(org.springframework.web.bind.MethodArgumentNotValidException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception}

왜?

@ExceptionHandler에 이미 MethodArgumentNotValidException이 구현되어있기 때문에 GlobalExceptionHandler에서 동일한 예외 처리를 하게 되면, Ambiguous(모호성) 문제가 발생한다. 즉, MethodArgumentNotValidException 예외에 대해 어떤 방법을 사용해야 할 지 모르게 된다.

해결 방법

  • 스택플로우 참고
  • @ExceptionHandler 어노테이션을 사용하는 대신, 해당 핸들러(handleMethodArgumentNotValid)를 직접 오버라이드해서 사용한다.
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatus status,
                                                                  WebRequest request) {
        return super.handleMethodArgumentNotValid(ex, headers, status, request);
    }

실행 결과

  • 저는 에러 응답 형식을 일정하게 하기 위해 ErrorCode와 ErrorResponse를 생성했고,
public enum ErrorCode {
    VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "Validation failed for argument");

    private final HttpStatus httpStatus;
    private final String message;
@Getter
public class ErrorResponse {
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final int statusCode;
    private final String error;
    private final String message;

    public ErrorResponse(ErrorCode errorCode) {
        this.statusCode = errorCode.getHttpStatus().value();
        this.error = errorCode.getHttpStatus().name();
        this.message = errorCode.getMessage();
    }
}
  • 오버라이딩한 handleMethodArgumentNotValid 핸들러를 아래와 같이 작성했습니다.
    // Bean Validation 예외 발생시
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ErrorCode errorCode = ErrorCode.VALIDATION_FAILED;
        return ResponseEntity.status(errorCode.getHttpStatus())
                .body(new ErrorResponse(errorCode));
    }
  • Postman에서 위와 똑같은 JSON 요청을 보내게 되면, 응답이 아래와 같이 출력됩니다.
profile
삶은 달걀이다
post-custom-banner

2개의 댓글

comment-user-thumbnail
2023년 3월 16일

덕분에 에러 해결했습니다. 감사합니다.

답글 달기
comment-user-thumbnail
2023년 8월 8일

감사합니다!

답글 달기