스프링 부트가 기본으로 제공하는 ExceptionResolver는 다음과 같다. (HandlerExceptionResolverComposite에 다음 순서로 등록)
ExceptionHandlerExceptionResolver은 @ExceptionHandler을 처리한다. API 예외 처리는 대부분 이 기능으로 해결한다. @ExceptionHandler 애노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다.
해당 컨트롤러에서 예외가 발생하면 이 메서드가 호출된다. 참고로 지정한 예외 또는 그 예외의 자식 클래스까지 모두 잡을 수 있다. 다음 예제는 IllegalArgumentException 또는 그 하위 자식 클래스를 모두 처리할 수 있다.
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
다음과 같이 다양한 예외를 한번에 처리할 수 있다.
@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
log.info("exception e", e);
}
@ExceptionHandler에 예외를 생략할 수 있다. 생략하면 메서드 파라미터의 예외가 지정된다.
//@ExceptionHandler(UserException e) - (UserException e)는 생략 가능
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
...json
{
"code": "BAD",
"message": "잘못된 입력 값"
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER_EX", e.getMessage());
return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
앞서 살펴본 @ResponseStatus는 애노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다.
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
예외에 따라서 HTTP 상태 코드를 지정해준다. @ResponseStatus가 달려있는 예외이거나, ResponseStatusException 예외를 처리한다. 예외에 다음과 같이 @ResponseStatus 애노테이션을 적용하면 HTTP 상태 코드를 변경해준다.
BadRequestException 예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver 예외가 해당 애노테이션을 확인해서 오류 코드를 HttpStatus.BAD_REQUEST (400)으로 변경하고, 메시지도 담는다.
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}
@GetMapping("/api/response-status-ex1")
public String responseStatusEx1() {
throw new BadRequestException();
}
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "잘못된 요청 오류",
"path": "/api/response-status-ex1"
}
ResponseStatusExceptionResolver는 reason을 MessageSource에서 찾는 기능도 제공한다.
//@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {
}
error.bad=잘못된 요청 오류입니다. 메시지 사용
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "잘못된 요청 오류입니다. 메시지 사용",
"path": "/api/response-status-ex1"
}
@ResponseStatus 는 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다. 애노테이션을 직접 넣어야 하는데, 내가 코드를 수정할 수 없는 라이브러리의 예외 코드 같은 곳에는 적용할 수 없다.
추가로 애노테이션을 사용하기 때문에 조건에 따라 동적으로 변경하는 것도 어렵다. 이때는 ResponseStatusException 예외를 사용하면 된다.
@GetMapping("/api/response-status-ex2")
public String responseStatusEx2() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
}
{
"status": 404,
"error": "Not Found",
"exception": "org.springframework.web.server.ResponseStatusException",
"message": "잘못된 요청 오류입니다. 메시지 사용",
"path": "/api/response-status-ex2"
}
DefaultHandlerExceptionResoler는 스프링 내부 기본 예외를 처리한다. 스프링 내부에서 터진 예외에 따라서 적절한 상태코드를 넣어서 해결해준다.
참조
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2