이전 포스트에서 Response를 객체에 담아 반환하도록 변경하는 과정에서, 예외처리를 하지 않은 상황에서 에러가 발생할 경우 위와 같이 나타남을 확인했었다.
대신, 에러의 원인에 따른 응답을 반환해줄 필요가 있다고 했는데, 오늘은 그 방법에 대해 알아보려고 한다.
스프링부트에서는 @ExceptionHandler
라는 예외 처리 기능을 제공한다.
@ExceptionHandler 는 해당 어노테이션이 선언된 클래스에 대해 오류가 발생할 경우,
그에 대한 대응을 정의할 수 있도록 도와준다.
User
엔티티에 대해 특정 id로 조회 쿼리를 요청하려고 한다.
해당 User가 존재하지 않는 경우 "해당하는 사용자가 없습니다" 라는 에러를 반환하도록 수정해보자.
우선, 여러 예외들을 관리할 exception 패키지를 하나 만들어주자.
패키지 내부에 InvalidateUserException
라는 RuntimeException 을 상속한 에러 클래스를 생성한다.
본인에게 필요한 예외에 맞는 이름을 직접 지정해 생성하면 된다.
다음으로, 에러 코드를 관리할 Enum 클래스인 Exception
클래스를 생성하고, 멤버변수로 code
와 message
를 지정해주었다.
추가로 반환하고 싶은 값이 있다면 멤버변수로 추가해주면 된다.
여기서 사용할 Exception은 INVALIDAE_USER
이다. code를 1000으로(임의설정), message는 예외처리시 사용할 문구로 설정해주었다.
이제, 해당 예외가 발생할 경우 반환값으로 무엇을 내보낼지 설정해주어야 한다.
ResponseService
클래스 내에 에러가 발생할 경우 그에 맞는 code와 message를 내보내줄 getErrorResponse
메서드를 새로 생성한다.
( ** CommonResponse 는 사용자정의 클래스임! 생성하는 이유 및 방법은 이 글을 참고하세유)
이 경우, 위의 CommonResponse
를 상속받은 또 다른 클래스(ErrorResponse 등)를 새로 정의할 수도 있다.
하지만 에러 응답값이 위의 필드들 ( 성공여부, 코드, 메세지 ) 만으로 이루어져도 무방하다면 그대로 CommonResponse를 반환하도록 해도 상관 없다.
이제, 예외가 발생할 수 있는 곳에서 우리가 생성해둔 xxxException 예외를 던져준다.
BookService
의 getBooksByUser
메서드 내에서, userId를 찾지 못할 경우 InvalidateUserException을 던지도록 수정해주었다.
그리고, 같은 클래스 내에 @ExceptionHandler
을 선언한 에러 반환 메서드를 추가한다.
@ExceptionHandler
내에는 가로챌 예외 클래스, xxxException.class를 넣어주면 된다.
즉, 우리가 방금 예외 발생시 반환하기 위해 생성한 getErrorResponse 를 이곳에 넣어주는 것이다.
그 인자로 역시 우리가 설정한 code와 message를 넣어준다.
이제 이 핸들러가 있는 위치인 BookService 에서 InvalidateUser 예외가 발생하면 -> ExceptionHandler가 이를 가로채고 -> 핸들러 아래에 정의된 invalidateUserException 메서드가 실행되는 것이다.
api 호출을 통해 확인해보자.
데이터베이스에 존재하지 않는 id=3 으로 조회 api를 호출한다. 다음과 같이 우리가 정의한 형식대로 에러를 반환받을 수 있다.
하지만, @ExceptionHandler는
해당 어노테이션이 선언된 클래스 내에서만 유효
하다고 했다.
즉, 해당 에러가 발생할 수 있는 클래스들에 대해 반복적으로 핸들러를 설정해주어야 한다는 큰 단점이 있다.
-> 특정 클래스 내에서만 발생하는 예외에만 사용하자!
이를 보완할 수 있는 방법이 @RestControllerAdvice
를 사용하는 것이다.
@RestControllerAdvice
는 프로젝트의 전역에서 발생할 수 있는 에러에 대한 처리를 도와준다.
사용법은 간단하다.다음과 같이 모든 예외들을 모아두고 처리할 ExceptionController
클래스를 생성하고,
@RestControllerAdvice
어노테이션을 붙여준다.
예시로 든 InvalidateUserException
이외에도, 여러 상황에 대해 발생할 수 있는 에러들을 이 클래스에 모아 관리할 수 있으며, 서비스단에서는 상황에 따라 에러를 던져주기만 하면 된다.
동시에, @ResponseStatus(HttpStatus.XXX)
어노테이션으로 응답할 Status를 지정도 가능하다.
위와 같이 HttpStatus.NOT_FOUND
로 설정할 경우, 기존의 500 에러 대신 404(NotFound) 에러가 발생하도록 설정할 수 있다.
기존의 Service 클래스 내에 선언했던 핸들러를 삭제한 후, 다시 한 번 동일한 api를 호출해보자. 위와 같이 우리가 지정한 Status가 잘 반환된다.
대부분의 에러는 전역에서 발생할 수 있는 경우가 많다.
따라서 각 클래스 내에 반복적으로 핸들러를 등록하기보다는, @RestControllerAdvice 를 이용해 가능한 에러들을 모아 관리하는 것이 효율적이다.
정리하자면,