[Spring] Global Exception Handler 만들기

giggle·2023년 11월 22일
1

📌 개요

개발을 진행하다보면, 코드레벨에서 발생하는 Exception을 처리하게 됩니다. 하지만, 이러한 Exception은 일일히 try-catch와 @ExceptionHandler로 처리해야 할까요?

이를 해결하고자 반복적인 작업을 줄이고 전역에서 공통으로 Exception을 효율적으로 처리할 수 있는 Global Exception Handler에 대해 알아보겠습니다.

📌 Global Exception Handler란?

Global Exception Handler 은 뜻 그래로 애플리케이션 전역에서 예외를 처리하고 사용자에게 응답을 제공하는 메커니즘입니다. 이를 통해 예외 처리 코드를 중앙에서 관리하고 예외에 대한 일관된 응답을 생성할 수 있습니다.

@ControllerAdvice / @RestControllerAdvice 과 @ExceptionHandler 어노테이션을 기반으로 Controller 내에서 발생하는 에러에 대해서 해당 핸들러에서 캐치하여 오류를 발생시키지 않고 응답 메시지로 클라이언트에게 전달해 주는 기능을 의미합니다.

📌 @RestControllerAdvice

@RestConrollerAdvice란?

@ExceptionHandler, @ModelAttribute, @InitBinder 가 적용된 메서드들에 AOP를 적용해 Controller 단에 적용하기 위해 고안된 어노테이션이라고 합니다.

클래스에 선언하면 되며, 모든 @RestController에 대한, 전역적으로 발생할 수 있는 예외를 잡아서 처리할 수 있습니다.

@RestConrollerAdvice 사용

@RestControllerAdvice
public class GlobalExceptionHandler {

    // ErrorCode내의 에러
    @ExceptionHandler(BusinessException.class)
    protected ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
        log.error("BusinessException", e);
        ErrorResponse errorResponse = ErrorResponse.of(e.getErrorCode().getHttpStatus(),
                e.getErrorCode().getErrorCode(), e.getErrorCode().getMessage());

        return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(errorResponse);
    }

    // 나머지 에러
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        log.error("Exception", e);
        ErrorResponse errorResponse = ErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR.toString(), e.getMessage());

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

위와 같이 @RestConrollerAdvice와 @ExceptionHandler를 활용하여 전역에서 Exception을 처리했습니다.

@ExceptionHandler는 메서드에 선언하거나 특정 예외 클래스를 지정해주면 해당 예외가 발생했을 때 메서드에 정의한 로직으로 처리할 수 있습니다. 또한, @RestControllerAdvice에 정의된 메서드가 아닌 일반 컨트롤러 단에 존재하는 메서드에 선언할 경우, 해당 Controller에만 적용됩니다.

위에서 작성한 BusinessException은 아래와 같은 클래스로 작성되어 있습니다.

@Getter
public class BusinessException extends RuntimeException {

    private ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
}

Error Code 관리

@Getter
public enum ErrorCode {
    // 인증 && 인가
    TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "A-001", "토큰이 만료되었습니다."),
    NOT_VALID_TOKEN(HttpStatus.UNAUTHORIZED, "A-002", "해당 토큰은 유효한 토큰이 아닙니다."),
    NOT_EXISTS_AUTHORIZATION(HttpStatus.UNAUTHORIZED, "A-003", "Authorization Header가 빈 값입니다."),
    NOT_VALID_BEARER_GRANT_TYPE(HttpStatus.UNAUTHORIZED, "A-004", "인증 타입이 Bearer 타입이 아닙니다."),
    REFRESH_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "A-005", "해당 refresh token은 존재하지 않습니다."),
    REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "A-006", "해당 refresh token은 만료됐습니다."),
    NOT_ACCESS_TOKEN_TYPE(HttpStatus.UNAUTHORIZED, "A-007", "해당 토큰은 ACCESS TOKEN이 아닙니다."),
    NO_PERMISSION(HttpStatus.UNAUTHORIZED, "A-008", "권한 없음"),
    FORBIDDEN_ROLE(HttpStatus.FORBIDDEN, "A-009", "해당 Role이 아닙니다."),
    UNAUTHORIZED_MEMBER(HttpStatus.UNAUTHORIZED, "A-001", "회원 정보가 일치하지 않습니다."),

    // 유저
    NOT_EXISTS_USER_ID(HttpStatus.NOT_FOUND, "U-001", "존재하지 않는 유저 아이디입니다."),
    NOT_EXISTS_USER_NICKNAME(HttpStatus.NOT_FOUND, "U-002", "존재하지 않는 유저 닉네임입니다."),
    NOT_EXISTS_USER_EMAIL(HttpStatus.NOT_FOUND, "U-003", "존재하지 않는 유저 이메일입니다."),
    ALREADY_REGISTERED_USER_ID(HttpStatus.BAD_REQUEST, "U-004", "이미 존재하는 유저 아이디입니다."),
    NOT_EXISTS_USER_PASSWORD(HttpStatus.NOT_FOUND, "U-005", "존재하지 않는 유저 비밀번호입니다."),
    INVALID_USER_DATA(HttpStatus.BAD_REQUEST, "U-006", "잘못된 유저 정보입니다."),
    INVALID_ADMIN(HttpStatus.BAD_REQUEST, "U-007", "Admin은 제외 시켜주세요."),

    private final HttpStatus httpStatus;
    private final String errorCode;
    private final String message;

    ErrorCode(HttpStatus httpStatus, String errorCode, String message) {
        this.httpStatus = httpStatus;
        this.errorCode = errorCode;
        this.message = message;
    }
}

Error Code는 공통적으로 사용될 수 있는 것들을 모아 enum 형태로 작성하였습니다. 또한, 동일한 Http Status Code를 가지고 있다면 정확하게 어떤 원인인지 알지 못하기 때문에 추가적으로 CodeMessage를 작성하여 효율적으로 Exception 처리를 진행했습니다.


참고


피드백 및 개선점은 댓글을 통해 알려주세요😊

profile
배움을 글로 기록하는 개발자가 되겠습니다.

0개의 댓글