@RestControllerAdvice 예외 처리 구현하기

박영준·2023년 7월 5일
0

Spring

목록 보기
35/58

프로젝트 중 @RestControllerAdvice 를 이용한 전역적인 예외 처리를 구현해봤다.
그를 토대로 작성했다.

1. 예외 케이스 관리

ErrorCode 클래스

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

    // 토큰 전달 하지 않은 경우, 정상 토큰이 아닌 경우
    INVALID_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 유효하지 않습니다."),

    // 토큰이 있으며 유효한 토큰이나, 해당 사용자의 게시글/댓글이 아닌 경우 (즉, 해당 사용자의 토큰이 아닌 경우)
    AUTHORIZATION(HttpStatus.BAD_REQUEST, "작성자만 삭제/수정할 수 있습니다."),

    // DB 에 이미 존재하는 username 으로 회원가입 요청한 경우
    DUPLICATED_USERNAME(HttpStatus.BAD_REQUEST, "중복된 username 입니다"),

    // 로그인 시, username 과 password 중 일치하지 않는 정보가 있을 경우
    NOT_MATCH_INFORMATION(HttpStatus.BAD_REQUEST, "회원을 찾을 수 없습니다."),

    // DB 에 해당 게시글이 존재하지 않는 경우
    NOT_FOUND_BOARD(HttpStatus.BAD_REQUEST, "게시글을 찾을 수 없습니다."),

    // DB 에 해당 댓글이 존재하지 않는 경우
    NOT_FOUND_COMMENT(HttpStatus.BAD_REQUEST, "댓글을 찾을 수 없습니다."),

    // admin 계정으로 회원가입 시, ADMIN_TOKEN 과 일치하지 않을 경우
    NOT_MATCH_ADMIN_TOKEN(HttpStatus.BAD_REQUEST, "관리자 암호가 일치하지 않습니다.");
    
    private final HttpStatus httpStatus;
    private final String message;
}
  • Client 에게 보내줄 에러 코드를 한 곳에서 관리할 수 있다.

  • 원하는 에러 및 메시지를 직접 추가해주면 된다.

  • Enum Class 임에 주의!

    • 에러 이름 : INVALID_TOKEN, AUTHORIZATION ...

    • HTTP 상태 : HttpStatus.BAD_REQUEST

    • HTTP 메시지 : "토큰이 유효하지 않습니다." ...

2. 에러 응답 형식 지정

ErrorResponse 클래스

@Getter
@RequiredArgsConstructor
public class ErrorResponse {
    private final int statusCode;
    private final String message;

    public ErrorResponse(ErrorCode errorCode) {
        this.statusCode = errorCode.getHttpStatus().value();
        this.message = errorCode.getMessage();
    }
}
  • 에러 응답 클래스

    • 데이터를 JSON 형식으로 반환하기 위한 에러 응답 형식을 지정한다
  • get 메서드를 이용하여, ErrorCode 클래스에서 정의해둔 각 예외들을 가져온다

  • .value() 쓰는 이유

    • 에러코드가 int 타입이기 때문
    • 200, 400 등... 상태 코드가 전달된다.

3. 에러 코드 정의

CustomException 클래스

@Getter
@RequiredArgsConstructor
public class CustomException extends RuntimeException {
    private final ErrorCode errorCode;
}
  • 발생한 예외를 처리해줄 예외 클래스

  • 언체크 예외(RuntimeException 런타임 예외)를 상속받는다

    • 최근에는 거의 모든 경우에 언체크 예외를 사용

언체크 예외를 상속받은 이유?
이유 1.
일반적인 비지니스 로직들은 따로 catch해서 처리할 것이 없기 때문이다.
만약 체크 예외로 할 경우, 불필요하게 throws가 전파될 것이다.

이유 2.
Spring 은 내부적으로 발생한 예외를 확인해서, '언체크 예외' or '에러' 일 경우, 자동으로 롤백시키도록 처리한다.
반면, '체크 예외' 의 경우에만 롤백을 하지 않는다. (체크 예외는 처리가 강제되므로, 개발자가 무언가를 처리할 것이라는 기대 때문이다)

4. 전역 예외 처리

GlobalExceptionHandler 클래스

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // 직접 정의한 CustomException 에러 클래스에 대한 예외 처리
    @ExceptionHandler({CustomException.class})
    public ResponseEntity<ErrorResponse> handleAllException(CustomException ex) {
        ErrorCode errorCode = ex.getErrorCode();
        return handleExceptionInternal(errorCode);
    }

    // handleExceptionInternal() 메소드를 오버라이딩해 응답을 커스터마이징
    private ResponseEntity<ErrorResponse> handleExceptionInternal(ErrorCode errorCode) {
        return ResponseEntity
                .status(errorCode.getHttpStatus().value())
                .body(new ErrorResponse(errorCode));
    }
}
  • 전역적으로 에러 처리해주는 클래스
    • 이렇게 모든 예외를 잡은 후에야, 예외 종류별로 메소드를 공통 처리할 수 있다.

ResponseEntityExceptionHandler

  • spring 예외를 미리 처리해둔 추상 클래스

    • Spring 에서 제공해준다
    • 해당 클래스에는 spring 예외에 대한 ExceptionHandler 가 모두 구현되어 있기 때문에, ControllerAdvice 클래스가 이를 상속받도록(extends) 하면 된다
  • 만약 이 추상 클래스를 상속받지 않는다면, Spring 예외들은 DefaultHandlerExceptionResolver가 처리하게 된다.

    • 이 경우, 예외 처리기가 달라지기 떄문에 클라이언트가 일관되지 못한 에러 응답을 받지 못하게 된다.

@RestControllerAdvice

  • 프로젝트 전역에서 발생하는 모든 예외를 잡아준다.

@ExceptionHandler

  • 발생한 특정 예외를 잡아서, 하나의 메소드에서 공통 처리할 수 있게 한다.

참고: Spring 전역 예외 처리: @RestControllerAdivce 적용
참고: [Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)

profile
개발자로 거듭나기!

0개의 댓글