본문 바로가기

Tech/Spring | SpringBoot

[SpringBoot] 예외처리 - 3 (@ExceptionHandler, @ResponseStatus, @ControllerAdvice)

반응형

1. @ExceptionHandler(Exception.class)

@Controller 레벨에서 예외를 처리하는 방법이다.

예외를 처리할 메서드를 구현하고 @ExceptionHandler 어노테이션으로 해당 메서드를 선언하여 예외 처리 메서드로 지정할 있다. 이때 @ExceptionHandler 어노테이션의 값으로 예외처리할 Exception 클래스 종류를 지정할 있다.

 

방식의 가장 약점은 @ExceptionHandler 선언된 메서드가 해당 컨트롤러 클래스에서만 유효하다는 것이다. 모든 컨트롤러에 해당 메서드를 구현하는 것은 코드의 양이 늘어나고 전반적인 해결방법이 아니기 때문에 사용하기 어렵다.

 

모든 클래스가 특정 base 클래스로부터 상속되도록 하는 방식으로 구현하여 해결할 있지만, 예외적인 클래스나 우리가 수정할 없는 외부 클래스들을 사용하는 경우 문제를 해결할 없다.

 

/**
 * @ExceptionHandler({RuntimeException.class})
 *
 * 해당 클래스에서 RuntimeException 에러 발생 시 동작하는 예외 처리 함수
 */
@ExceptionHandler({RuntimeException.class})
public ResponseEntity<String> runtimeExceptionHandler(RuntimeException exception) {
    log.error("RuntimeException");
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage());
}

/**
 * @ExceptionHandler({NullPointerException.class})
 *
 * 해당 클래스에서 NullPointerException 에러 발생 시 동작하는 예외 처리 함수
 */
@ExceptionHandler({NullPointerException.class})
public ResponseEntity<String> nullPointerExceptionHandler(NullPointerException exception) {
    log.error("NullPointerException");
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(exception.getMessage());
}

2. @ResponseStatus(HttpStatus.SERVICE_UNAVELABLE)

@ResponseStatus 는 이름 그대로 에러 발생 시에 매핑되는 HTTP 상태를 지정해주는 역할을 하는 어노테이션이다. @ReeponseStatus 는 Exception 클래스 자체, 또는 메소드에 @ExceptionHandler 와 함께 사용할 수 있다. 클래스에 @RestControllerAdvice 를 지정하여 이와 함께 사용할 수도 있다.

 

/**
 * 해당 예외 발생 시에 HTTP 상태 코드를 SERVICE_UNAVAILABLE (503) 으로 매핑한다.
 */
@ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public class CustomException extends Exception {
    public CustomException(String msg) {
        super(msg);
    }
}

 

@ResponseStatus 를 사용하면 사용자가 정의한 예외 클래스에 대해서 HTTP 상태 코드를 지정할 수 있다. 하지만 예외 클래스와 강하게 결합되어 같은 예외는 같은 상태의 에러 메시지를 반환하기 때문에 별도의 상태에 대해서는 예외 클래스를 추가해야 하는 제약사항이 있다. 또한 외부에서 정의한 Exception 에 대해서는 @ResponseStatus 를 선언할 수 없다.

 

@ResponseStatus 의 경우 ResponseStatusExceptionResolver 에 의해서 처리되는데, 어노테이션으로 지정된 예외를 WAS 까지 전달하여서 BasicErrorController 에 에러 처리 요청을 보내게 된다. 그렇기 때문에 예외처리 과정이 WAS 에서 예외 처리를 재요청하는 복잡한 과정을 거친다는 단점이 있다. 이러한 단점을 해결하기 위하여 @ExceptionHandler 함께 사용하여 ExceptionHandlerExceptionResolver 에서 미리 예외를 처리하도록 하기도 한다.

3. ResponseStatusException

Spring5 에서는 @ResponseStatus 의 대안으로 ResponseStatusException 을 추가하였다. ResponseStatusException 은 HttpStatus 와 함께 선택적으로 reason, cause 등을 설정하여 응답으로 전달할 수 있다.

 

