본문 바로가기
❤️‍🔥TIL (Today I Learned)

[TIL] 2023-01-10(52day)

by elicho91 2023. 1. 10.

Exception 처리(2)


👉  스프링이 제공하는 다양한 예외처리 방법

# Spring은 에러 처리라는 공통 관심사(cross-cutting concerns)를 메인 로직으로부터 분리하여 예외 처리 전략을 추상화한 HandlerExceptionResolver 인터페이스를 만들었다. 

// Object 타입인 handler는 예외가 발생한 컨트롤러 객체

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex);
}

 

 - 발생 위치 : 시스템 레벨에서 생성

 - 발생 클래스 : java.lang.Error 클래스의 서브 클래스

 

 

# HandlerExceptionResolver가 빈으로 등록해서 관리하는 4가지 구현체

 - DefaultErrorAttributes : 에러 속성을 저장하며 직접 예외를 처리하지는 않는다.
 - ExceptionHandlerExceptionResolver : 에러 응답을 위한 Controller나 ControllerAdvice에 있는 ExceptionHandler를 처리함
 - ResponseStatusExceptionResolver : Http 상태 코드를 지정하는 @ResponseStatus 또는 ResponseStatusException를 처리함
 - DefaultHandlerExceptionResolver : 스프링 내부의 기본 예외들을 처리.

 

 

# Spring은 아래와 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있다.

 - @ResponseStatus :

    에러 HTTP 상태를 변경하도록 도와주는 어노테이션

  • Exception 클래스 자체
  • 메소드에 @ExceptionHandler와 함께
  • 클래스에 @RestControllerAdvice와 함께
// 이 @ResponseStatus로 응답 상태를 지정해줄 수 있다.

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NoSuchElementFoundException extends RuntimeException {
  ...
}
// 그러면 ResponseStatusExceptionResolver가 지정해준 상태로 에러 응답이 내려가도록 처리한다.

{
    "timestamp": "2021-12-31T03:35:44.675+00:00",
    "status": 404,
    "error": "Not Found",
    "path": "/product/5000"
}

 

 - ResponseStatusException :

    HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수 있고, 언체크 예외을 상속받고 있어 명시적으로 에러를 처리해주지 않아도 된다.

@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");
    }
}

 

 - @ExceptionHandler  :

    다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리.

  • 컨트롤러의 메소드
  • @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
// 컨트롤러의 메소드에 @ExceptionHandler를 추가함으로써 에러를 처리. 
// @ExceptionHandler에 의해 발생한 예외는 ExceptionHandlerExceptionResolver에 의해 처리.

@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());
  }
}

 

 - @ControllerAdvice와 @RestControllerAdvice : 

    다음에 어노테이션을 추가함으로써 에러를 손쉽게 처리.

  • 컨트롤러의 메소드
  • @ControllerAdvice나 @RestControllerAdvice가 있는 클래스의 메소드
// @ControllerAdvice는 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    ...
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
// @Component 어노테이션이 있어서 @ControllerAdvice가 선언된 클래스는 스프링 빈으로 등록
public @interface ControllerAdvice {
    ...
}
// 특정 클래스에만 제한적으로 적용하고 싶다면 @RestControllerAdvice의 basePackages 등을 설정함으로써 제한할 수 있다.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NoSuchElementFoundException.class)
    protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
        final ErrorResponse errorResponse = ErrorResponse.builder()
                .code("Item Not Found")
                .message(e.getMessage()).build();

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
    }
}

🙋‍♂️ 소감 : 

예외가 발생하여도 정상적인 흐름이 되어야 하기 때문에 예외 처리 작업은 정말 중요한 작업이다.

자바는 안전성이 중요한 언어로 대부분 프로그램에서 발생하는 오류에 대해 문법적으로 예외 처리를 해야 하고,
오류가 발생 했을 때 그 오류에 대한 기록을 남겨 디버깅에 용이하기 위해 충분한 로그를 남겨야 한다.

😈 아는 내용이라고 그냥 넘어가지 않기! 😈

 

댓글