[SpringBoot] Exception 예외 처리

suhwani·2024년 3월 25일
0
post-thumbnail

예외 처리


참고!!
[Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)

기본적인 동작 원리

WAS(톰캣) → 필터 → 서블릿 → 인터셉터 → 컨트롤러

별 다른 예외 처리가 없을 때, 동작

결국 컨트롤러를 2번 호출 하는 꼴

WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러
-> 컨트롤러(예외발생) -> 인터셉터 -> 서블릿(디스패처 서블릿) -> 필터 -> WAS(톰캣)
-> WAS(톰캣) -> 필터 -> 서블릿(디스패처 서블릿) -> 인터셉터 -> 컨트롤러(BasicErrorController)

효율적인 예외 처리

모든 코드에 예외 처리를 위해 try-catch 를 붙이는 건 비효율적!!
Spring 에서는 Exception 처리를 메인 로직으로부터 분리하여 HandlerExceptionResolver 사용 권장!!
HandlerExceptionResolver 는 발생한 Exception 을 catch 하고 HTTP 상태, 응답 메시지 등을 설정
WAS 입장에서는 해당 요청이 정상적인 응답으로 인식되며, WAS 의 에러 전달 발생 X

4가지 예외 처리 구현

DefaultErrorAttributes : 에러 속성을 저장하며 직접 예외를 처리하지는 않는다.

ExceptionHandlerExceptionResolver : 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리한다.

DefaultHandlerExceptionResolver :  스프링 내부의 기본 예외들을 처리한다.

ResponseStatusExceptionResolver : Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리한다.

4개 중 DefaultErrorAttributes 는 직접 예외처리X, 속성만 관리하므로 성격이 다르다. 나머지 3개는 ExceptionResolver 들은 HandlerExceptionResolverComposite 로 모아서 관리한다.
Spring 에서는 아래와 같은 도구를 이용해서 ExceptionResolver 를 동작시켜 에러를 처리한다.

Spring 에서 ExceptionResolver 를 동작시킬 때, 사용하는 도구는 아래와 같다.
ResponseStatus, ResponseStatusException, ExceptionHandler, RestControllerAdvice

에러처리 도구1. @ResponseStatus (추천X)

  • 에러 HTTP 상태를 변경하도록 도와주는 어노테이션이다.
  • 하지만, 에러 응답에서 볼 수 있듯이 BasicErrorController 에 의한 응답이다.
  • 즉, ResponseStatusExceptionResolver 는 WAS 까지 예외를 전달시킨다.
  • 에러 응답의 내용을 수정할 수 없다. (DefaultErrorAttributes 를 수정하면 가능하긴 하다.)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
  ...
}
{
    "timestamp": "2021-12-31T03:35:44.675+00:00",
    "status": 404,
    "error": "Not Found",
    "path": "/product/5000"
}

에러처리 도구2. ResponseStatusException (추천X)

  • HttpStatus + reason, cause 를 추가할 수 있다.
  • ResponseStatusExceptionResolver 가 에러를 처리한다.
  • 하지만, 직접적인 예외처리이기 때문에, 일관된 예외 처리를 하기 어렵다.
  • 예외 처리가 중복되고, 예외가 WAS 까지 전달되는 단점이 있다.
@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");
    }
}

에러처리 도구3. @ExceptionHandler (추천O)

  • 컨트롤러의 메서드, @RestControllerAdvice 가 있는 클래스의 메서드의 에러처리가 가능하다.
  • ExceptionHandlerExceptionResolver 에 의해 처리된다.
  • @ExceptionHandler는 Exception 클래스들을 속성으로 받아 처리할 예외를 지정할 수 있다.
    만약 ExceptionHandler 어노테이션에 예외 클래스를 지정하지 않는다면, 파라미터에 설정된 에러 클래스를 처리하게 된다.
  • 또한 @ResponseStatus와도 결합가능한데,  만약 ResponseEntity에서도 status를 지정하고 @ResponseStatus도 있다면 ResponseEntity가 우선순위를 갖는다.
  • ExceptionHandler는 @ResponseStatus와 달리 에러 응답(payload)을 자유롭게 다룰 수 있다는 점에서 유연하다. 예를 들어 응답을 다음과 같이 정의해서 내려준다면 좋을 것이다.
code: 어떠한 종류의 에러가 발생하는지에 대한 에러 코드
message: 왜 에러가 발생했는지에 대한 설명
erros: 어느 값이 잘못되어 @Valid에 의한 검증이 실패한 것인지를 위한 에러 목록
@RestController
@RequiredArgsConstructor
public class ProductController {

  private final ProductService productService;
  
  @GetMapping("/product/{id}")
  public Response getProduct(@PathVariable String id){
    return productService.getProduct(id);
  }

  @ExceptionHandler(NoSuchElementFoundException.class)
  public ResponseEntity<String> handleNoSuchElementFoundException(NoSuchElementFoundException exception) {
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
  }
}

에러처리 도구4. @RestControllerAdvice & @ControllerAdvice (추천O)

  • 둘의 차이는 응답 타입이다. RestControllerAdvice 는 Json, ControllerAdvice 는 HTML 응답을 한다.
  • ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용해준다.
  • 위에서 보이듯 ControllerAdvice 어노테이션에는 @Component 어노테이션이 있어서 ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록된다.
  • 그러므로 우리는 다음과 같이 전역적으로 에러를 핸들링하는 클래스를 만들어 어노테이션을 붙여주면 에러 처리를 위임할 수 있다.

내가 구현한 에러처리 방법


  • code : 에러 분류 정보를 담은 폴더
    • UserErrorCode : User 에 관한 에러를 분류해놓은 파일
  • exception : 예외 처리에 관한 정보를 담은 폴더
    • GlobalExceptionHandler
      : 모든 예외가 처음으로 오는 곳. Exception 을 분류해서 지정된 곳으로 보내서 처리한다.
    • UserException
      : UserException 을 처리하는 파일.
  • response : 응답 형태에 관한 정보를 담은 폴더
    • ErrorResponse : Error 에 대한 응답 포맷을 정리해놓은 파일
    • SuccessResponse : 200 응답 포맷을 정리해놓은 파일
./
├── DemoApplication.java
├── code/
│   └── UserErrorCode.java
├── controller/
│   └── UserApiController.java
├── dto/
│   ├── UserCreateRequestDto.java
│   └── UserUpdateRequestDto.java
├── exception/
│   ├── GlobalExceptionHandler.java
│   └── UserException.java
├── model/
│   └── UserEntity.java
├── repository/
│   └── UserRepository.java
├── response/
│   ├── ErrorResponse.java
│   └── SuccessResponse.java
└── service/
    └── UserService.java
profile
Backend-Developer

0개의 댓글