[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
댓글남기기