[Spring] 매우 편리하게 예외처리 하기 - ResponseEntityExceptionHandler

유알·2023년 9월 14일
0

[Spring]

목록 보기
14/17

나는 항상 @ControllerAdvice를 통해 전역으로 예외처리를 진행하였다.

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {


    /**
     * 클라이언트의 잘못된 값 전달
     * @param e
     * @return
     */
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<ErrorResponse> handleBadRequestException(BadRequestException e){
        log.error("handleMethodArgumentNotValidException", e);
        ErrorResponse response = ErrorResponse.of(e.getErrorCode(),e.getFieldErrors());
        return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST);
    }

    /**
     * @Validated 로 binding error 발생시 발생
     * HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할 경우 발생
     * 주로 @RequestBody 나 @RequestPart 어노테이션에서 발생
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        log.error("handleMethodArgumentNotValidException", e);
        ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * @ModelAttribut 으로 binding error 발생시 발생
     */
    @ExceptionHandler(BindException.class)
    protected ResponseEntity<ErrorResponse> handleBindException(BindException e){
        log.error("handleBindException", e);
        ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
        return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
    }

    /**
     * enum type 일치하지 않아 binding 못할 경우 발생
     * 주로 @RequestParam enum 으로 binding 못했을 경우 발생
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e){
        log.error("handleMethodArgumentTypeMismatchException",e);
        ErrorResponse response = ErrorResponse.of(e);
        return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST);
    }

대충 이런 식이다. 그런데, 항상 이렇게 공통적으로 발생하는 녀석들에 대해 예외처리를 하는 과정이 반복되고 굉장히 번거로웠다. 특히 뭘 또 놓친것이 아닌지 걱정이 되곤 했다.

그러다가 스프링에 굉장히 유용한 유틸성 객체가 존재하는 것을 알게 되었다.(나도 너무 귀찮아서 이와 비슷한 걸 미리 정의해 놓을까 고민 중이었는데, 역시.. 스프링 모든게 있다.)

바로, ResponseEntityExceptionHandler이다.

public abstract class ResponseEntityExceptionHandler implements MessageSourceAware {

추상 클래스로, 우리가 할 것은 protected로 정의된 handle***메서드만 오버라이드 하여 정의하면 된다.
이것이 무엇인지 내부에 정의된 메서드를 하나 보자

	@ExceptionHandler({
			MethodNotAllowedException.class,
			NotAcceptableStatusException.class,
			UnsupportedMediaTypeStatusException.class,
			MissingRequestValueException.class,
			UnsatisfiedRequestParameterException.class,
			WebExchangeBindException.class,
			ServerWebInputException.class,
			ServerErrorException.class,
			ResponseStatusException.class,
			ErrorResponseException.class
	})
	public final Mono<ResponseEntity<Object>> handleException(Exception ex, ServerWebExchange exchange) {
		if (ex instanceof MethodNotAllowedException theEx) {
			return handleMethodNotAllowedException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof NotAcceptableStatusException theEx) {
			return handleNotAcceptableStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof UnsupportedMediaTypeStatusException theEx) {
			return handleUnsupportedMediaTypeStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof MissingRequestValueException theEx) {
			return handleMissingRequestValueException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof UnsatisfiedRequestParameterException theEx) {
			return handleUnsatisfiedRequestParameterException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof WebExchangeBindException theEx) {
			return handleWebExchangeBindException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof ServerWebInputException theEx) {
			return handleServerWebInputException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof ServerErrorException theEx) {
			return handleServerErrorException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof ResponseStatusException theEx) {
			return handleResponseStatusException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else if (ex instanceof ErrorResponseException theEx) {
			return handleErrorResponseException(theEx, theEx.getHeaders(), theEx.getStatusCode(), exchange);
		}
		else {
			if (logger.isWarnEnabled()) {
				logger.warn("Unexpected exception type: " + ex.getClass().getName());
			}
			return Mono.error(ex);
		}
	}

참고로 나는 지금 webflux로 작업중이기 때문에 이렇게 떳지만, mvc에도 동일한 클래스가 있으며, 리턴값만 차이난다.
그렇다. 웹 프레임워크가 던지는 예외를 미리 다 정의해 놓았다.(너무 편리하다.)

우리는 저기에 있는 protected로 선언되어있는 handle***메서드만 오버라이드 하면 된다.

profile
더 좋은 구조를 고민하는 개발자 입니다

0개의 댓글