[Spring] 전역 예외 처리

Yoon Uk·2023년 5월 21일
0

Spring

목록 보기
4/5
post-thumbnail
post-custom-banner

Spring에서는 @ControllerAdvice 어노테이션과 @RestControllerAdvice 어노테이션을 사용하여 컨트롤러에서 발생하는 예외를 전역적으로 처리할 수 있습니다.

1. @RestControllerAdvice 와 @ControllerAdvice 의 특징과 차이

Spring에서 예외 처리를 할 때, @RestControllerAdvice@ControllerAdvice 는 전역적으로 예외를 잡아주는 어노테이션입니다.
하지만 두 어노테이션은 몇 가지 차이점이 있습니다.

  • @RestControllerAdvice@ControllerAdvice + @ResponseBody 의 조합으로, RESTful API를 개발할 때 사용합니다.
    응답을 JSON 형식으로 내려줍니다.

  • @ControllerAdvice 는 MVC 패턴을 사용할 때 적합하며, 응답에 @ResponseBody 를 추가해야 합니다.

  • @RestControllerAdvice@ControllerAdvice 는 패키지 단위로 적용할 수 있습니다.
    예를 들어, @ControllerAdvice ("com.controller")@RestControllerAdvice ("com.rest.controller") 로 각각의 컨트롤러에 맞는 예외 처리를 할 수 있습니다.

두 어노테이션을 각각의 상황에 맞게 적절하게 사용하면 됩니다.

이 글에서는 @RestControllerAdvice를 중점적으로 다뤄보겠습니다.

2. @(Rest)ControllerAdvice의 장점

  • 컨트롤러에서 예외를 직접 처리하지 않아도 됩니다.
    예외가 발생하면 @(Rest)ControllerAdvice가 선언된 클래스에서 해당 예외를 캐치하고 적절한 응답을 반환합니다.

  • 예외에 따라 다른 처리 로직을 적용할 수 있습니다.
    @ExceptionHandler 어노테이션을 사용하여 특정 예외에 대한 핸들러 메서드를 정의할 수 있습니다.
    예를 들어, NullPointerException이 발생하면 400 에러 코드와 에러 메시지를 반환하고, IOException이 발생하면 500 에러 코드와 에러 메시지를 반환하는 등의 로직을 구현할 수 있습니다.

  • 공통적인 예외 처리 로직을 재사용할 수 있습니다.
    @(Rest)ControllerAdvice가 선언된 클래스는 모든 컨트롤러에 적용됩니다. 따라서 여러 컨트롤러에서 발생하는 동일한 예외에 대해 한 곳에서 처리할 수 있습니다.

3. @RestControllerAdvice 사용 방법

Spring에서 @RestControllerAdvice를 사용해서 전역 예외 처리하는 방법에 대해 알아보겠습니다.

@RestControllerAdvice@ControllerAdvice와 @ResponseBody를 합친 어노테이션으로, 모든 컨트롤러에서 발생하는 예외를 한 곳에서 처리할 수 있습니다.
이렇게 하면 코드의 중복을 줄이고, 일관된 에러 응답을 보낼 수 있습니다.

@RestControllerAdvice를 사용하려면 다음과 같은 단계를 따라야 합니다.

  1. 에러 코드를 정의합니다.
    에러 코드는 에러 이름, HTTP 상태 코드, 메시지 등을 포함하는 클래스나 열거형으로 만들 수 있습니다.
    예를 들어, 다음과 같이 CommonErrorCode와 UserErrorCode를 정의할 수 있습니다.
public interface ErrorCode {
  String name();
  HttpStatus getHttpStatus();
  String getMessage();
}
@Getter
@RequiredArgsConstructor
public enum CommonErrorCode implements ErrorCode {
  INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid parameter included"),
  RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "Resource not exists"),
  INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error");
  
  private final HttpStatus httpStatus;
  private final String message;
}
@Getter
@RequiredArgsConstructor
public enum UserErrorCode implements ErrorCode {
  INACTIVE_USER(HttpStatus.FORBIDDEN, "User is inactive");
  
  private final HttpStatus httpStatus;
  private final String message;
}
  1. 커스텀 예외를 만듭니다. 커스텀 예외는 RuntimeException을 상속받고, ErrorCode를 필드로 가지는 클래스로 만들 수 있습니다.
    예를 들어, 다음과 같이 ResourceNotFoundException과 InactiveUserException을 만들 수 있습니다.
public class ResourceNotFoundException extends RuntimeException {
  private final ErrorCode errorCode;

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

  public ErrorCode getErrorCode() {
    return errorCode;
  }
}
public class InactiveUserException extends RuntimeException {
  private final ErrorCode errorCode;

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

  public ErrorCode getErrorCode() {
    return errorCode;
  }
}
  1. @RestControllerAdvice를 선언한 클래스를 만듭니다.
    이 클래스는 스프링 빈으로 등록되며, @ExceptionHandler 어노테이션을 사용해서 특정 예외에 대한 처리 로직을 정의할 수 있습니다.
    예를 들어, 다음과 같이 GlobalExceptionHandler 클래스를 만들 수 있습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
  
  /*
  * Developer Custom Exception: 직접 정의한 RestApiException 에러 클래스에 대한 예외 처리
  */
  @ExceptionHandler(RestApiException.class)
  protected ResponseEntity<ErrorResponse> handleCustomException(RestApiException ex) {
  	GlobalErrorCode globalErrorCode = ex.getGlobalErrorCode();
  	return handleExceptionInternal(globalErrorCode);
  }

  // handleExceptionInternal() 메소드를 오버라이딩해 응답 커스터마이징
  private ResponseEntity<ErrorResponse> handleExceptionInternal(GlobalErrorCode errorCode) {
    return ResponseEntity
      .status(errorCode.getHttpStatus().value())
      .body(new ErrorResponse(errorCode));
  }

}
  1. Controller나 Service에서 커스텀 예외를 발생시킵니다.
    Controller나 Service에서 비즈니스 로직을 수행하다가 예외 상황이 발생하면, 적절한 커스텀 예외를 throw합니다.
    예를 들어, 다음과 같이 UserController 클래스에서 InactiveUserException을 발생시킬 수 있습니다.
@RestController
@RequestMapping("/users")
public class UserController {

  @Autowired
  UserService userService;

  @GetMapping("/{id}")
  public UserDto getUser(@PathVariable Long id) {
    User user = userService.findById(id);
    if (user.isActive()) {
      return UserDto.of(user);
    } else {
      throw new InactiveUserException(UserErrorCode.INACTIVE_USER);
    }
  }
}

이렇게 하면 스프링은 @RestControllerAdvice가 선언된 클래스에서 해당 예외에 대한 처리 메소드를 찾아서 실행하고, 클라이언트에게 에러 응답을 보냅니다.

@RestControllerAdvice는 스프링 부트에서 제공하는 편리한 어노테이션으로, REST API 개발 시 전역적인 예외 처리를 쉽게 구현할 수 있습니다.

4. 참고

post-custom-banner

0개의 댓글