2 분 소요

이전글에서는 에러 발생 시 HTTP 상태 코드에 따라 맞는 오류 페이지(HTML)을 제공해주면 됐었다. 하지만 API는
각 API마다 그리고 각 컨트롤러에서 오류 상황에 맞는 응답을 해줘야 하고 json 형태로 내보내야 해서 복잡하다.
이런 상황에서 스프링에서 제공하는 기능으로는 어떻게 처리할 수 있는지 알아보자!

@ResponseStatus

controller에서 RuntimeException이 발생하고 적절히 처리해주지 못해 밖으로 나간다면 상태 코드가 500으로
나갈 것이다. RuntimeException을 상속받은 BadRequestException이 처리되지 못해도 똑같은 상황이 된다.
이 때 500이 아니라 다른 상태 코드로 변경하고 싶다면 다음과 같이 @ResponseStatus를 설정해주면 된다!
BAD_REQUEST400을 의미하므로 최종적으로 500이 아닌 400으로 응답이 나가게 된다.

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
  public class BadRequestException extends RuntimeException {
}
  • code : 변경할 HTTP 상태 코드
  • reason : 내보낼 메시지
    • 메시지를 직접 작성해도 좋고, 국제화도 적용이 가능하다

하지만 @ResponseStatus은 직접 코드를 수정할 수 없는 경우(라이브러리 등)에는 적용할 수 없다. 이 때는 다음과
같이 ResponseStatusException를 적용해주자.

@GetMapping("/api/response-status-example")
public String responseStatusExample() {
    throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "잘못된 요청 오류", new RuntimeException());
}


DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver는 스프링 내에서 발생하는 스프링 예외를 해결한다. 예를 들면 파라미터 타입
입략이 잘못된 경우 사용자의 잘못으로 400 오류로 나가야 하므로 스프링에서 500이 아닌 400 오류로 나가도록 처리
해주는걸 말한다.

@ExceptionHandler

스프링에서 API 예외 처리를 하기위해 @ExceptionHandler라는 애노테이션을 제공한다.
아래 객체를 API 응답으로 내보낸다고 해보자.

@Data
@AllArgsConstructor
public class ErrorResult {
    private String code;
    private String message;
}


그리고 IllegalArgumentException를 처리해주는 핸들러를 정의해주고 위에서 정의한 ErrorResult를 반환해줄 때,
code에는 BAD가, message에는 파라미터로 들어오는 에러 메시지(e.getMessage())가 들어가게 된다.
이 때 @ExceptionHandler를 사용해서 IllegalArgumentException 예외를 처리해주기 때문에 @ResponseStatus
없다면 정상 처리로 상태 코드가 200으로 나가게 된다. 하지만 여기선 BAD_REQUEST로 설정돼서 400 오류로 나간다!

//IllegalArgumentException 처리 핸들러
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
    return new ErrorResult("BAD", e.getMessage());
}

//응답 결과
{
  "code": "BAD",
  "message": "잘못된 입력 값"
}


일반적인 RestController에서 처리하는 것처럼 정의할 수도 있다.

@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {
    ErrorResult errorResult = new ErrorResult("USER-EXCEPTION", e.getMessage());
    return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
  • @ExceptionHandler에 처리할 예외를 적지 않으면 파라미터에 있는 예외를 알아서 처리한다.

@ExceptionHandler를 활용해 처리할 예외 중 서로 부모-자식 관계인 경우 더 자세한 예외인 자식 예외를 먼저
처리한다. 즉, 더 자세한 쪽이 우선순위를 갖는다!

@ControllerAdvice

controller에 정상 코드와 예외 처리 코드가 섞여있으면 복잡하다. 이를 @ControllerAdvice로 분리할 수 있다!
새 class를 만들고 여기에 모든 예외 처리 코드를 가져와보자. (기존 controller에선 예외 처리 코드를 삭제하자!)
그리고 @RestControllerAdvice를 붙여주면 RestController에 있던 예외들을 모두 처리할 수 있다.
아래는 hello.exception.api 패키지에 속한 RestController들에 대해 적용한 것이다.

@RestControllerAdvice(basePackages = "hello.exception.api")
public class ExControllerAdvice {

  //IllegalArgumentException 처리 핸들러
  //RuntimeException 처리 핸들러
  //Exception 처리 핸들러
  //...
}
  • @ControllerAdvice@RestControllerAdvice 차이는 @Controller@RestController의 차이와 같다.
    • @ResponseBody가 붙는지 여부만 다르다.

controller 지정 방법은 4가지가 있다.

  • @ControllerAdvice : 글로벌(=모든 controller)
  • @ControllerAdvice(annotations = RestController.class) : @RestController가 붙는 모든 controller
  • @ControllerAdvice("org.example.controllers") : 해당 패키지 내 모든 controller
  • @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) : 특정 class

카테고리:

업데이트:

댓글남기기