[Spring] Exception handling

hyng·2022년 1월 20일
0

이번에 팀 프로젝트를 진행하면서 예외를 어떻게 처리해야 할지를 고민했다. 고민한 과정과 어떻게 예외 처리를 했는지 기록한 글이다.

1. 표준 예외 활용

가장 먼저 생각했던 방법은 표준 예외를 활용하는 방법이다.

public void activateLike(){
        if(this.status == LikePostStatus.ACTIVE){
            throw new IllegalStateException("이미 좋아요 한 글은 좋아요가 불가능합니다.");
        }

}
  • 이 방법은 커스텀 예외를 따로 만들지 않고 표준 예외를 활용함으로써 코드의 가독성이 높아지는 등의 장점이 있다.
  • 예외를 던지면서 메시지를 전달하는데 여럿이서 개발하는 경우 메시지가 중복될 수 있는 등 불편한 점들이 있다.

2. 커스텀 예외 활용

  • 커스텀 예외를 사용할 경우 표준 예외와 달리 메시지가 겹칠 일이 없다.
  • 클래스 이름만으로도 어떤 예외인지 유추하기 쉽다.
  • 상세한 예외 정보를 제공할 수 있다.(관련 정보를 클래스 내에서 관리할 수 있다) 이 부분은 물론 표준 예외에서도 가능하다. 하지만 같은 예외가 발생하는 곳이 무수히 많다면 코드의 중복이 많이 발생하고 리팩토링이 어려워진다.
  • @ControllerAdvice.. 등에서 예외를 일괄처리하기 쉽다.
  • 표준 예외의 경우 Exception이나 RuntimeException을 잡아 예외에 대한 일괄처리를 해줄 수도 있겠지만 우리가 원치 않는 예외까지 모두 잡아내 우리에게 혼란을 줄 수 있다.
  • 이외에도 여러 장점들이 존재하지만, 다른 한편으로는 일일이 커스텀 예외 클래스를 만들어주는 것이 지나친 구현이라고 느껴질 수도 있다.

커스텀 예외, 표준 예외 사용의 차이점은 아래 링크 참조를 추천합니다.
https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/

3. 그래서 사용한 방법

자세한 내용은 아래 링크 참조
https://bcp0109.tistory.com/303

CustomException

  • 커스텀 예외 클래스를 하나 만들어 예외를 던질 때 해당 예외를 던지도록 한다.
  • 예외의 메시지는 (응답 코드, 에러 메시지)로 에러를 정의해놓은 enum 클래스인 ErrorCode를 이용한다.
@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException{
    private final ErrorCode errorCode;

    @Override
    public String getMessage() {
        return errorCode.getDetail();
    }
}

ErrorCode

  • (응답 코드, 에러 메시지) 형식으로 모든 에러를 관리한다.
  • 모든 에러를 enum 클래스로 관리하기 때문에 표준 예외를 사용할 때처럼 여러 사람이 개발할 때 메시지가 겹칠 일이 없고 한 곳에서 예외 메시지를 관리할 수 있다는 장점이 있다.
  • 또 커스텀 예외를 사용할 때처럼 예외 처리마다 예외 클래스를 새로 생성할 필요가 없다.
@Getter
@AllArgsConstructor
public enum ErrorCode {

    /* 400 BAD_REQUEST : 잘못된 요청 */
    USER_ID_NOT_THE_SAME(BAD_REQUEST, "로그인 정보[ID]가 올바르지 않습니다."),
    SOCIAL_LOGIN_ID_AND_AUTH_PROVIDER_NOT_THE_SAME(BAD_REQUEST, "로그인 정보[SOCIAL_LOGIN_ID, AUTH_PROVIDER]가 올바르지 않습니다."),
    REFRESH_TOKEN_NOT_FOUND(BAD_REQUEST, "쿠키에 리프레시 토큰이 존재하지 않습니다."),

 	 ...

    private final HttpStatus httpStatus;
    private final String detail;

}

ExceptionController

  • 모든 예외를 전역적으로 관리한다.
  • @ControllerAdvice로 모든 컨트롤러에서 발생하는 예외를 잡는다.
  • 그리고 @ExceptionHandler로 예외를 각각 처리해 준다.
  • 예외 처리해야 할 부분이 많아질수록 try-catch가 여기저기 나타나게 되면서
    코드의 가독성이 떨어지고 무엇보다 일일이 try-catch 코드를 작성해 줘야 해서 불편한데 전역으로 예외를 관리하기 때문에 이러한 불편함을 없애줄 수 있다.
@Slf4j
@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler(value = {ConstraintViolationException.class, MethodArgumentNotValidException.class, MethodArgumentTypeMismatchException.class})
    public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(Exception e){
        log.error("handleMethodArgumentNotValidException throw CustomException : {}", e.getMessage());
        return ErrorResponse.toResponseEntity(ErrorCode.valueOf("INVALID_INPUT_VALUE"));
    }


    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e){
        log.error("handleHttpRequestMethodNotSupportedException throw CustomException : {}", e.getMessage());
        return ErrorResponse.toResponseEntity(ErrorCode.valueOf("METHOD_NOT_ALLOWED"));

    }
    @ExceptionHandler(value = {CustomException.class})
    public ResponseEntity<ErrorResponse> handleCustomException(CustomException e){
        log.error("handleCustomException throw CustomException : {}", e.getErrorCode());
        return ErrorResponse.toResponseEntity(e.getErrorCode());
    }

}

ErrorResponse

예외 발생시 응답을 보낼때 사용할 dto이다.

@Getter
public class ErrorResponse {
    private final LocalDateTime timestamp = LocalDateTime.now();
    private final String error;
    private final String message;

    @Builder
    private ErrorResponse(String error, String message){
        this.error = error;
        this.message = message;
    }
    public static ResponseEntity<ErrorResponse> toResponseEntity(ErrorCode errorCode){
        return ResponseEntity
                .status(errorCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .error(errorCode.getHttpStatus().name())
                        .message(errorCode.getDetail())
                        .build()
                );

    }
}

이 방법은 커스텀 예외 클래스를 하나만 두고 메시지로 각 예외들을 구분하는 방법이다.
예외 처리 시, CustomException을던지 되 enum 클래스에 예외 코드만 입력해 주면 된다. 여럿이서 개발을 진행할 때도 enum 클래스에 있는 예외 코드를 활용하거나 필요한 예외가 없다면 enum 클래스에 추가해서 커스텀 예외를 던지면 된다.
또 ExceptionHandler로 전역에서 발생하는 예외를 잡아서 처리하기 때문에 코드에 try-catch 문을 반복해서 넣을 필요도 없다.

참조

https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/
https://tecoble.techcourse.co.kr/post/2020-07-28-global-exception-handler/
https://velog.io/@aidenshin/Spring-Boot-Exception-Controller
https://cheese10yun.github.io/spring-guide-exception/
https://bcp0109.tistory.com/303

profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글