Spring - ExceptionResolver

박민수·2023년 11월 14일

Spring

목록 보기
16/46
post-thumbnail

ExceptionResolver

스프링 부트가 기본으로 제공하는 ExceptionResolver는 다음과 같다. (HandlerExceptionResolverComposite에 다음 순서로 등록)

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResoler

ExceptionHandlerExceptionResolver

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) {}

실행 흐름 및 사용 예시 (1)

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("BAD", e.getMessage());
}
  1. IllegalArgumenException 예외 발생
  2. 예외가 발생했으므로 ExceptionResolver가 작동한다.
  3. 가장 우선순위가 높은 ExceptionHandlerExceptionResolver가 실행된다.
  4. ExceptionHandlerExceptionResolver는 해당 컨트롤러에 예외를 처리할 수 있는 @ExceptionHandler가 있는지 확인한다.
  5. illegalExHandle()이 실행된다. @RestController 이므로 illegalExHandle()에도 @ResponseBody가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.
  6. @ResponseStatus(HttpStatus.BAD_REQUEST)를 지정했으므로 HTTP 상태 코드 400으로 응답한다.

실행 결과

...json
{
    "code": "BAD",
    "message": "잘못된 입력 값"
}

사용 예시 (2)

@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);
}
  • ResponseEntity를 사용해서 HTTP 메시지 바디에 직접 응답한다.
  • ResponseEntity를 사용하면 HTTP 응답 코드를 프로그래밍해서 동적으로 변경할 수 있다.

앞서 살펴본 @ResponseStatus는 애노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다.

사용 예시 (3)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
    log.error("[exceptionHandler] ex", e);
    return new ErrorResult("EX", "내부 오류");
}
  • RuntimeException은 Exception의 자식 클래스이므로, RuntimeException 예외가 발생한 경우 이 메서드가 호출된다.
  • @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)에 의해 HTTP 상태 코드 500으로 응답한다.

ResponseStatusExceptionResolver

예외에 따라서 HTTP 상태 코드를 지정해준다. @ResponseStatus가 달려있는 예외이거나, ResponseStatusException 예외를 처리한다. 예외에 다음과 같이 @ResponseStatus 애노테이션을 적용하면 HTTP 상태 코드를 변경해준다.

BadRequestException 예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver 예외가 해당 애노테이션을 확인해서 오류 코드를 HttpStatus.BAD_REQUEST (400)으로 변경하고, 메시지도 담는다.

@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}

ApiExceptionController - 추가

@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 {
}

messages.properties

error.bad=잘못된 요청 오류입니다. 메시지 사용

메시지 사용 결과

{
     "status": 400,
     "error": "Bad Request",
     "exception": "hello.exception.exception.BadRequestException",
     "message": "잘못된 요청 오류입니다. 메시지 사용",
     "path": "/api/response-status-ex1"
}

ResponseStatusException

@ResponseStatus 는 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다. 애노테이션을 직접 넣어야 하는데, 내가 코드를 수정할 수 없는 라이브러리의 예외 코드 같은 곳에는 적용할 수 없다.
추가로 애노테이션을 사용하기 때문에 조건에 따라 동적으로 변경하는 것도 어렵다. 이때는 ResponseStatusException 예외를 사용하면 된다.

ApiExceptionController

@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

DefaultHandlerExceptionResoler는 스프링 내부 기본 예외를 처리한다. 스프링 내부에서 터진 예외에 따라서 적절한 상태코드를 넣어서 해결해준다.


참조
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2

profile
안녕하세요 백엔드 개발자입니다.

0개의 댓글