Spring API 예외처리

ForLearn·2022년 11월 9일
0

스프링 MVC

목록 보기
1/1

API 예외의 경우 일반적인 예외 화면을 뿌리는 것이 아니라, JSON 오류 메시지를 반환해줘야 한다. 스프링은 @ExceptionHandler를 제공하고 이를 통해 간편한 API 예외처리를 할 수 있다.

@ExceptionHandler

@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을 활용한 예외처리

예외의 종류와 상황마다 다양한 예외 클래스가 생성이 될 수 있다. Enum을 활용하여 클래스 증가 없이 예외들을 한번에 관리하는 방법을 알아보자.

BusinessException

@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 정의

@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);
    }
}
  • @ControllerAdviceBusinessException를 선언하여 모든 비즈니스 예외를 처리하도록 한다.

예외 관리를 위한 Enum 생성

@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;
    }
}
  • 새로운 예외가 추가되면 Enum을 추가적으로 정의하여 사용한다.

사용 예시

 public void delete(Long id) {
        Optional<Board> result = boardRepository.findById(id);
        Board board = result.orElseThrow(() ->  new BusinessException(ErrorCode.BOARD_NOT_EXIST));

        boardRepository.delete(board);
    }
  • 위의 예시는 게시글을 삭제시 넘어온 ID에 해당하는 게시글이 없는 경우 예외를 처리하는 코드이다.
  • 위처럼 BusinessException 에 해당 Enum 타입을 넘겨 예외처리를 하면 된다.

Reference

0개의 댓글