Day9. SpringBoot : @RestControllerAdivce로 에러 중앙 관리하기

2ㅣ2ㅣ·2024년 11월 1일

Project

목록 보기
8/13
post-thumbnail

개요

서버에서 BaseResponse로 넘기는 에러 메세지를 프론트에 띄우기 위해 전역 예외 처리 설정을 하려고 한다. 이는 프론트엔드에게 일관된 형태의 에러 및 상태값을 넘겨줄 수 있을 뿐만 아니라 에러를 중앙 제어하기도 편리하다.

나는 실패 시 다음과 같은 응답을 넘긴다.
BaseResponse.java

    public BaseResponse(BaseResponseStatus status){
        this.isSuccess = status.isSuccess();
        this.code = status.getCode();
        this.message = status.getMessage();
        this.result = null;
    }

As-Is

RuntimeException을 상속받은 CustomRuntimeException을 통해 BaseResponseStatus를 관리하고 있었다.
예를 들어, 이미 사용 중인 이메일로 회원가입을 시도하면 400 에러를 발생시키고자 할 때, 다음과 같은 코드를 작성했다.

BaseResponseStatus.java

ALREADY_EXIST_EMAIL(false, HttpStatus.BAD_REQUEST.value(), "이미 사용중인 이메일입니다."),

Serivce.java

if (memberRepository.existsByEmail(signupRequestDto.getUsername())){
            return new BaseResponse<>(BaseResponseStatus.ALREADY_EXIST_EMAIL);
        }

브라우저에서 이미 db에 존재하는 이메일로 회원가입을 시도하면 200 응답값과 함께 서버가 보낸 에러 메세지 "이미 사용중인 이메일입니다."가 갈 것이다.

응? 에러인데 200? 근데 에러메세지는 간다?

이유는 return으로 단순히 BaseResponse를 반환해서, 실제 에러는 발생하지 않기 때문이다. 에러가 발생하지 않기 때문에 HTTP 상태 코드는 200으로 나가고, 결과적으로 프론트엔드는 에러 메세지는 받지만 에러라는 신호는 전달받지 못하게 된 것이다.

그래서 에러를 throw하게 변경했다.

if (memberRepository.existsByEmail(signupRequestDto.getUsername())){
    throw new CustomRuntimeException(BaseResponseStatus.ALREADY_EXIST_EMAIL);
}

하지만 문제는 CustomRuntimeException을 그대로 에러로 던지면 프론트엔드는 500 에러로만 나간다는 것이다. 왜냐하면 스프링은 모든 예외가 전역해서 처리되지 않으면 500 서버 에러로 처리되기 때문이다. 즉, BaseResponseStatus에 4xx로 정의한 모든 사용자 정의가 무시되고, 기본값인 500으로 퉁처진다는 말이다. 당연히 에러 메세지도 보이지 않는다.

이러한 이유로 전역 에러 처리기를 도입할 필요가 있었다! 그것이 바로 GlobalExceptionHandler이다.

To-Be

GlobalExceptionHandler를 통해 CustomRuntimeException을 잡아 BaseResponse 형태의 일관된 JSON 응답으로 프론트에 에러 메시지를 전달하도록 한다.

GlobalExceptionHandler.java

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomRuntimeException.class)
    public ResponseEntity<BaseResponse<Void>> handleCustomRuntimeException(CustomRuntimeException ex) {
        BaseResponse<Void> errorResponse = new BaseResponse<>(ex.getStatus());

        return new ResponseEntity<>(errorResponse, HttpStatus.valueOf(ex.getStatus().getCode()));
    }
}
  • @RestControllerAdvice: 애플리케이션 전역에서 발생하는 예외를 한 곳에서 처리할 수 있도록 설정하는 어노테이션이다. 개별 컨트롤러마다 예외 처리를 중복할 필요가 없고, 전역에서 예외를 일관되게 관리할 수 있다.
  • @ExceptionHandler(CustomRuntimeException.class): 특정 예외 클래스(CustomRuntimeException)가 발생할 때 실행될 메서드를 정의한다. 여기서 CustomRuntimeException이 발생하면 설정된 BaseResponseStatus를 포함한 JSON 응답이 프론트엔드로 전달된다.

Service.java

if (memberRepository.existsByEmail(signupRequestDto.getUsername())){
            throw new CustomRuntimeException(BaseResponseStatus.ALREADY_EXIST_EMAIL);
        }

이제 CustomRuntimeException 발생 시 전역 예외 처리기가 이를 받아 BaseResponse 형식의 JSON 응답을 프론트엔드로 보내게 된다. BaseResponse의 필드 값들이 프론트에서도 확인되며, 더 이상 프론트에서 에러 메시지를 작성하지 않고 서버에서 제공한 메시지를 그대로 사용할 수 있게 된다.


서버에서 정의한 message가 띄워지는 것을 확인할 수 있다.

[개발자도구] - [Network]


단순히 400에러만 뜨지 않고, BaseResponse가 포함된 JSON 응답을 확인할 수 있다.

profile
https://sususoo.tistory.com/

0개의 댓글