서버 개발

Spring Boot REST API 회원관리 - (3) @RestControllerAdvice를 사용하여 전역 예외처리하기

devgenie 2024. 8. 8. 16:14

애플리케이션에서 예외 처리를 일관성 있게 관리하는 것은 중요하다. 이를 위해 @RestControllerAdvice를 사용할 수 있다.

 

@RestControllerAdvice?

모든 @RestController 에서 발생하는 예외를 한곳에서 처리할 수 있게 해주는 어노테이션이다. 이 어노테이션을 이용하면 개별 컨트롤러에서 예외처리를 중복하여 작성할 필요 없이 한곳에서 관리할 수 있다.

 

 

공통된 응답값을 주기 위한 클래스 작성

예외 발생시 일관성 있는 응답값을 주기 위한 클래스를 작성하였다. 오류 발생 timestamp, 메세지, 세부 사항 정보를 넘겨줄 생각이다.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExceptionResponse {
    private Date timestamp;
    private String message;
    private String detail;
}

 

 

InvalidRequestException 클래스 생성

보통 클라이언트가 서버에 잘못된 요청을 보낸 경우 '400 Bad Request' 상태코드를 반환한다. 이 경우를 처리하기 위해 'InvalidRequestException' 클래스를 생성하였다.

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestException extends RuntimeException{
    public InvalidRequestException(String message) {
        super(message);
    }
}

 

 

CustomizedResponseEntityExceptionHandler 클래스 생성

@RestControllerAdvice 어노테이션을 이용하여 @RestController 에서 발생하는 예외를 전역적으로 처리하기 위한 클래스를 생성한다. 차후 스프링의 기본 예외 처리 로직을 확장할 경우가 생길 수도 있으므로 ResponseEntityExceptionHandler를 우선 상속받았다.
이 코드에서는 @ExceptionHandler(Exception.class)를 사용하여 모든 예외를 처리하는 메서드 'handleAllException' 와 @ExceptionHandler(CustomException.class)를 사용하여 커스텀 예외를 처리할 'handleInvalidResourceException' 메서드가 있다. @ExceptionHandler(CustomException.class)를 보면 알 수 있듯이 커스텀 예외처리를 하기 위해서는 이를 위해 생성한 클래스를 어노테이션 value에 명시하면 된다.

이를 이용하여 필요할 때마다 커스텀 예외처리를 위해 클래스를 추가하고 다양한 예외 상황에 대한 적절한 응답을 쉽게 구성할 수 있다.

@Slf4j
@RestControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest webRequest) {
        log.error("message : {}", NestedExceptionUtils.getMostSpecificCause(ex));
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), webRequest.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(InvalidRequestException.class)
    public final ResponseEntity<Object> handleInvalidResourceException(Exception ex, WebRequest webRequest) {
        log.error("message : {}", NestedExceptionUtils.getMostSpecificCause(ex));
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(), webRequest.getDescription(false));
        return new ResponseEntity(exceptionResponse, HttpStatus.CONFLICT);
    }

}

 

예외처리를 위한 패키지 및 클래스들

 

 

테스트를 위한 엔드포인트 생성

@RestControllerAdvice 가 잘 적용되어 컨트롤러에서 발생한 예외처리가 잘 되는지 확인하기 위한 엔드포인트를 생성하였다.

@GetMapping("/invalidRequestExceptionTest")
public ResponseEntity<?> conflictTest() {
    throw new InvalidRequestException("잘못된 입력값 오류 발생");
}

@GetMapping("/exceptionTest")
public ResponseEntity<?> exceptionTest() throws Exception {
    throw new Exception("공통 오류 발생");
}

 

 

테스트

Swagger UI를 이용해 위에서 생성된 엔드포인트를 호출하여 예외처리가 잘 동작되는지 확인하였다.

  1. "/invalidRequestExceptionTest" 호출 (커스텀 예외처리 호출)
    "잘못된 입력값 오류 발생"
  2. "/exceptionTest" 호출 (공통 예외처리 호출)
    "공통 오류 발생"


스프링에서 예외가 발생하였을 때의 예외처리 흐름

 

  1. 컨트롤러에서 예외가 발생
    • 컨트롤러에서 예외가 발생
  2. 동일한 컨트롤러에 @ExceptionHandler 메서드가 존재하는지 확인
    • 예외가 발생한 컨트롤러 클래스에 해당 예외를 처리하는 @ExceptionHandler 메서드가 있는지 확인
    • YES: 해당 메서드에서 예외를 처리하고, 클라이언트에게 응답을 반환
    • NO: 다음 단계로 진행
  3. @ControllerAdvice 클래스에 @ExceptionHandler 메서드가 존재하는지 확인
    • @ControllerAdvice 클래스를 확인하여, 해당 예외를 처리하는 @ExceptionHandler 메서드가 있는지 확인
    • YES: 해당 메서드에서 예외를 처리하고, 클라이언트에게 응답을 반환
    • NO: 다음 단계로 진행
  4. 예외가 @ResponseStatus로 주석이 달렸는지 확인
    • 예외 클래스에 @ResponseStatus 어노테이션이 있는지 확인
    • YES: 스프링의 ResponseStatusExceptionResolver 클래스가 예외를 처리
    • NO: 스프링의 DefaultHandlerExceptionResolver 클래스가 예외를 처리
  5. 클라이언트에게 응답 반환
    • 각 처리 방법에 따라 예외가 처리된 후, 클라이언트에게 응답이 반환

 

출처 : https://medium.com/@seonggil/handling-exceptions-with-restcontrolleradvice-exceptionhandler-e7c95216da8d