본문 바로가기
Programming/Spring

[Spring] - @RestControllerAdvice를 통한 커스텀 예외 처리 작성 방법

by jenlve 2024. 2. 27.

Postman을 통해 request에 대한 response를 받을 수 있는데, 에러가 발생 시 이해하기 어려운 장문의 문장들이 뜨는 것을 볼 수 있다.

시스템적인 문제부터 클라이언트의 문제까지 에러의 종류는 다양한데, 서버측에서 명확하게 파악할 수 있는 에러들은 따로 정의해서 사용자가 알아보기 쉽게 할 방법을 찾아보기로 하였다. 


@RestControllerAdvice

@ControllerAdvice + @ResponseBody 형태로, RESTfull한 웹 서비스를 개발할 시 예외 처리 로직을 구현할 때 적합하다. @RestControllerAdvice 를 사용함으로써, 컨트롤러 계층 전반에서 발생하는 예외들을 한 곳에 서 효율적으로 관리하고 처리할 수 있게 된다.

즉 @RestControllerAdvice를 사용함으로써, 애플리케이션 전반에 걸쳐 일어나는 예외들을 중앙에서 처리하여 API의 사용자에게 일관된 형태의 에러 응답을 제공할 수 있게 된다!

 

RestControllerAdvice를 통한 커스텀 예외처리 작성 방법

1. 커스텀 예외 처리 클래스를 정의한다.  

exception 패키지를 만들어 작성하였다.

@Getter
@Setter
@AllArgsConstructor
public class ReservationSystemException extends RuntimeException{
    private ErrorCode errorCode;
    private String errorMessage;

    public ReservationSystemException(ErrorCode errorCode) {
        this.errorCode = errorCode;
        this.errorMessage = errorCode.getDescription();
    }
}

 

나는 에러 코드와 상세 메시지를 담는 클래스를 정의했다. 

여기서 추가할 점은

1. RuntimeException 클래스를 상속해줘야 하고

2. 해당 클래스를 간편하게 호출하기 위해 AllArgsConstructor를 추가하고

3. 객체를 JSON으로 직렬화하는 과정에서 객체의 상태값을 읽기 위해 필요한 Getter와 역직렬화를 위한 Setter를 추가해준다.   

 

2. 커스텀 예외들의 항목을 모아놓을 enum 클래스를 따로 만들어준다.

여기서 내가 커스텀한 모든 에러항목들을 관리할 수 있다. (enum 패키지를 만들어 작성)

@Getter
@AllArgsConstructor
public enum ErrorCode {
    ALREADY_REGISTERED_USER("이미 등록된 사용자입니다."),
    INVALID_SERVER_ERROR("내부 서버 오류가 발생하였습니다.");

    private final String description;
}

 

 아래의 description이 각 열거형 상수(ALREADY_REGISTERED_USER)의 상세 설명 "이미 등록된 사용자입니다." 이 부분을 가리키는 것이다. 

 

3. 작성 에러 객체를 어떤 식으로 전달할지 ErrorResponse(Dto)를 작성해준다.  (dto 패키지)

@Getter
@AllArgsConstructor
public class ErrorResponseDto {
    private ErrorCode errorCode;
    private String errorMessage;
}

 

 

4. 앞서 언급한 RestControllerAdvice 를 적용한 GlobalExceptionHandler 클래스를 만들어 에러를 포함하여 사용자에게 넘여주도록 구현한다. (exception 패키지)

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ReservationSystemException.class)
    public ResponseEntity<ErrorResponseDto> handleReservationSystemException(ReservationSystemException e) {
        log.error("{} is occurred.", e.getErrorCode());
        ErrorResponseDto errorResponseDto =
                new ErrorResponseDto(e.getErrorCode(), e.getErrorMessage());
        return new ResponseEntity<>(errorResponseDto, BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponseDto> handlerException(Exception e) {
        log.error("Exception is occurred.", e);
        ErrorResponseDto errorResponseDto = new ErrorResponseDto(
                INVALID_SERVER_ERROR,
                INVALID_SERVER_ERROR.getDescription()
        );
        return new ResponseEntity<>(errorResponseDto, INTERNAL_SERVER_ERROR);
    }
}

 

여기서 위쪽이 내가 커스텀한 에러들을 처리

@ExceptionHandler(ReservationSystemException.class)

 

하는 곳이고 아래쪽은 위쪽의 모든 에러 해당하지 않는 나머지 모든 에러들을 처리하는 Exception.class를 작성하였다. 추가적으로 ResponseEntity<> 형식으로 반환하게 하여, HTTP 상태코드도 같이 전달되게 하였다. 

 

 

결과! 아래 사진과 같이 내가 원하는 에러 코드와 상세 메시지를 볼 수 있고, Http.BAD_REQUEST를 통해 400에러가 뜨는 모습을 볼 수 있다!