Spring

Spring ExceptionHandler, ResponseStatus 동작 과정

자바생 2023. 4. 22. 21:33
728x90

글을 쓰게 된 이유

 

 

@ExceptionHandler, @RestControllerAdvice, @ControllerAdvice, @ResponseStatus를 사용하여 스프링에서 exception 처리하는 방법을 알게 됐습니다.

 

 

 

해당 어노테이션들이 어떻게 동작이 되는지, 처리되는지 알아보고자 디버깅을 해보았습니다.

 

 

어디에서 breakpoint?

 

 

공식문서에서 아래와 같이 DispatcherServlet 안에서 HandlerExceptionResolver 를 통해서 처리된다고 나와있기 때문에 breakpoint를 쉽게 찾을 수 있었습니다.

 

 

Support for @ExceptionHandlermethods in Spring MVC is built on the DispatcherServlet level,  HandlerExceptionResolver mechanism.

 

 

그래서 바로 HandlerExceptionResolver를 들어갔습니다.

 

 

 

ExceptionResolver 클래스 다이어그램

 

 

 

 

 

HandlerExceptionResolver를 구현하고 있는 xxxExceptionResolver들입니다.

 

이 중에 xxxComposite 가 눈에 띕니다. 

 

저번에 컨트롤러에서 요청받은 데이터를 바인딩해주는 xxxArgumentResolver에서도 xxxComposite가 있었기 때문입니다.

 

 

 

 

HandlerExceptionResolver

 

HandlerExceptionResolver는 DispatcherServlet 레벨에서 발생하는 Exception을 핸들링하는 인터페이스입니다.

 

public interface HandlerExceptionResolver {
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

 

 

HandlerExceptionResolverComposite

 

 

HandlerExceptionResolverComposite 를 보도록 하겠습니다.

 

xxxComposite는 말 그대로 ExceptionResolver 들을 List로 가진 합성 클래스입니다.

 

 

HandlerExceptionResolver를 구현하고 있고, 오버라이딩 된 메서드에서 ExceptionResolver들을 순회하면서 처리해 줍니다.

 

 

 

@Override
@Nullable
public ModelAndView resolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

	if (this.resolvers != null) {
		for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
			ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (mav != null) {
				return mav;
			}
		}
	}
	return null;
}

 

 

this.resolvers는 List<HandlerExceptionHandler>를 의미합니다.

 

 

해당 List는 3개의 exceptionHandler를 가지고 있습니다. 즉, 스프링이 제공해 주는 ExceptionHandler는 3개가 있다는 뜻이죠.

 

 

list의 순서는 ExceptionResolver 우선순위를 뜻합니다.

 

순차적으로 ExceptionResolver를 통해 조건을 만족한다면 바로 return을 하기 때문이죠.

 

 

그래서 ExceptionHandlerExceptionResolver → ResponseStatusExceptionResovler → DefaultHandlerExceptionResolver 순으로 ExceptionResolver가 동작하게 됩니다.

 

 

 

이제 각 클래스가 어떤 일을 하는지 알아보겠습니다.

 

 

 

 

스프링이 제공하는 ExceptionResolver

 

 

 

ExceptionHandlerExceptionResolver

 

해당 ExceptionResolver는 AbstractHandlerMethodExceptionResolver를 상속받고 있습니다.

 

 

An AbstractHandlerMethodExceptionResolver that resolves exceptions through @ExceptionHandler methods.

 

 

@ExceptionHandler 어노테이션이 붙은 메서드의 exception을 처리합니다.

 

 

ResponseStatusExceptionResolver

 

 

A HandlerExceptionResolver that uses the @ResponseStatus annotation to map exceptions to HTTP status codes. This exception resolver is enabled by default in the DispatcherServlet and the MVC Java config and the MVC namespace. As of 4.2 this resolver also looks recursively for @ResponseStatus present on cause exceptions, and as of 4.2.2 this resolver supports attribute overrides for @ResponseStatus in custom composed annotations. As of 5.0 this resolver also supports ResponseStatusException.

 

 

ResponseStatusExceptionHandler는 @ResponseStatus 어노테이션을 사용하면 동작합니다.

 

HTTP 상태 코드 및 이유에 대해서 처리를 해줍니다.

 

 

 

 

DefaultHandlerExceptionResolver

 

 

