공부내용공유

Spring 더 나은 예외처리 본문

Spring/Spring MVC

Spring 더 나은 예외처리

forfun 2023. 8. 29. 09:27

서론


처음에 프로젝트를 시작할때는 예외처리에 대해 깊게 고민하지 않고 비즈니스상 발생하는 Custom Exception 만 만들고 던지면 충분하겠지 라고 생각을했다.

 

그러나 실제 프로젝트를 진행하면서 client 개발자분은 다른 예외들을 더 많이 마주쳤고 해당 예외에 대해서는 별도의 처리가 안돼있어서 어디서 잘못된건지 알아볼 수 없는 형태였다.

 

예외가 터질때마다 연락을 하여 물어보는 비효율적인 방식을 개선하기 위해 위해 예외처리에 대해 알아보고 프로젝트에 적용을 하고 어떻게 더 개선할 수 있을까에 대한 고민을 글로 작성하였다.

본론


프로젝트를 진행하면서 client 개발자 분이 가장 자주 마주치는 Exception 들은

  • MethodArgumentNotValidException
    • HttpMessageConverter 에서 @valid 혹은 json 형식 오류로 binding 이 안될 경우
  • BindException
    • ModelAttribute 를 사용할 때 binding Error 가 발생하는 경우
  • HttpMessageNotReadableException
    • request 가 json 형태가 아니거나 json 형식이 잘못된 경우

등등이 있었다.

이러한 Exception 들이 발생하게 되면

 

 

client 분은 이러한 무의미한 정보와 긴 trace 정도만 적혀있는 reponse를 받게되었고 이는 어디에서 문제가 생긴건지 한눈에 들어오지 않고 개발 경험이 상대적으로 적은 분은 어떤 뜻인지 이해하기 힘든 경우가 많았다.

 

프로젝트를 계속 진행하면서도, 추후 다른 개발자분에게 인수인계를 할때도 이러한 정리되지 않은 reponse 는 효율성을 매우 저하시킬거라 판단되어 예외처리에 대해 좀 더 알아보고 적용하여 예외 메세지에 대한 규격화 및 가독성 증대를 시켰다.

예외 처리에 대해 알아보던중 현재 우리가 마주한 문제에 잘 맞는 HandlerExceptionResolver 라는 best pratice 가 있는걸 알게되었다.

해결방안을 찾으면서 알게된 스프링이 지원하는 여러 예외처리 방법에 대해 간단하게 설명하고 우리가 적용한 방식을 예시로 글을 마무리 할 것이다.

스프링 예외 처리


기본적으로 spring 에서 아무런 예외처리를 해주지 않으면 예외가 터진곳부터 WAS 까지 가게된다.

스프링 부트의 WAS 에 대한 기본 설정은 예외가 올 경우 /error 로 보내는데 spring 이 제공하는 BasicErrorController 가 호출된다.

즉 스프링 부트로 만든 프로젝트에서 별도의 설정 없이 컨트롤러에서 혹은 그 아래 계층에서 예외가 발생하게 되면

 

WAS(/request) -> 필터 -> 서블릿 -> 인터셉터 -> request 컨트롤러 (예외 발생)

WAS 에서 특정 컨트롤러를 호출하고 해당 컨트롤러에서 예외가 발생한다.

request 컨트롤러 (예외 발생) -> 인터셉터 -> 서블릿-> 필터 -> WAS(톰캣)

예외가 발생한 컨트롤러에서 위로 예외를 던진다.

WAS(/error) -> 필터 -> 서블릿-> 인터셉터 -> 컨트롤러(BasicErrorController)

예외를 받은 WAS 가 BasicErrorController 를 호출하고 설정돼있는 기본 예외처리가 작동한다.

 

또한 WAS에서 서버 내부에서 발생한 예외이기 때문에 client 가 param 을 잘못 보내서 발생한 예외임에도 HTTP status 가 500으로 발생한다.

 

근데 왜

별도의 설정을 하지 않았는데 400이 떴을까?

Spring MVC 는 controller 밖으로 던져진 예외를 ExceptionResolver 를 통해 관리할 수 있게 도와준다.

ExceptionResolver 에는

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

이렇게 우선순위로 Bean 들이 등록되어 있다.

 

ExceptionHandlerExceptionResolver

  • @ExceptionHandler 가 붙어있는 컨트롤러 내부 메소드
  • @ControllerAdivce 나 @RestControllerAdivce 가 있는 클래스의 메소드

를 처리한다.

각 메소드마다 예외 class 를 설정하여 각 상황에 맞게 알맞은 예외 처리가 가능하고

@ControllerAdivce 나 @RestControllerAdivce 를 통해 예외 처리 로직을 한곳에서 관리가 가능하다.

 

ResponseStatusExceptionResolver

  • @ResponseStatus 가 달려있는경우
  • ResponseStatusException 인 경우

를 처리한다.

이러한 예외들은 해당 Resolver 에서 잡아서 sendError 로 오류 코드등을 설정해주고 ModelAndView 를 반환하여 예외 흐름을 정상적인 흐름으로 돌려놓는다.

 

DefaultHandlerExceptionResolver

스프링 내부에서 발생하는 예외들을 잡아서 위 resolver 와 같은 방식으로 적절한 HTTP code 및 메세지를 설정해주고 ModelAndView 를 반환하여 정상 흐름으로 돌려놓는다.

우리 프로젝트의 경우 MethodArgumentNotValidException 이 DefaultHandlerExceptionResolver 에서

자동으로 이렇게 예외 처리가 되는것이었고

우리는 best pratice 라 알려져 있고 예외 처리의 규격화 및 유연한 대응을 할 수 있는 ExceptionHandlerExceptionResolver 를 적극 사용하기로 했다.

 

 

ExceptionHandlerExceptionResolver 적용


이러한 식으로 지금까지 마주쳤던 Exception 과 발생할거같은 Exception 들을 등록하고 보기 좋게 response 를 설정해 주었다.

 

이러한 식으로 민감한 trace 정보는 제외를 하고 client 분이 어느 부분이 잘못되었는지 더 쉽게 알 수 있게 response 를 적절히 커스텀하여 전달하였고 client 개발자분도 이전보다 훨씬 나아졌다고 만족해 하셨다.

 

결론


처음 Spring 공부를 할 때 보고 지나쳤던 내용이였는데 실제 프로젝트에서 문제를 마주치고 어떻게 해결할까 고민하면서 다시 찾아보니 왜 이런식으로 하는지 어떤식으로 활용해야 하는지 조금 감이 잡혔다.

하지만 아직 이렇게 단순히 client 에게 예외 내용만 알려주는것이 아닌 예외가 발생할 경우 내부에서 처리해야 하는 부분이 생길 수 있다..

이에 대해서는 좀 더 공부하고 알아보면서 준비를 해놓을 예정이다.