스프링 API 예외처리

구본식·2023년 1월 28일
2
post-thumbnail

프로젝트를 진행하면서 많은 예외처리가 필요로 했다.
그래서 예전에 공부하였던 스프링 API 예외처리에 대해서 복습을 하면서 다시 한번 정리해보려 한다.

HTML 에러 화면을 처리할때는
기본적으로 스프링 부트가 제공하는 ErrorPage, BasicErrorController를 사용하면
에러를 전달받은 WAS가 오류 페이지 정보를 찾을때 필요한 기능들을 개발자가 별도로 작성하지 않아도
자동으로 HTML,JSON 형태로 편하게 처리해준다.

API 오류에 대해서 BasicErrorController을 확장하여 JSON 오류 메시지를 변경할수 있지만 한계가 있다.

서로 다른 API에서 예외가 발생할수 있고 그에 따라 다른 JSON 응답 메시지가 출력으로 요구되어질수 있다.

그렇기 때문에 각각의 컨트롤러나 예외마다 서로 다른 JSON 오류 메시지를 효율적으로 처리할수 있는 방법이 필요하다.


1. HandlerExceptionResolver (ExceptionReslover)

⏺ ExceptionReslover가 없는 경우

컨트롤러 밖으로 예외가 처리되지 않고 나간 경우 서블릿을 넘어 WAS 까지 예외가 발생하게 된다.

그럼 WAS는 서버내부에서 발생하였기 때문에 HTTP 상태코드를 500으로 바꾸고
WAS는 오류를 처리하기 위해 필터,서블릿,인터셉터,BasicErrorController를 호출하게 된다.
BasicErrorController는 HTML, JSON 형식에 맞게 에러를 처리하게 된다.

그림으로 아래와 같이 메커니즘이 동작하게 된다.

❗참고로 WAS가 오류 처리를 위해 필터,서블릿이 다시 호출하는것은 비효율적이다.
그렇기 때문에 서블릿은 이러한 경우를 위해서 DispatcherType을 이용하여
필터가 에러처리 요청이 아닌 클라이언트 요청시에만 동작하도록 기능을 제공한다.

또한 인터셉터서블릿이 제공하는 기능이 아닌 스프링이 제공하는 기능이기에 위의 기능을 사용하지 못한다.
그래서 excludePathPatterns을 통해 오류 페이지 경로인 /error제외하는 식으로 사용하게 된다.

그림으로 한번더 살펴 보자면 아래와 같다.

  1. 컨트롤러에서 에러 발생. 예외 전달~
  2. 스프링 인터셉터의 postHadler 호출이 안됨
  3. WAS까지 에러 전달!!
  4. 추후 WAS에러 처리 메커니즘 동작

⏺ ExceptionReslover가 있는 경우

위와 같은 문제를 해결하기 위해 스프링 MVC는 컨트롤러 밖으로 던져진 예외를 해결하고
동작을 새로 정의(정상흐름으로)
할 수 있도록 방법을 제공한다.

그것이 ExceptionReslover 이다.

그림을 통해 동작 매커니즘을 살펴보겠다.

  1. 컨트롤러에서 에러 발생. 예외 전달~
  2. 스프링 인터셉터의 postHadler은 마찬가지로 호출안됨!
  3. DispaterServlet이 등록된 ExceptionResolver 중에 에러를 처리할수 있는지 확인!
  4. 선택된 ExceptionResolver가 상황에 맞게 처리
    • 빈 ModelAndView 반환시: 뷰 랜더링을 하지 않고 서블릿에게 정상흐름으로 전달
    • ModelAndView 지정 : 뷰 랜더링 진행
    • null : 다음 ExceptionResolver를 찾게 된다. 만약 없을시 그대로 예외가 WAS로 전달된다.
    • API 처리 : HTTP 바디에 데이터를 넣어 JSON으로 응답 처리 가능

간단하게 동작하는 방식을 알아보았다.

ExceptionReslover을 직접 구현해 사용하는 방식이 있지만
스프링이 제공하는 ExceptionReslover을 알아보겠다.


2. 스프링이 제공하는 ExceptionResolver

스프링 부트가 기본으로 스프링에 3개의 ExceptionResolver을 등록해준다.

1. ExceptionHandlerExceptionResolver

  • 실제 API의 예외처리에 주로 사용된다.

  • @ExceptionHandler 어노테이션이 붙어 있으면 ExceptionHandlerExceptionResolver가 동작하게 된다.

  • ExceptionResolver중 우선순위가 가장 높다.

  • ex)

    @RestController
      public class ApiExceptionV2Controller {
         @ResponseStatus(HttpStatus.BAD_REQUEST)
         @ExceptionHandler(IllegalArgumentException.class)
         public ErrorResult illegalExHandle(IllegalArgumentException e) {
           log.error("[exceptionHandle] ex", e);
           return new ErrorResult("BAD", e.getMessage());
       }
    }
    • 해당 컨트롤러에 처리하고 싶은 예외를 지정해주면된다.
    • 해당 컨트롤러에서 예외 발생시 메서드가 호출된다.
    • @RestController에 의해 HTTP body에 데이터가 쓰여지고 @ResponseStatus로 지 정한 상태코드로 설정된다.
    • 오류를 완벽처리하기 때문에 WAS에서 오류페이지 요청 없이 정상흐름으로 종료된다!!

2. ResponseStatusExceptionResolve

  • HTTP 상태코드를 지정해주는 역할을 한다.

  • 예) @ResponseStatus(value = HttpStatus.NOT_FOUND)

  • 내부적으로 response.sendError() 사용하기 때문에 WAS에서 다시 오류 페이지 /error를 호출한다. 즉 예외를 완벽히 처리한것이 아니다!

3. DefaultHandlerExceptionResolver

  • 스프링 내부에서 발생하는 예외를 처리해준다.
  • 대표적으로 파라미터 바인딩 오류인 TypeMismatchException이 있다.
    이러한 오류는 클라이언트 측에서 대부분은 잘못한것이기 때문에 DefaultHandlerExceptionResolver가 HTTP 상태코드를 400오류로 바꾸어준다.
  • 또한 response.sendError()를 호출하기 때문에 WAS에서 다시 오류 페이지 /error를 호출한다. 즉 예외를 완벽히 처리한것이 아니다!

3. @ControllerAdvice

@ExceptionHandler사용하여 예외를 정상적으로 처리할수 있었는데
컨트롤러에 정상코드, 예외처리코드가 같이 있었다.

@ControllerAdvice 또는 @RestControllerAdvice 를 사용하며 이를 분리할수 있다.

또한 @ControllerAdvice에 적용할 대상을 지정할수 있다.
(만약 적용하지 않으면 모든 컨트롤러에 대해서 처리)

아래는 예시이다.

// 직접 적용할 클래스 지정
@RestControllerAdvice(basePackageClasses = UserController.class)
public class UserExControllerAdvice {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(DuplicationUserNickname.class)
    public DataErrorResult<ValidNickNameDto> DuplicationUserNickname(DuplicationUserNickname e) {
       ...
    }
}

// 애노테이션도 지정 가능
@RestControllerAdvice(annotations = RestController.class)
public class UserExControllerAdvice {

   @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(AlreadyJoinedUser.class)
    public BaseErrorResult AlreadyJoinedUser(AlreadyJoinedUser e) {
        ...
    }
}

추가로 패키지도 지정할수 있다. 하위 패키지도 모두 적용된다.

profile
백엔드 개발자를 꿈꾸며 기록중💻

0개의 댓글