프로젝트를 진행하면서 많은 예외처리가 필요로 했다.
그래서 예전에 공부하였던 스프링 API 예외처리에 대해서 복습을 하면서 다시 한번 정리해보려 한다.
HTML 에러 화면을 처리할때는
기본적으로 스프링 부트가 제공하는 ErrorPage
, BasicErrorController
를 사용하면
에러를 전달받은 WAS
가 오류 페이지 정보를 찾을때 필요한 기능들을 개발자가 별도로 작성하지 않아도
자동으로 HTML,JSON 형태로 편하게 처리해준다.
API 오류에 대해서 BasicErrorController
을 확장하여 JSON 오류 메시지를 변경할수 있지만 한계가 있다.
서로 다른 API에서 예외가 발생할수 있고 그에 따라 다른 JSON 응답 메시지가 출력으로 요구되어질수 있다.
그렇기 때문에 각각의 컨트롤러나 예외마다 서로 다른 JSON 오류 메시지를 효율적으로 처리할수 있는 방법이 필요하다.
컨트롤러 밖으로 예외가 처리되지 않고 나간 경우
서블릿
을 넘어WAS
까지 예외가 발생하게 된다.그럼
WAS
는 서버내부에서 발생하였기 때문에 HTTP 상태코드를 500으로 바꾸고
WAS
는 오류를 처리하기 위해필터
,서블릿
,인터셉터
,BasicErrorController
를 호출하게 된다.
BasicErrorController
는 HTML, JSON 형식에 맞게 에러를 처리하게 된다.그림으로 아래와 같이 메커니즘이 동작하게 된다.
❗참고로
WAS
가 오류 처리를 위해 필터,서블릿이 다시 호출하는것은 비효율적이다.
그렇기 때문에서블릿
은 이러한 경우를 위해서DispatcherType
을 이용하여
필터
가 에러처리 요청이 아닌 클라이언트 요청시에만 동작하도록 기능을 제공한다.또한
인터셉터
는서블릿
이 제공하는 기능이 아닌스프링
이 제공하는 기능이기에 위의 기능을 사용하지 못한다.
그래서excludePathPatterns
을 통해 오류 페이지 경로인/error
제외하는 식으로 사용하게 된다.
그림으로 한번더 살펴 보자면 아래와 같다.
postHadler
호출이 안됨WAS
까지 에러 전달!!WAS
에러 처리 메커니즘 동작위와 같은 문제를 해결하기 위해 스프링 MVC
는 컨트롤러 밖으로 던져진 예외를 해결하고
동작을 새로 정의(정상흐름으로)할 수 있도록 방법을 제공한다.
그것이 ExceptionReslover
이다.
그림을 통해 동작 매커니즘을 살펴보겠다.
postHadler
은 마찬가지로 호출안됨!DispaterServlet
이 등록된 ExceptionResolver
중에 에러를 처리할수 있는지 확인!ExceptionResolver
가 상황에 맞게 처리
빈 ModelAndView 반환시
: 뷰 랜더링을 하지 않고 서블릿에게 정상흐름으로 전달ModelAndView 지정
: 뷰 랜더링 진행null
: 다음ExceptionResolver
를 찾게 된다. 만약 없을시 그대로 예외가WAS
로 전달된다.API 처리
: HTTP 바디에 데이터를 넣어 JSON으로 응답 처리 가능
간단하게 동작하는 방식을 알아보았다.
ExceptionReslover
을 직접 구현해 사용하는 방식이 있지만
스프링
이 제공하는 ExceptionReslover
을 알아보겠다.
스프링 부트
가 기본으로 스프링
에 3개의 ExceptionResolver
을 등록해준다.
실제 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
에서 오류페이지 요청 없이 정상흐름으로 종료된다!!HTTP 상태코드를 지정해주는 역할을 한다.
예) @ResponseStatus(value = HttpStatus.NOT_FOUND)
내부적으로 response.sendError()
사용하기 때문에 WAS
에서 다시 오류 페이지 /error
를 호출한다. 즉 예외를 완벽히 처리한것이 아니다!
TypeMismatchException
이 있다.DefaultHandlerExceptionResolver
가 HTTP 상태코드를 400오류로 바꾸어준다.response.sendError()
를 호출하기 때문에 WAS
에서 다시 오류 페이지 /error
를 호출한다. 즉 예외를 완벽히 처리한것이 아니다!@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) {
...
}
}
추가로 패키지도 지정할수 있다. 하위 패키지도 모두 적용된다.