ResponseEntityExceptionHandler

공식문서는 이렇게 설명한다.

A class with an @ExceptionHandler method that handles all Spring MVC raised exceptions by returning a ResponseEntity with RFC 7807 formatted error details in the body.

번역하자면, RFC 7807 형식의 오류 세부 사항이 있는 ResponseEntity를 반환하여 모든 Spring MVC를 처리하는 @ExceptionHandler 메서드가 있는 클래스.

먼저 RFC 7807 을 알아보자.

RFC 7807

국제 인터넷 표준화 기구(IETF) 에서 관리하는 RFC 8707 은 HTTP API 의 표준이다.

예외를 어떻게 관리하나

ResponseEntityExceptionHandler 클래스는 HTTP API 표준에 어긋나는 예외를 처리하는 클래스이다.
아래 코드는 해당 클래스 내부의 메소드이다.

@ExceptionHandler({
    HttpRequestMethodNotSupportedException.class,
    HttpMediaTypeNotSupportedException.class,
    HttpMediaTypeNotAcceptableException.class,
    MissingPathVariableException.class,
    MissingServletRequestParameterException.class,
    ServletRequestBindingException.class,
    ConversionNotSupportedException.class,
    TypeMismatchException.class,
    HttpMessageNotReadableException.class,
    HttpMessageNotWritableException.class,
    MethodArgumentNotValidException.class,
    MissingServletRequestPartException.class,
    BindException.class,
    NoHandlerFoundException.class,
    AsyncRequestTimeoutException.class
})
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
    HttpHeaders headers = new HttpHeaders();

    if (ex instanceof HttpRequestMethodNotSupportedException) {
        HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
        return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotSupportedException) {
        HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
        return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMediaTypeNotAcceptableException) {
        HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
        return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingPathVariableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestParameterException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
    }
    else if (ex instanceof ServletRequestBindingException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
    }
    else if (ex instanceof ConversionNotSupportedException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
    }
    else if (ex instanceof TypeMismatchException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotReadableException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
    }
    else if (ex instanceof HttpMessageNotWritableException) {
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
    }
    else if (ex instanceof MethodArgumentNotValidException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
    }
    else if (ex instanceof MissingServletRequestPartException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
    }
    else if (ex instanceof BindException) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return handleBindException((BindException) ex, headers, status, request);
    }
    else if (ex instanceof NoHandlerFoundException) {
        HttpStatus status = HttpStatus.NOT_FOUND;
        return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
    }
    else if (ex instanceof AsyncRequestTimeoutException) {
        HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
        return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request);
    }
    else {
        // Unknown exception, typically a wrapper with a common MVC exception as cause
        // (since @ExceptionHandler type declarations also match first-level causes):
        // We only deal with top-level MVC exceptions here, so let's rethrow the given
        // exception for further processing through the HandlerExceptionResolver chain.
        throw ex;
    }
}

이 메소드는 위에서 언급했듯 HTTP API 표준에 어긋난 경우 발생하는 예외를 잡아준다.

나의 생각

당연히 MethodArgumentNotValidException.class 만 이용해 예외를 처리했었다. 하지만 이미 Spring 에서 지원을 해주고 있었고 Spring 기능이 진짜 많은 것 같다고 느낀 부분이었다.

ResponseEntityExceptionHandler 를 사용해도 좋냐라고 물을 땐, Yes 다.

상속 받는 클래스가 다양한 커스텀 예외(어플리케이션의 요청과 응답을 처리할 때 발생할)를 만들어서,
handle 메소드를 추가한다면 그 클래스는 탄탄한 HTTP 통신을 위한 전역 예외 처리 클래스가 될 것이다.

'spring' 카테고리의 다른 글

@WebMvcTest 는 어떻게 Controller 관련 bean 들만 등록할까?  (2) 2023.04.28
spring boot - oracle wallet  (0) 2022.04.10
Gradle  (0) 2022.02.14
Maven 과 Gradle  (0) 2022.02.14
복사했습니다!