[Spring] API 예외 처리
이전글에서는 에러 발생 시 HTTP 상태 코드에 따라 맞는 오류 페이지(HTML)을 제공해주면 됐었다. 하지만 API는
각 API마다 그리고 각 컨트롤러에서 오류 상황에 맞는 응답을 해줘야 하고 json 형태로 내보내야 해서 복잡하다.
이런 상황에서 스프링에서 제공하는 기능으로는 어떻게 처리할 수 있는지 알아보자!
@ResponseStatus
controller에서 RuntimeException이 발생하고 적절히 처리해주지 못해 밖으로 나간다면 상태 코드가 500으로
나갈 것이다. RuntimeException을 상속받은 BadRequestException이 처리되지 못해도 똑같은 상황이 된다.
이 때 500이 아니라 다른 상태 코드로 변경하고 싶다면 다음과 같이 @ResponseStatus를 설정해주면 된다!
BAD_REQUEST는 400을 의미하므로 최종적으로 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
댓글남기기