본문 바로가기
Spring

스프링 예외처리

by dharana7723 2024. 7. 13.

스프링은 아래와 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있는데
각각의 방식에 대해 자세히 살펴보도록 하자
 
ResponseStatus
ResponseStatusException
ExceptionHandler
ControllerAdvice, RestControllerAdvice
 
 
 
@ReponseStatus
어노테이션 이름에서 예측 가능하듯이 @ReponseStatus 는 에러 HTTP 상태를 변경하도록
도와주는 어노테이션이다.
@ReponseStatus는 다음과 같은 경우들에 적용할 수 있다.
 
  • Exception 클래스 그 자체
  • 메소드에 @ExceptionHandler와 함께
  • 클래스에 @RestControllerAdvice와 함께
 
예를 들어 우리가 만든 예외 클래스에 다음과 같이 @ReponseStatus로
응답 상태를 지정해 줄 수 있다.
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundExceptoin extends RuntimeException {
….
}
 
ResponseStatusExceptionResolver가 지정해준 상태로 에러 응답이 내려가도록
처리한다.
 
하지만 이는 에러 응답에서 볼 수 있듯이 이는
BasicErrorController에 의한 응답이다.
즉 @ResponseStatus를 처리하는 ResponseStatusExceptionResolver는
WAS까지 예외를 전달시키며
복잡한 WAS 의 에러 요청 전달이 진행되는 것이다.
이러한 @ResponseStatus는 다음과 같은 한계점들을 가지고 있다.
  • 에러 응답의 내용(페이로드)를 수정할 수 없음(DefaultErrorAttributes를 수정하면 가능하긴함)
  • 예외 클래스와 강하게 결합되어 같은 예외는 같은 상태와 에러 메시지를 반환함
  • 별도의 응답 상태가 필요하다면 예외 클래스를 추가해야됨
  • WAS까지 예외가 전달되고 WAS의 에러 요청 전달이 진행됨
  • 외부에서 정의한 Exception 클래스에는 @ResponseStatus를 붙여줄 수 없음
 
 
ResponseStatusException
외부 라이브러리에서 정의한 코드는 우리가 수정할 수 없으므로
@ReponseStatus를 붙여줄 수 없다.
Spring5에는 @ReponseStatus의 프로그래밍적 대안으로서 손쉽게 에러를 반환할 수 있는
ResponseStatusException가 추가되었다.
ResponseStatusException는 HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수
있고, 언체크 예외를 상속받고 있어 명시적으로 에러를 처리해주지 않아도 된다.
이러한 ResponseStatusException은 다음과 같이 사용할 수 있다.
 
@GetMapping(“/product/{id}”)
public ResponseEntity<Product> getProduct(@PathVariable String id) {
try {
return ResponseEntity.ok(productService.getProduct(id));
} catch (NoSuchElementFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, “Item Not Found”);
}
}
 
@ResponseStatus와 동일하게 예외가 발생하면 ResponseStatusExceptionResolver가 예외를 처리히한다.
ResponseStatusException을 사용하면 다음과 같은 이점을 누릴 수 있다.
  • 기본적인 예외 처리를 빠르게 적용할 수 있으므로 손쉽게 프로토타이핑할 수 있음
  • HttpStatus를 직접 설정하여 예외 클래스와의 결합도를 낮출 수 있음
  • 불필요하게 많은 별도의 예외 클래스를 만들지 않아도 됨
  • 프로그래밍 방식으로 예외를 직접 생성하므로 예외를 더욱 잘 제어할 수 있음
 
 

 

하지만 그럼에도 불구하고 ResponseStatusException는 다음과 같은 한계점들을 가지고 있다. 이러한 이유로 API 에러 처리를 위해서는 @ExceptionHandler를 사용하는 방식이 더 많이 사용된다.

  • 직접 예외 처리를 프로그래밍하므로 일관된 예외 처리가 어려움
  • 예외 처리 코드가 중복될 수 있음
  • Spring 내부의 예외를 처리하는 것이 어려움
  • 예외가 WAS까지 전달되고, WAS의 에러 요청 전달이 진행됨

 
@ExceptionHandler는 매우 유연하게 에러를 처리할 수 있는 방법을 제공하는
기능이다.
@ExceptinoHandler는
다음에 어노테이션을 추가함으로서 에러를 손쉽게 처리할수 있다.
  • 컨트롤러의 메소드
  • @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
 
예를 들어 다음과 같이 컨트롤러의 메소드에 @ExceptionHandler를 추가함으로서
에러를 처리할 수 있다. @ExceptionHandler에 의해 발생한 예외는
ExceptionHandlerExceptionResolver에 의해 처리가 된다.
 
 
@ExceptionHandler를 사용시에 주의할 점은
@ExceptionHandler에 등록된 예외클래스와 파라미터로 받는 예외클래스가 동일해야 한다는 것이다.
만약 값이 다르다면 스프링은 컴파일 시점에 에러를 내지 않다가 런타임 시점에 에러를 발생시킨다.
 
ExceptionHandler의 파라미터로 HttpSErvletRequest나 WebRequest등을 얻을 수 있으며
반환 타입으로는 ResponseEntity, String, void 등 자유롭게 활용할 수 있다.
(더많은 입력/반환 타입을 위해서는 공식문서 참고)
 
ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를
적용해준다. 위에서 보이듯 ControllerAdvice 어노테이션에는 @Component 어노테이션이
있어 ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록된다.
그러므로 우리는 다음과 같이 전역적으로 에러를 핸들링 하는 클래스를 만들어
어노테이션을 붙여주면 에러 처리를 위임할 수 있다.
 
 

이러한 이유로 API에 의한 예외 처리를 할 때에는 ControllerAdvice를 이용하면 평가된다. 하지만 ControllerAdvice를 사용할 때에는 항상 다음의 내용들을 주의해야 한다. 여러 ControllerAdvice가 있을 때 @Order 어노테이션으로 순서를 지정하지 않는다면 Spring은 ControllerAdvice를 임의의 순서로 처리할 수 있으므로 일관된 예외 응답을 위해서는 이러한 점에 주의해야 한다.

  • 한 프로젝트당 하나의 ControllerAdvice만 관리하는 것이 좋다.
  • 만약 여러 ControllerAdvice가 필요하다면 basePackages나 annotations 등을 지정해야 한다.
  • 직접 구현한 Exception 클래스들은 한 공간에서 관리한다.

 

참조: https://mangkyu.tistory.com/204