[스프링부트] 예외 처리하기(@ExceptionHandler , @RestControllerAdvice )

minji·2021년 10월 18일
2
post-thumbnail

👻 들어가며

이전 포스트에서 Response를 객체에 담아 반환하도록 변경하는 과정에서, 예외처리를 하지 않은 상황에서 에러가 발생할 경우 위와 같이 나타남을 확인했었다.

대신, 에러의 원인에 따른 응답을 반환해줄 필요가 있다고 했는데, 오늘은 그 방법에 대해 알아보려고 한다.


1️⃣ @ExceptionHandler 사용하기

스프링부트에서는 @ExceptionHandler 라는 예외 처리 기능을 제공한다.
@ExceptionHandler 는 해당 어노테이션이 선언된 클래스에 대해 오류가 발생할 경우,
그에 대한 대응을 정의할 수 있도록 도와준다.

User 엔티티에 대해 특정 id로 조회 쿼리를 요청하려고 한다.
해당 User가 존재하지 않는 경우 "해당하는 사용자가 없습니다" 라는 에러를 반환하도록 수정해보자.

1-1. 예외 클래스 생성

우선, 여러 예외들을 관리할 exception 패키지를 하나 만들어주자.
패키지 내부에 InvalidateUserException 라는 RuntimeException 을 상속한 에러 클래스를 생성한다.
본인에게 필요한 예외에 맞는 이름을 직접 지정해 생성하면 된다.

1-2. Enum 클래스 생성

다음으로, 에러 코드를 관리할 Enum 클래스인 Exception 클래스를 생성하고, 멤버변수로 codemessage를 지정해주었다.
추가로 반환하고 싶은 값이 있다면 멤버변수로 추가해주면 된다.
여기서 사용할 Exception은 INVALIDAE_USER 이다. code를 1000으로(임의설정), message는 예외처리시 사용할 문구로 설정해주었다.

1-3. 에러 응답 메서드 설정

이제, 해당 예외가 발생할 경우 반환값으로 무엇을 내보낼지 설정해주어야 한다.

ResponseService 클래스 내에 에러가 발생할 경우 그에 맞는 code와 message를 내보내줄 getErrorResponse 메서드를 새로 생성한다.

( ** CommonResponse 는 사용자정의 클래스임! 생성하는 이유 및 방법은 이 글을 참고하세유)

이 경우, 위의 CommonResponse를 상속받은 또 다른 클래스(ErrorResponse 등)를 새로 정의할 수도 있다.

하지만 에러 응답값이 위의 필드들 ( 성공여부, 코드, 메세지 ) 만으로 이루어져도 무방하다면 그대로 CommonResponse를 반환하도록 해도 상관 없다.

1-4. 예외 처리하기

이제, 예외가 발생할 수 있는 곳에서 우리가 생성해둔 xxxException 예외를 던져준다.

  • BookService

BookServicegetBooksByUser 메서드 내에서, userId를 찾지 못할 경우 InvalidateUserException을 던지도록 수정해주었다.

그리고, 같은 클래스 내에 @ExceptionHandler을 선언한 에러 반환 메서드를 추가한다.
@ExceptionHandler 내에는 가로챌 예외 클래스, xxxException.class를 넣어주면 된다.
즉, 우리가 방금 예외 발생시 반환하기 위해 생성한 getErrorResponse 를 이곳에 넣어주는 것이다.
그 인자로 역시 우리가 설정한 code와 message를 넣어준다.

이제 이 핸들러가 있는 위치인 BookService 에서 InvalidateUser 예외가 발생하면 -> ExceptionHandler가 이를 가로채고 -> 핸들러 아래에 정의된 invalidateUserException 메서드가 실행되는 것이다.


api 호출을 통해 확인해보자.
데이터베이스에 존재하지 않는 id=3 으로 조회 api를 호출한다. 다음과 같이 우리가 정의한 형식대로 에러를 반환받을 수 있다.

하지만, @ExceptionHandler는 해당 어노테이션이 선언된 클래스 내에서만 유효하다고 했다.
즉, 해당 에러가 발생할 수 있는 클래스들에 대해 반복적으로 핸들러를 설정해주어야 한다는 큰 단점이 있다.
-> 특정 클래스 내에서만 발생하는 예외에만 사용하자!

이를 보완할 수 있는 방법이 @RestControllerAdvice 를 사용하는 것이다.


2️⃣ @RestControllerAdvice 사용하기

@RestControllerAdvice프로젝트의 전역에서 발생할 수 있는 에러에 대한 처리를 도와준다.
사용법은 간단하다.다음과 같이 모든 예외들을 모아두고 처리할 ExceptionController 클래스를 생성하고,
@RestControllerAdvice 어노테이션을 붙여준다.

예시로 든 InvalidateUserException 이외에도, 여러 상황에 대해 발생할 수 있는 에러들을 이 클래스에 모아 관리할 수 있으며, 서비스단에서는 상황에 따라 에러를 던져주기만 하면 된다.

동시에, @ResponseStatus(HttpStatus.XXX) 어노테이션으로 응답할 Status를 지정도 가능하다.
위와 같이 HttpStatus.NOT_FOUND 로 설정할 경우, 기존의 500 에러 대신 404(NotFound) 에러가 발생하도록 설정할 수 있다.

기존의 Service 클래스 내에 선언했던 핸들러를 삭제한 후, 다시 한 번 동일한 api를 호출해보자. 위와 같이 우리가 지정한 Status가 잘 반환된다.

대부분의 에러는 전역에서 발생할 수 있는 경우가 많다.
따라서 각 클래스 내에 반복적으로 핸들러를 등록하기보다는, @RestControllerAdvice 를 이용해 가능한 에러들을 모아 관리하는 것이 효율적이다.

정리하자면,

  • @RestControllerAdvice 가 선언된 클래스를 생성, 핸들러를 모아 정의한다.
  • 각 예외들의 고유한 코드, 메세지 (+ 또다른 원하는 반환값) 는 Enum 클래스에 모아 관리한다.
  • 예외가 발생할 수 있는 곳에서 원하는 예외를 던져주기만 하면, 그에 맞는 핸들러가 동작할 것이다.
profile
SW Engineer

0개의 댓글