Spring에서는 @ControllerAdvice
어노테이션과 @RestControllerAdvice
어노테이션을 사용하여 컨트롤러에서 발생하는 예외를 전역적으로 처리
할 수 있습니다.
Spring에서 예외 처리를 할 때, @RestControllerAdvice
와 @ControllerAdvice
는 전역적으로 예외를 잡아주는 어노테이션입니다.
하지만 두 어노테이션은 몇 가지 차이점이 있습니다.
@RestControllerAdvice
는 @ControllerAdvice + @ResponseBody
의 조합으로, RESTful API를 개발할 때 사용합니다.
응답을 JSON 형식으로 내려줍니다.
@ControllerAdvice
는 MVC 패턴을 사용할 때 적합하며, 응답에 @ResponseBody 를 추가해야 합니다.
@RestControllerAdvice
와 @ControllerAdvice
는 패키지 단위로 적용할 수 있습니다.
예를 들어, @ControllerAdvice ("com.controller")
와 @RestControllerAdvice ("com.rest.controller")
로 각각의 컨트롤러에 맞는 예외 처리를 할 수 있습니다.
두 어노테이션을 각각의 상황에 맞게 적절하게 사용하면 됩니다.
이 글에서는 @RestControllerAdvice
를 중점적으로 다뤄보겠습니다.
컨트롤러에서 예외를 직접 처리하지 않아도 됩니다.
예외가 발생하면 @(Rest)ControllerAdvice
가 선언된 클래스에서 해당 예외를 캐치하고 적절한 응답을 반환합니다.
예외에 따라 다른 처리 로직을 적용할 수 있습니다.
@ExceptionHandler
어노테이션을 사용하여 특정 예외에 대한 핸들러 메서드
를 정의할 수 있습니다.
예를 들어, NullPointerException이 발생하면 400 에러 코드와 에러 메시지를 반환하고, IOException이 발생하면 500 에러 코드와 에러 메시지를 반환하는 등의 로직을 구현할 수 있습니다.
공통적인 예외 처리 로직을 재사용할 수 있습니다.
@(Rest)ControllerAdvice
가 선언된 클래스는 모든 컨트롤러에 적용됩니다. 따라서 여러 컨트롤러에서 발생하는 동일한 예외에 대해 한 곳에서 처리할 수 있습니다.
Spring에서 @RestControllerAdvice
를 사용해서 전역 예외 처리하는 방법에 대해 알아보겠습니다.
@RestControllerAdvice
는 @ControllerAdvice와 @ResponseBody
를 합친 어노테이션으로, 모든 컨트롤러에서 발생하는 예외를 한 곳에서 처리할 수 있습니다.
이렇게 하면 코드의 중복을 줄이고, 일관된 에러 응답을 보낼 수 있습니다.
@RestControllerAdvice
를 사용하려면 다음과 같은 단계를 따라야 합니다.
클래스나 열거형
으로 만들 수 있습니다.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;
}
커스텀 예외
를 만듭니다. 커스텀 예외는 RuntimeException을 상속
받고, ErrorCode를 필드
로 가지는 클래스로 만들 수 있습니다.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;
}
}
@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));
}
}
@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 개발 시 전역적인 예외 처리를 쉽게 구현할 수 있습니다.