스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - (48) 스프링 부트 - API 오류 처리 - 스프링이 제공하는 ExceptionResolver
인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.
API 예외 처리 - 스프링이 제공하는 ExceptionResolver 1
스프링 부트가 기본으로 제공하는 ExceptionResolver는 다음과 같다.
HandlerExceptionResolverComposite에 다음 순서대로 등록
ExceptionHandlerExceptionResolverResponseStatusExceptionResolverDefaultHandlerExceptionResolver→ 우선 순위 가장 낮음
ExceptionHandlerExceptionResolver
@ExceptionHandler 을 처리한다. API 예외 처리는 대부분 이 기능으로 해결한다.
매우 중요한 기능이기 때문에 따로 정리
ResponseStatusExceptionResolver
HTTP 상태 코드를 지정해준다.
예) @ResponseStatus(value = HttpStatus.NOT_FOUND)
DefaultHandlerExceptionResolver
스프링 내부 기본 예외를 처리한다.
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver는 예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다.
다음 두 가지 경우를 처리한다.
@ResponseStatus가 달려 있는 예외ResponseStatusException예외
hello.exception.exception.BadRequestException
1
2
3
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}
다음과 같이 Exception 하나를 만들었다.
1
2
3
4
@GetMapping("/api/response-status-ex1")
public String responseStatusEx1() {
throw new BadRequestException();
}
컨트롤러 하나 만들어서 호출 해보자.

400 에러가 잘 나온다.
BadRequestException예외가 컨트롤러 밖으로 넘어가면 ResponseStatusExceptionResolver예외가 해당 애노테이션을 확인해서 오류 코드를 HttpStatus.BAD_REQUEST(400)으로 변경하고, 메시지도 남긴다.
1
2
3
4
5
6
7
8
9
10
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) throws IOException {
if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
} else {
String resolvedReason = this.messageSource != null ? this.messageSource.getMessage(reason, (Object[])null, reason, LocaleContextHolder.getLocale()) : reason;
response.sendError(statusCode, resolvedReason);
}
return new ModelAndView();
}
ResponseStatusExceptionResolver에 applyStatusAndReason를 보면
결국 마지막에 response.sendError(statusCode, resolvedReason)를 호출하는 걸 확인할 수 있다.
1
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
reason = "잘못된 요청 오류" 메시지 기능도 있다.
application.propeties 에 server.error.include-message=always가 되어 있어야 함.
요청을 http://localhost:8080/api/response-status-ex1?message= 으로 해보면

다음과 같이 담아지며 messages.properties를 만들어서 따로 관리할 수도 있다.
1
2
3
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {
}
messages.properties
1
error.bad=잘못된 요청입니다. 메시지 사용
다음과 같이 세팅해 놓으면

지정 해 놓은 메시지가 출력 되는 것을 확인 가능.
ResponseStatusException
@ResponseStatus는 개발자가 직접 변경할 수 없는 예외에는 적용할 수 없다. (애노테이션을 직접 넣어야 하는데, 내가 코드를 수정할 수 없는 라이브러리의 예외 코드 같은 곳에는 적용할 수 없다.)
추가로 애노테이션을 사용하기 때문에 조건에 따라 동적으로 변경하는 것도 어렵다.
이때는 ResponseStatusException를 사용하면 된다.
1
2
3
4
@GetMapping("/api/response-status-ex2")
public String responseStatusEx2() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
}
다음과 같이 컨트롤러에서 ResponseStatusException를 호출 했는데, 여기서 바로 status랑 메시지, 오류를 넣어줄 수 있다.

status 가 404로 잘 나오는 걸 확인할 수 있다.
API 예외 처리 - 스프링이 제공하는 ExceptionResolver2
이번에는 DefaultHandlerExceptionResolver를 살펴보자.
DefaultHandlerExceptionResolver는 스프링 내부에서 발생하는 스프링 예외를 해결한다.
대표적으로 파라미터 바인딩 시점에 타입이 맞지 않으면 내부에서 TypeMismatchException이 발생하는데, 이 경우 예외가 발생했기 때문에 그냥 두면 서블릿 컨테이너까지 올라가고, 결과적으로 500 오류가 발생한다.
그런데 파라미터 바인딩은 대부분 클라이언트가 잘못 호출해서 발생한 오류이기 때문에 400 오류를 리턴하는게 맞다.
1
2
3
4
@GetMapping("/api/default-handler-ex")
public String defaultException(@RequestParam(name = "data") Integer data) {
return "ok";
}
다음과 같이 컨트롤러를 만들었고, 숫자를 받도록 만든 다음에 문자를 넘겨 봤다.

원래라면 서블릿 타고 500 에러가 나와야 하는데, 400 Bad Request가 나온다.
DefaultHandlerExceptionResolver가 처리해 준 것 이다.

로그에도 친절히 나와있다.

DefaultHandlerExceptionResolver 코드를 보면은 엄청 많은 오류를 체크 하고 있고
TypeMismatchException도 있는 걸 확인 할 수 있다.
댓글남기기