본 프로젝트 자료는 김영한님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 참고 제작됐음을 알립니다.
스프링 부트가 기본으로 제공하는 ExceptionResolver 는 다음과 같다.
먼저 가장 쉬운 ResponseStatusExceptionResolver 부터 알아보자.
ResponseStatusExceptionResolver 는 예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다.
다음 2가지 겨우를 처리하는데,
BadRequestException - 신규 클래스 생성
예외에 다음과 같이 @ResponseStatus 애노테이션을 적용하면 HTTP 상태 코드를 변경해준다.
BadRequestException 예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver 예외가 해당 애노테이션을 확인해서 오류 코드를 HttpStatus.BAD_REQUEST (400)으로 변경하고, 메시지도 담는다.
ApiExceptionController - 추가
실행 결과
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "잘못된 요청 오류",
"path": "/api/response-status-ex1"
}
BadRequestException - 수정
reason 을 MessageSource 에서 찾는 기능도 제공한다. reason = "error.bad"
messages.properties - 프로퍼티스 신규 생성
PostMan 출력 결과
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "잘못된 요청 오류입니다. 메시지 사용",
"path": "/api/response-status-ex1"
}
@ResponseStatus 는 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다. (애노테이션을 직접 넣어야 하는데, 내가 코드를 수정할 수 없는 라이브러리의 예외 코드 같은 곳에는 적용할 수 없다.)
추가로 애노테이션을 사용하기 때문에 조건에 따라 동적으로 변경하는 것도 어렵다. 이때는 ResponseStatusException 예외를 사용하면 된다.
ApiExceptionController - 추가
PostMan 출력 결과
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "잘못된 요청 오류입니다. 메시지 사용",
"path": "/api/response-status-ex1"
}
DefaultHandlerExceptionResolver 를 알아보자.
ApiExceptionController - 추가
Integer data 에 문자를 입력하면 내부에서 TypeMismatchException 이 발생한다.
실행 결과
실행 결과를 보면 HTTP 상태 코드가 400인 것을 확인할 수 있다.
웹 브라우저에 오류가 발생했을 때 BasicErrorController 를 사용해 단순한 방식으로 5xx, 4xx 관련된 오류 화면을 보여준다.. BasicErrorController 는 이런 메커니즘으로 동작한다.
하지만 API 는 각 시스템 마다 하나하나 전부 출력해야하는 번거로움이 있다. 그리고 같은 예외라고 해도 어떤 컨트롤러에서 발생했는가에 따라서 다른 예외 응답을 내려주어야 할 수 있다.
지금까지 살펴봤을 때 BasicErrorController 를 사용하거나 HandlerExceptionResolver 를 직접 구현하는 방식으로 API 예외를 다루기는 쉽지 않다. 복잡하고 번거롭고 혹은 단순하게 만들 수 있으나 세부사항을 입력하려면 여러가지 로직을 추가야 해야 하는 등...
스프링에서 이런 단점들을 보안해서 나온게 @ExceptionHandler 이다.
스프링은 API 예외 처리 문제를 해결하기 위해 @ExceptionHandler 라는 애노테이션을 사용하는 매우 편리한 예외 처리 기능을 제공하는데, 이것이 바로 ExceptionHandlerExceptionResolver 이다.
스프링은 ExceptionHandlerExceptionResolver 를 기본으로 제공하고, 기본으로 제공하는 ExceptionResolver 중에 우선순위도 가장 높다. 실무에서 API 예외 처리는 대부분 이 기능을 사용한다.
예제를 통해 어떤 식으로 흘러가는지 확인해보자.
ErrorResult - 신규 클래스 생성
예외가 발생했을 때 API 응답으로 사용하는 객체를 정의했다.
ApiExceptionV2Controller - 신규 클래스 생성
스프링의 우선순위는 항상 자세한 것이 우선권을 가진다. 예를 들어서 부모, 자식 클래스가 있고 다음과 같이 예외가 처리된다.
다음과 같이 다양한 예외를 한번에 처리할 수 있다.
@ExceptionHandler 에 예외를 생략할 수 있다. 생략하면 메서드 파라미터의 예외가 지정된다.
@ExceptionHandler 에는 마치 스프링의 컨트롤러의 파라미터 응답처럼 다양한 파라미터와 응답을 지정할 수 있다.
bad - 실행 결과
위와 같이 출력된다면 오류 없이 제대로 처리된 것 이다.
@ControllerAdvice 또는 @RestControllerAdvice 를 사용해 정상 코드와 예외 처리 코드가 하나의 컨트롤러에 있는걸 깔끔하게 둘로 나눌 수 있다.
ApiExceptionV2Controller 코드에 있는 @ExceptionHandler 코드 복사 후 모두 제거
복사한 @ExceptionHandler 코드 새로 생성한 ExControllerAdvice 클래스에 복사 붙이기
이대로 실행해서 정상작동 하는지 테스트.
마지막으로 글로벌로 테스트할 수 있는지도 확인
ApiExceptionV2Controller 복사 후 ApiExceptionV3Controller 신규 생성
ExControllerAdvice - 수정
@RestControllerAdvice(basePackages = "hello.exception.api") 만 추가해주고 작동하는지 테스트.
정상적으로 작동한다. 끝