스프링/4. 스프링 MVC-2

71. 스프링이 자동등록해주는 ExceptionResolver

sdafdq 2023. 9. 15. 22:02

기본적으로 등록되는게 3가지가 있는데,

 

우선순위 순으로

ExceptionHandlerExceptionResolver : @ExceptionHandler 라는 애노테이션이 붙은 걸 처리한다. API는 대부분 이걸로 해결이 된다.

ResponseStatusExceptionResolver : @ResponseStatus(value = HttpStatus.NOT_FOUND) 등 응답 상태 코드를 지정해 준다.

 

DefaultHandlerExceptionResolver : 스프링 내부에서 처리해주는 기본 예외

 

저렇게 다 거친 후에도 처리가 안되면 그제서야 WAS로 간다.

 

 

ResponseStatusExceptionResolver 

이건 예외에 따라 상태코드를 지정해 주는 역할을 한다.

@ResponseStatus가 달려있는 예외거나

ResponseStatusException이라는 예외가 정의되어 있다.

 

위 두가지 경우에 예외를 처리해 준다.

 

 

 

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

}

대충 예외 상속받아서 만들어 주고

 

@GetMapping("/api/response-status-ex1")
public String responseStatusEx1(){
    throw new BadRequestException();
}

컨트롤러에서 예외 뿜게 했다.

 

 

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

}

여기보면 이제 ResponsStatusExceptionResolver 이용하려고 만든 예외에 @ResponseStatus 붙여 놨는데,

안에 code는 상태코드이고, reason은 직역은 이유인데, 에러 메시지 넣을 수 있는거다.

 

 

정의한 바와 같이 400으로 나온다.

 

 

 

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, null, reason, LocaleContextHolder.getLocale()) :
                reason);
        response.sendError(statusCode, resolvedReason);
    }
    return new ModelAndView();
}

이게 실제 스프링에서 제공해주는 ResponsStatusExceptionResolver 클래스 안에 있는 코드인데,

 

보면 그냥 response.sendError(상태코드, 메시지) 해주고

비어있는 ModelAndView를 반환하여 WAS가 정상으로 처리하게끔 한다. 물론 sendError()기 때문에 WAS가 /error을 호출하긴 한다.

 

 

protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
        HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {

    int statusCode = responseStatus.code().value();
    String reason = responseStatus.reason();
    return applyStatusAndReason(statusCode, reason, response);
}

안에 또 있는 코드인데, ResponseStatus 저걸로 애노테이션 확인하는 거다.

 

 

참고로,

@ResponseStatus( code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {

}

여기서 reason을 문자열 말고 아예 messages.properties에 메시지에 지정하고 그걸 호출해서 쓸 수도 있다.

 

이렇게 할 수 있는 이유는, 위에 ResponseStatusExceptionResolver 정의한 코드 들 중에 sendError() 하는 코드 보면 그 전에 먼저 저 reason의 문자열에 대해 정의한 것이 있는지 messageSource에서 뒤져본다.

 

@Override
public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
}

이렇게 되어 있는거보니까 밖에서 주입 받는 듯?

 

 

 

 

 

근데 @ResponseStatus는 봐서 알겠지만 우리가 정의한 에러에 직접 붙여주는거기 때문에, 내가 수정할 수 없는 정의된 예외에 대해서는 적용할 수 없다,

추가로 애노테이션으로 사용하는 거기 때문에 조건에 따라 동적으로 변경하는 것도 어렵다.

 

이 때는 ResponseStatusException을 사용하면 된다.

 

@GetMapping("/api/response-status-ex2")
public String responseStatusEx2(){
    throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
}

굉장히 쉽다. 그냥 조건부에 따라 예외 날려주면 된다.

throw new ResponseStatusException(상태코드, 메시지, 원래에러)를 넣어준다.