스프링 부트가 기본으로 제공하는 HandlerExceptionResolver
직접 ExceptionResolver 를 구현하려고 하니 상당히 복잡하다.
스프링 부트는 기본으로 ExceptionResolver를 제공한다.
3가지 ExceptionResolver를 제공한다.
3가지 ExceptionResolver의 우선 순위
1. ExceptionHandlerExceptionResolver # @ExceptionHandler 을 처리한다. API 예외 처리는 대부분 이 기능으로 해결한다.
2. ResponseStatusExceptionResolver # HTTP 상태 코드를 지정해준다. 에너테이션으로 쉽게 상태코드를 바꿀수있다. 500->404
3. DefaultHandlerExceptionResolver # 스프링 내부 기본 예외를 처리한다.
ExceptionHandlerExceptionResolver 는 제일 마지막에 설명하겠다.
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver 는 예외에 따라서 HTTP 상태 코드를 지정해주는 역할을 한다
@ResponseStatus 가 달려있는 예외
또는 ResponseStatusException 예외
의 경우 상태코드를 원하는 것으로 변경해주고 해당 상태코드로 response.sendError() 를 발생시켜주는 리졸버이다.
//사용자 정의 exception에 에너테이션을 활용해서 상태코드 지정
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
public class BadRequestException extends RuntimeException {
}
@GetMapping("/api/response-status-ex1")
public String responseStatusEx1() {
throw new BadRequestException(); //예외 발생
}
원래는 서버에서 처리되지 못한 예외는 모두 500상태코드를 가지지만 ResponseStatusExceptionResolver를 사용하면 내가 지정한 HttpStatus.BAD_REQUEST 즉 400 상태코드로 해당 예외를 다룰수있다.
에너테이션 내부를 살펴보면 입력값을 바탕으로 sendError()를 호출하는것을 볼수있다.
결과적으로 sendError()를 호출했기때문에 WAS에서 다시 오류 페이지( /error )를 내부 요청한다.
단점
개발자가 정의한 exception이 아닌 시스템 내부 예외같은경우엔 적용할수없다.
ResponseStatusException 예외에 대해서 찾아보자
DefaultHandlerExceptionResolver : 스프링 내부 예외를 알아서 처리해주는 리졸버
ExceptionHandlerExceptionResolver
스프링 초보자들은 바로 이 개념만 알면 예외처리 기능을 사용할수있다.
이전 포스트에서 스프링 부트가 기본으로 제공하는 3가지 HandlerExceptionResolver중 가장 중요한 리졸버
라고 설명했었다
예외를 처리할때 크게 2가지 경우를 고려해야한다.
1. 예외페이지로 예외 처리하는 경우
Json이 아닌 Html로 돌려줘도 되는경우 오류가 발생하면 스프링이 제공하는 BasicErrorController 를 사용하는게 편하다.
5xx, 4xx 관련된 오류 페이지를 보여주면 된다.
2. API 예외처리하는 경우
Json으로 돌려줘야하는 API 예외처리의 경우 예외에 따라서 각각 다른 데이터를 출력해야 할 수도 있다. 그리고 같은 예외라고 해도 어떤 컨트롤러에서 발생했는가에 따라서 다른 예외 응답을 내려주어야 할 수 있다
즉 세밀한 제어가 필요하다.
BasicErrorController도 Json 처리를 지원하는 메서드가 있지만 세밀한 제어를 하기엔 기능이 부족하다.
HandlerExceptionResolver를 직접 구현해서 사용하기엔 너무 번거롭다.
비유하자면 스프링의 편리함없이 서블릿으로 WAS를 하나하나 만드는것 같다.
즉, BasicErrorController와 HandlerExceptionResolver 둘다 사용하기 번거롭다.
@ExceptionHandler
스프링은 API 예외 처리 문제를 해결하기 위해 @ExceptionHandler라는 애노테이션을 사용하는 매우 편리한 예외 처리 기능을 제공하는데, 이것이 바로 ExceptionHandlerExceptionResolver 이다.
실무에서 API 예외 처리는 대부분 이 기능을 사용한다.
@ExceptionHandler 예외 처리 방법
@ExceptionHandler 애노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다. 해당 컨트롤러에서 예외가 발생하면 이 메서드가 호출된다.
지정한 예외 또는 그 예외의 자식 클래스는 모두 잡을 수 있다.
우선순위는 항상 자세한것부터 임으로 자식 예외가 있다면 자식먼저 실행된다.
@Controller
public class APItest{
//ExceptionHandler=======================================================================
@ResponseStatus(HttpStatus.BAD_REQUEST) //상태코드도 변경 가능
@ExceptionHandler(IllegalArgumentException.class) //IllegalArgumentException예외에 적용
public ErrorResult illegalExHandle(IllegalArgumentException e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("BAD", e.getMessage()); //일반 컨트롤러처럼 다양한 반환을 가질수있다. responseEntitiy,json,html 등
}
//======================================================================================
@GetMapping("/api2/members/{id}")
public MemberDto getMember(@PathVariable("id") String id) {
if (id.equals("ex")) {
throw new IllegalArgumentException("잘못된 파라미터"); //IllegalArgumentException 발생!
}
}
컨트롤러 내부에서 매핑 메서드 getMember에서 IllegalArgumentException가 발생하면 ExceptionHandler메서드 illegalExHandle이 실행되어 해당 예외를 처리한다.
ResponseStatus를 사용
ResponseStatusExceptionResolver의 @ResponseStatus를 이용해서 함께 반환할 상태코드를 변경할수있다.
실행 흐름
1. 컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
2. 예외가 발생했으로 WAS로 전달되지 않고 대신 ExceptionResolver 가 작동한다. 가장 우선순위가 높은 ExceptionHandlerExceptionResolver 가 실행된다.
3. ExceptionHandlerExceptionResolver 는 해당 컨트롤러에 IllegalArgumentException 을 처리할 수 있는 @ExceptionHandler 가 있는지 확인한다.
4. illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.
5. @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다.
[중요] 4번에서 응답이 Json으로 반환한다고했는데 실제로 컨트롤러처럼 다양한 반환을 가질수있다. (HTML,JSON 모두 가능)
ExceptionHandler 즉, ResponseStatusExceptionResolver의 목적은
WAS까지 예외를 던지지 않고 대신 처리한후 정상흐름으로 바꿔버리는 것
이다.
상태코드 200 OK 이 된다.
ResponseStatus 를 이용해서 원하는 상태코드로 변경해줘도 된다.
예외가 발생해서 서블릿을 넘어 WAS까지 예외가 전달되면 HTTP 상태코드가 500으로 처리된다.
발생하는 예외에 따라서 400, 404 등등 다른 상태코드도 처리하고 싶다.
또는 오류 메시지, 형식등을 API마다 다르게 처리하고 싶다
면 ExceptionHandler 를 사용해서 쉽게 예외처리를 할수있다.
아쉬운 점
위 코드를 보면 ExceptionHandler 코드가 해당 컨트롤러에 같이 섞여있어 핵심로직과 함께 있다는 것이다.
클래스를 분리하고 @ControllerAdvice 또는 @RestControllerAdvice 를 사용하면 따로 분리할수있다.