API 예외의 경우 일반적인 예외 화면을 뿌리는 것이 아니라, JSON 오류 메시지를 반환해줘야 한다. 스프링은 @ExceptionHandler를 제공하고 이를 통해 간편한 API 예외처리를 할 수 있다.
@ExceptionHandler
을 컨트롤러 안의 예외를 처리하는 메서드를 선언할 때 사용한다. 속성으로 어떤 예외를 처리할지 작성하면, 해당 예외가 발생시 @ExceptionHandler
메서드가 실행되어 처리해 준다.
@Slf4j
@RestController
public class ApiExceptionTest {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResult> illegalExHandle(IllegalArgumentException e) {
log.error("[exceptionHandle] ex", e);
ErrorResult errorResult = new ErrorResult("BAD-REQ", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
.
.
.
ApiExceptionTest
에서 IllegalArgumentException
가 발생한다면 , illegalExHandle
가 예외를 처리해 줄 것이다.@ExceptionHandler
에 선언된 예외가 파라미터와 동일한 경우 컨트롤러의 예외는 생략 가능하다. @ControllerAdvice
와 같은 글로벌 처리로 해결 가능하다. @ExceptionHandler
를 사용하면 컨트롤러에 정상 로직과 예외로직이 같이 사용하게 되고 , 컨트롤러가 증가할 때마다 처리해야할 수가 많아진다.
➡ @ControllerAdvice
& @RestControllerAdvice
를 이용하여 해결이 가능하다.
@ControllerAdvice
는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler
, @InitBinder
기능을 부여해주는 역할을 한다 @ControllerAdvice
에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
ExampleAdvice1
은 해당 어노테이션을 타켓ExampleAdvice2
은 패키지를 타겟ExampleAdvice3
은 해당 타입을 타겟예외의 종류와 상황마다 다양한 예외 클래스가 생성이 될 수 있다. Enum을 활용하여 클래스 증가 없이 예외들을 한번에 관리하는 방법을 알아보자.
@Getter
public class BusinessException extends RuntimeException {
// validate 예외 처리 정보를 담기 위해 생성
private final Map<String, String> validation = new HashMap<>();
private final ErrorCode errorCode; // enum..
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public void addValidation(String fieldName, String message) {
validation.put(fieldName, message);
}
}
BusinessException
를 생성한다. @ControllerAdvice
@Slf4j
public class ExceptionController {
// Enum을 활용한 BusinessException
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> customException(BusinessException exception) {
log.warn("error: ",exception);
ErrorResponse body = ErrorResponse.builder()
.code(exception.getErrorCode().getStatus())
.message(exception.getMessage())
.validation(exception.getValidation())
.build();
return ResponseEntity.status(exception.getErrorCode().getStatus())
.body(body);
}
}
@ControllerAdvice
에 BusinessException
를 선언하여 모든 비즈니스 예외를 처리하도록 한다. @Getter
public enum ErrorCode {
//BOARD
BOARD_NOT_EXIST(400, "BOARD NOT EXIST"),
USER_NOT_FOUND(404, "User not found");
private final int status;
private final String message;
ErrorCode(int status, String message) {
this.status = status;
this.message = message;
}
}
public void delete(Long id) {
Optional<Board> result = boardRepository.findById(id);
Board board = result.orElseThrow(() -> new BusinessException(ErrorCode.BOARD_NOT_EXIST));
boardRepository.delete(board);
}
BusinessException
에 해당 Enum 타입을 넘겨 예외처리를 하면 된다.