@GetMapping("/Bad-Request")
public void badRequestException() {
    try {
        log.info("/Bad-Request");
    } catch (Exception e) {
        log.error(String.format("status code=%d", HttpStatus.BAD_REQUEST));
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "something bad request");
    }
}

 

@ResponseStatus 와 동일하게 예외가 발생하면 ResponseStatusExceptionResolver 가 에러를 처리한다.

 

기본적인 예외 처리를 빠르게 할 수 있고, HttpStatus 와 reason 을 직접 지정하고 예외를 직접 생성하기 때문에 프로토타이핑에서 장점을 가지고 있다. 하지만 매번 예외 처리시마다 이러한 코드를 작성해야하기 때문에 코드의 중복이 많아지고 Spring 내부의 예외를 처리하는 데에 어려움이 있다. 또한 @ResponseStatus 와 동일하게 예외가 WAS 까지 전달되고 WAS 의 예외 처리 요청이 발생하게 된다.

4. @ControllerAdvice, @RestControllerAdvice

Spring 에서는 전역적으로 @ExceptionHandler 를 적용할 수 있는 @ControllerAdvice 와 @RestControllerAdvice 를 제공한다. 이 방법을 통해서 이전의 MVC 모델에서 벗어나 @ExceptionHandler 의 유연함과 타입 안전성과 함께 ResponseEntity 를 사용할 수 있게된다.

 

@Slf4j
@ControllerAdvice
class ControllerAdviceExceptionHandler {

    @ExceptionHandler(NoSuchElementException.class)
    public ResponseEntity<?> noSuchElementExceptionHandler(NoSuchElementException exception) {
        log.error("NoSuchElementException");
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
    }
}

@Slf4j
@RestControllerAdvice
class RestControllerAdviceExceptionHandler {

    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseEntity<?> indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException exception) {
        log.error("IndexOutOfBoundsException");
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(exception.getMessage());
    }
}

 

@ControllerAdvice 는 여러개로 흩어져 있는 @ExceptionHanler 들을 하나로 병합하여 하나의 전역 에러 핸들링 컴포넌트로 만들 수  있게 해준다. 이 방법은 status code 에 따라서 response 의 body 를 설정할 수 있도록 해준다. 그리고 여러개의 예외들을 하나의 메서드로 연결하여 처리할 수 있도록 한다.

 

@ControllerAdvice 와 @RestControllerAdvice 는 @Component 어노테이션을 포함하고 있다. 그렇기 때문에 @ControllerAdvice 와 @RestControllerAdvice 로 선언된 클래스는 스프링 빈으로 등록이 된다. 빈으로 등록되었기 때문에 에러 처리 클래스들은 Exception 이 발생했을 때 에러 처리 과정에서 전역적으로 사용될 수 있다.

 

@ControllerAdvice 를 쓰면 하나의 클래스로 모든 컨트롤러의 에러를 전역적으로 처리할 수 있다. 하지만 전역적으로 에러 처리에 영향을 미치는 만큼 @ControllerAdvice 를 여러 클래스로 구현하는 것이 아니라 한 프로젝트 내에서 하나의 ControllerAdvice 만 정의하여 관리하는 것이 좋다. 만약 여러개의 ControllerAdvice 가 필요하다면 basePackages 를 지정해서 사용하는 것이 좋다.

 

@ControllerAdvice, @RestControllerAdvice 는 모두 @ExceptionHandler 를 사용하기 때문에 ExcptionHandlerExceptionResolver 가 동작할 때 사용되어진다.

[Reference]

 

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

예외 처리는 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 어떠한 방법들이 있고 가장 좋은 방법(Best Practice)은 무엇인

mangkyu.tistory.com

 

[Spring] Rest API Exception Handling

Spring MVC (+ Spring Boot)를 사용하여 Rest API를 만들때마다 Exception Handling을 하면서...

blog.naver.com

 

API Exception 처리[Spring]

스프링 예외 처리

velog.io

반응형