API 예외처리

정병웅·2024년 8월 29일
0

Spring 강의

목록 보기
6/6

이번에는 스프링의 예외처리에 대해서 정리하려고 한다.
실무에서는 정상 동작은 당연히 되어야하는 것이고, 서비스를 제공할 때, 예외도 함께 고려되어야한다.
예상치 못한 에러가 발생 되어도 서비스 운영에는 문제가 없어야하기 때문이다.
아직도 예외처리에 대해서 완벽히 알지는 못하지만 기본적인 흐름에 대해서는 정리가 됐다 🤔

ExceptionResovler를 통한 Spring Exception 처리 플로우

@ExceptionHandler 사용 이유

  1. API 예외 처리 문제를 해결하기 위함.
  2. 다양한 파라미터와 응답을 지정 할 수 있다.

구현 예제

ErrorResult 클래스
예외 발생 시 API 응답으로 사용할 객체

@Data
@AllArgsConstructor
public class ErrorResult {

  private String code;
  private String message;
}

ApiExceptionV2Controller
컨트롤러 역할을 하기 위한 객체

@Slf4j
@RestController
public class ApiExceptionV2Controller {

  @ExceptionHandler(IllegalArgumentException.class)
  public ErrorResult illegalArgumentException(IllegalArgumentException e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("BAD", e.getMessage());
  }

  @ExceptionHandler
  public ResponseEntity<ErrorResult> userExHandler(UserException e) {
    log.error("[exceptionHandler] ex", e);
    ErrorResult result = new ErrorResult("USER-EX", e.getMessage());
    return new ResponseEntity(result, HttpStatus.BAD_REQUEST);
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler
  public ErrorResult exHandler(Exception e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("EX", "내부오류");
  }
  
  @GetMapping("/api2/members/{id}")
  public MemberDto getMember(@PathVariable("id") String id) {

    if (id.equals("ex")) {
      throw new RuntimeException("잘못된 사용자");
    }
    if (id.equals("bad")) {
      throw new IllegalStateException("잘못된 입력 값");
    }
    if (id.equals("user-ex")) {
      throw new UserException("사용자 오류");
    }
    return new MemberDto(id, "hello" + id);
  }

  @Data
  @AllArgsConstructor
  private class MemberDto {

    private String memberId;
    private String name;
  }
}

코드 설명

  1. 컨트롤러 호출 결과로 IllegalStateException 예외가 컨트롤러 밖으로 던져짐.
  2. 예외 발생 되었기 때문에 ExceptionResolver 작동.
    가장 우선 순위가 높은 ExceptionHandlerExceptionResolver 실행.
  3. ExceptionHandlerExceptionResolver 에서 해당 컨트롤러에 IllegalStateException 처리할 @ExceptionHandler 탐색
  4. illegalArgumentException 실행
  5. @RestController 애노테이션으로 인해 응답이 JSON으로 컨버팅

@ExceptionHandler 애노테이션을 선언하면 해당 컨트롤러에서 처리할 예외들을 지정해서 처리가 가능하다.

  1. throw new IllegalStateException("잘못된 입력 값") => 에러 발생 시 @ExceptionHandler(IllegalArgumentException.class) 로 선언한 illegalArgumentException() 메서드에서 해당 에러를 처리 할 수 있다.

  2. @ResponseStatus 애노테이션을 사용할 경우 해당 에러의 Http 상태 값을 원하는 상태로 변경할 수 있다.

@ControllerAdvice

사용 이유

위의 컨트롤러를 확인해보면 해당 @ExceptionResolver가 해당 컨트롤러에서만 사용할 수 있게 되어있다.
매번 컨트롤러마다 동일한 에러 처리를 하는건 상당히 귀찮다,,,(개발자는 귀찮아 해야함,,,)
@ControllerAdvice를 이용하면 @ExceptionResolver를 한번만 생성하고, 여러 컨트롤러에서 사용할 수 있다!!

예시 코드

ExControllerAdvice 클래스
@ExceptionHandler를 모아놓은 클래스

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

  @ExceptionHandler(IllegalArgumentException.class)
  public ErrorResult IllegalArgumentException(IllegalArgumentException e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("BAD", e.getMessage());
  }

  @ExceptionHandler
  public ResponseEntity<ErrorResult> userExHandler(UserException e) {
    log.error("[exceptionHandler] ex", e);
    ErrorResult result = new ErrorResult("USER-EX", e.getMessage());
    return new ResponseEntity(result, HttpStatus.BAD_REQUEST);
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler
  public ErrorResult exHandler(Exception e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("EX", "내부오류");
  }

}

ApiExceptionV2Controller
컨트롤러 클래스

@Slf4j
@RestController
public class ApiExceptionV2Controller {

  @GetMapping("/api2/members/{id}")
  public MemberDto getMember(@PathVariable("id") String id) {

    if (id.equals("ex")) {
      throw new RuntimeException("잘못된 사용자");
    }
    if (id.equals("bad")) {
      throw new IllegalStateException("잘못된 입력 값");
    }
    if (id.equals("user-ex")) {
      throw new UserException("사용자 오류");
    }
    return new MemberDto(id, "hello" + id);
  }

  @Data
  @AllArgsConstructor
  private class MemberDto {

    private String memberId;
    private String name;
  }
}

코드 설명

@ControllerAdvice는 지정한 여러 컨트롤러에 @ExceptionHandler, @InitBinder 기능을 사용할 수 있게 함.
대상을 지정하지 않으면 모든 컨트롤러에 적용 됨.

대상 컨트롤러 지정 예시

 // 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 {}
profile
인생은 IT 노가다

0개의 댓글