Api 예외처리

ys·2024년 3월 14일
  • html + thymleaf를 이용한 오류 페이지.html을 이용하는 방법은, 단순히 오류페이즈를 화면에 전달하는 방식이다
  • Api는 각 오류 상황에 맞게 오류 응답의 스펙을 정의해야 하고, Json형식으로 데이터를 전달해야 한다

Api는 각 시스템마다 응답 모양도 다르고, Api스펙도 모두 다르다

  • 다음과 같은 형식으로 공통 Api 스펙을 정의하였다고 가정하자
  • result라는 이름의 결과의 상태들을 나타내는 부분
    • result_code : http 상태 코드
    • result_message : 결과 메시지
    • result_description : 결과에 대한 짧은 설명
  • body 부분에 내가 response로 보내는 data를 @RestController을 통해 직렬화를 해서 body라는 이름으로 담아서 보내준다

🤔같은 NPE여도, controller가 다르다면 ???

  • 예를 들어 Null_Point_Exception이 일어나는데
  • 컨트롤럴에 따라, 어디서는 ArticleControllerUserController에서 일어났다고 가정을 해본다면, 두 경우 다른 Api메시지가 나가야 된다
  • 오류가 나면, body부분에 데이터가 나가지 않고 result로 정보만 준다고 합의되었다고 가정해본다면...
  • ✅같은 NPE여도 응답 Api response의 result메시지 부분은 달라야 한다

  • okay! 이제 Api스펙을 다르게 정의해야 하는 것을 알게되었다

🤔 Spring은 어디서 예외처리를 할까 ???

  • 웹 서버에서 예외가 발생한다고 해서, 바로 그 순간 코드가 멈추지 않는다
  • 그럼 대체 어디서 예외가 어디로 가는 것일까?
  • Spring MVC에서 ✅컨트롤러 밖으로 예외가 던져진 경우 예외를 해결하고, 동작 방식을 변경하고 싶으면 ✅HandlerExceptionResolver를 사용하면 된다
  • 줄여서 ExceptionResolver 이라고 한다
  • 처음 request 후 예외 발생 -> 모든 예외는 Exception Handler로 모이게 된다
  • 이곳에서 예외 처리 -> 사용자에게 지정한 Api 스펙으로 response를 내려줄 수 있다

1.ErrorCode 각각 정의

  • 먼저 ErrorCode를 Api요청이 다르게 해줘야 할 것들 마다 지정을 해준다
  • 그런데 앞서 같은 NPE여도, Entity마다 다른 ErrorCode가 되므로
  • 인터페이스를 이용해서 ✅SolidOCP원칙을 지키자!!

ErrorCodeIfs

public interface ErrorCodeIfs {
    Integer getHttpStatusCode();  // 실제 httpStatusCode
    Integer getErrorCode();  // 이번 프로젝트에서 사용할 code
    String getDescription(); // 설명
}
  • 필요한 Error들을 구현한 후, 그 안에서 에러들을 Enum type으로 만들어서 사용한다
  • ErrorCode를 이용해서, Api스펙의 result부분을 만들어 Api스펙을 정의한다

2. Exception을 만든다

  • Api 스펙이 다 만들어졌으므로, Exception을 만든다
  • 해당 Exception이 발생해서 -> Exception Handler로 전달된다면
  • 각각 예외에 맞게 예외처리를 해준다
    • 해당 예외에 대한 에러 정보를 담고, 약속된 Api스펙에 따라 만들어진 Api스펙을 사용한다
  • 모두 ✅RuntimeException상속 받고, 인터페이스를 구현한다
  • 인터페이스로 먼저 예외를 만든 후, 다른 예외에 따라 상황에 맞게 확장을 할 수 있다
  • ✅Solid의 OCP를 염두해둔 설계이다!!!

3. 상황에 맞게 컨트롤러에서, 예외 처리부분을 만든다

  • @ExceptionHandler어노테이션을 이용해서,
  • 해당 컨트롤러에서 지정한 예외가 터진다면 예외 처리 부분의 메서드가 실행된다
  • 이때, 지정한 예외의 자식 클래스는 모두 같은 ExceptionHandler을 사용할 수 있다
  • 하지만...
  • 컨트롤러의 코드가 복잡해진다
  • 컨트롤러 레벨에, 정상 처리 로직, 예외 처리 로직이 같이 있다
  • Solid의 SRP원칙 위배!!!

4.✅@ContollerAdvice로 정상 코드, 예외 처리 코드 분리

  • 3번에서 말했던 SRP원칙이 위배되는 것을 해결하기 위해서
  • @ControllerAdvice를 사용한다

@ControllerAdvice

  • ControllerAdvice는 대상으로 지정한 여러 컨트롤러에서 @ExceptionHandler, @InitBinder 기능을 부여해주는 역할을 한다
  • @ControllerAdvice에 대상을 지정하지 않으면, 모든 컨트롤러에 적용된다(global 기준)
  • @RestContollerAdvice = @ControllerAdvice + @ResponseBody
  • 파라미터로는
      1. 어노테이션을 기준으로
      1. 패키지명을 직접 지정
      1. 부모, 특정 컨트롤러를 직접 지정 할 수 있다

@ExceptionHandler@ControllerAdvice조합하면 예외를 깔끔하게 처리할 수 있다

globalExceptionHandler

@Slf4j
@RestControllerAdvice
@Order(value = Integer.MAX_VALUE)
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<Api<Object>> exception(Exception e){

        log.error("",e);
        return ResponseEntity
                .status(500)
                .body(
                        Api.Error(ErrorCode.SERVER_ERROR)
                );
    }
}
  • 로그 트레이스를 찍어서, 나중에 어떤 예외인지 확인 할 수 있다
  • @Order : 순서를 지정, 작을 수록 우선순위
  • ✅해당 예외 발생 -> Exception Handler로 이동 -> 로그 찍기 -> 지정해둔 Api 스펙대로 오류 정보를 담아서, 응답

ApiExceptionHandler

@Slf4j
@RestControllerAdvice
@Order(value = Integer.MIN_VALUE)
public class ApiExceptionHandler {

    @ExceptionHandler(value = ApiException.class)
    public ResponseEntity<Api<Object>> apiException(ApiException apiException){

        log.error("", apiException);

        ErrorCodeIfs errorCodeIfs = apiException.getErrorCodeIfs();

        return ResponseEntity
                .status(errorCodeIfs.getHttpStatusCode())
                .body(
                        Api.Error(errorCodeIfs, apiException.getErrorDescription())
                );
    }
}
  • ContollerAdvice를 사용하면, 해당 예외가 발생했을때, @ExceptionHandler가 있는 메서드로
  • ✅Spring이 해당 예외를 넘겨준다
  • ApiExceptionHandler에서는 @ExceptionHandler의 ApiException이 발생하면
  • 해당 ApiException@ExceptionHandler가 붙어있는 레벨의 메서드인 apiException 메서드에 전달해준다
  • 해당 메서드에서 로그를 찍고, 정의해둔 Api공통 스펙을 담아서 전달해준다
profile
개발 공부,정리

0개의 댓글