The default implementation of the org.springframework.web.servlet.HandlerExceptionResolver interface, resolving standard Spring MVC exceptions and translating them to corresponding HTTP status codes. This exception resolver is enabled by default in the common Spring org.springframework.web.servlet.DispatcherServlet.

 

 

 

HandlerExceptionResolver 인터페이스의 기본 구현이라고 합니다. 다른 ExceptionHandler 가 존재하지 않는다면 해당 클래스가 실행됩니다.

 

 

 

doResolverException 메서드를 보면 발생하는 Exception을 분기 별로 처리하는 것을 알 수 있습니다.

 

 

 

 

 

 

 

ExceptionHandlerExceptionResolver 동작

 

 

 

public class ExceptionsController {

    @GetMapping("/hello")
    public ResponseEntity exceptionHandler() {
        throw new HelloException();
    }
}

@RestControllerAdvice
public class HelloAdvice {

    @ExceptionHandler(HelloException.class)
    public ResponseEntity<String> handle() {
        return ResponseEntity.badRequest().body("HelloException");
    }
}

 

 

 

Exception이 발생하면 DispatcherServlet에서 처리를 합니다.

 

 

등록된 HandlerExceptionResolvers를 통해서 MAV를 반환받습니다.

 

 

당연히 지금은 exception 이 발생했기 때문에 MAV는 null임을 알 수 있습니다.

 

 

 

 

 

 

DefaultErrorAttributes에서는 exception과 관련된 속성들을 setting 해줍니다.

 

 

공식문서에서는 타임스탬프, 상태, 오류 이유, exception, message 등과 같은 속성을 제공한다고 합니다.

 

 

 

 

 

위에서 말씀드린 스프링이 제공한 ExceptionResolver 들을 볼 수 있습니다.

 

 

 

처음에 ExceptionHandlerExceptionResolver의 resolveException이 동작합니다.

 

 

 

 

 

ExceptionHandlerAdviceCache를 보면 HelloAdvice가 등록돼있는 것을 알 수 있습니다.

 

 

ExceptionHandlerExceptionResolver#doResolveHandlerMethodException 실행하여 처리합니다.

 

 

 

 

 

 

 

 

 

 

ResponseStatusExceptionResolver 동작

 

 

 

위에서 말한 @ExcpetionHandler 가 존재하지 않고, @ResponseStatus 가 존재한다면 과연 ExceptionHandlerExceptionResolver를 건너뛸까?라는 의심이 들었습니다.

 

 

 

의심에 그치지 않고 @ResponseStatus만을 가진 코드를 통해서 디버깅해 보겠습니다.

 

 

 

@GetMapping("/hello")
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity exceptionHandler() {
    throw new IllegalArgumentException();
}

 

 

 

 

 

 

 

 

이전 과정은 모두 ExceptionHandlerExceptionResolver와 같습니다. 

 

 

 

다만 xxxComposite에서 ResponseStatusExceptionResolver가 동작하는 것을 알 수 있습니다.

 

 

 

 

 

 

 

ResponseStatusExceptionResolver#doResolverException이 동작하고 그 뒤처리는  ExceptionHandlerExceptionResolver와 같습니다.

 

 

 

 

 

왜 같은 ExceptionResolver인데 동작하는 메서드가 다른가?

 

 

ExceptionHandlerExceptionResolver는 doResolverHandlerMethodException,

ResponseStatusExceptionResolver는 doResolverException

 

 

 

doResolverHandlerMethodException ⇒ AbstractHandlerMethodExceptionResolver

doResolverException ⇒ AbstractHandlerExceptionResolver

 

 

 

 

 

위의 ExceptionResolver에 관한 클래스 다이어그램을 보면 AbstractHandlerExceptionResolver의 추상화 레벨이 더 높습니다.

 

 

 

또한, 클래스 이름을 보면 Handler, HandlerMethod라고 되어있습니다.

 

 

결국 ExceptionHandlerExceptionResolver는 요청에 대한 처리를 하면서의 예외 처리를 위한 범용적인 클래스이고,

 

 

 

AbstractHandlerMethodExceptionResolver는 HandlerMethod를 대상으로 예외 처리를 하는 특정한 클래스입니다.

 

 

 

 

 

 

 

 

 

 

728x90