나는 CustomException을 적용하고자 했다
그 이유는 일단 우리회사의 경우에는 HashMap에 담아서 에러를 처리하는 시스템인데
일일히 예외처리 로직을 구현할 때마다 HashMap에 resultCode, resultMsg, resultData 를 담아주는 로직과 그걸 return 하는 로직이 구현되어있고
또 사용자에게 보이는 메시지가 중복되는 경우가 흔하며
그걸 구분할 수 있는 errorCode라는것을 만들어서 따로 관리하고 싶었다
그러기 위해서는 customException 적용이 필수였다
그런데 회사 프레임워크에는 이미 ExceptionHandler가 구현되어있었고,
나는 아무리 @Order을 통해 우선순위를 내가 만든 ExceptionHandler로 가장 우선순위를 높게 올리더라도
자꾸 충돌이나는건지 사내 프레임워크 ExceptionHandler만 실행되는 것이다
내가생각한 로직 : 나의 Ordered 최상위 ExceptionHandler => 사내 ExceptionHandler 요롷게 가는 로직을 생각했는데 전혀 그렇게 구현되지 않았다!!!!!
그래서 나는 spring이 돌아가는 원리를 잘 몰라서 이렇게 헤매는 거구나 로 결론내림
그래서 조금 찾아보니 스프링 예외체인의 단계가 있었고,, ExceptionHandler 보다
먼저 실행되는 HandlerExceptionResolver 로 구현하면서 CustomExceptionHandler를 구현할 수 있었다.
그러면 내가 해멨었던 과정과 해메지않기 위해 알아야할 지식들을 공유하겠다.
throw new Error가 실행되면 해당 예외가 호출 스택을 따라 상위로 전파됩니다.
서비스를 호출한 컨트롤러 메서드로 예외가 전파됩니다.
컨트롤러에서 try-catch로 예외를 잡지 않으면 계속해서 상위로 전파됩니다.
컨트롤러에서 처리되지 않은 예외는 DispatcherServlet으로 전달됩니다.
Spring MVC는 다음 순서로 예외 처리를 시도합니다:
ExceptionHandlerExceptionResolver
@ExceptionHandler 어노테이션이 붙은 메서드를 찾아 실행합니다.
먼저 예외가 발생한 컨트롤러 내부에서 찾고, 없으면 @ControllerAdvice 클래스에서 찾습니다.
ResponseStatusExceptionResolver
@ResponseStatus 어노테이션이 붙은 예외 클래스를 처리합니다.
DefaultHandlerExceptionResolver
Spring MVC의 표준 예외들을 처리합니다.
위의 HandlerExceptionResolver들로 처리되지 않은 예외는 서블릿 컨테이너로 전달됩니다.
서블릿 컨테이너는 web.xml에 설정된 error-page나 기본 오류 페이지로 요청을 포워딩합니다.
예외처리 흐름을 먼저 이해하고 있으면, 왜 HandlerExceptionResolver가 @ExceptionHandler 보다 먼저 실행되는 지 알 수 있다.
클라이언트가 웹 애플리케이션에 요청을 보냅니다.
모든 요청은 프론트 컨트롤러인 DispatcherServlet이 가로챕니다. DispatcherServlet은 요청을 처리할 적절한 컨트롤러를 찾는 작업을 시작합니다.
DispatcherServlet은 HandlerMapping에게 요청 URL에 매핑되는 핸들러(컨트롤러)를 찾도록 요청합니다. HandlerMapping은 적절한 컨트롤러를 찾아 DispatcherServlet에 반환합니다.
DispatcherServlet은 선택된 컨트롤러를 실행할 수 있는 HandlerAdapter를 찾습니다. HandlerAdapter는 컨트롤러의 메소드를 호출합니다.
컨트롤러는 비즈니스 로직을 실행하고, 결과를 Model 객체에 담아 논리적인 뷰 이름과 함께 DispatcherServlet에 반환합니다.
DispatcherServlet은 ViewResolver를 통해 컨트롤러가 반환한 뷰 이름에 해당하는 실제 View 객체를 찾습니다.
View 객체는 Model 데이터를 사용하여 클라이언트에게 보여줄 결과를 생성합니다.
DispatcherServlet은 View가 렌더링한 결과를 클라이언트에게 응답으로 보냅니다.
이 과정에서 필터나 인터셉터가 추가로 동작할 수 있으며, 예외 처리 등의 부가적인 로직도 포함될 수 있습니다.
컨트롤러 메서드 실행 중 예외가 발생하면:
DispatcherServlet이 예외를 캐치
DispatcherServlet이 등록된 HandlerExceptionResolver들을 순차적으로 호출
ExceptionHandlerExceptionResolver가 실행 (가장 높은 우선순위)
ExceptionHandlerExceptionResolver가 @ExceptionHandler 메서드를 검색 및 실행
ExceptionHandlerExceptionResolver는 HandlerExceptionResolver 인터페이스의 구현체로, 다음과 같은 역할을 수행합니다:
발생한 예외에 대해 적절한 @ExceptionHandler 메서드를 찾습니다.
찾은 @ExceptionHandler 메서드를 실행합니다.
메서드 실행 결과를 DispatcherServlet에 반환합니다.
따라서, ExceptionHandlerExceptionResolver는 @ExceptionHandler를 찾아 실행하는 메커니즘을 제공하는 것이지, 별도의 처리 단계가 아닙니다.
ExceptionHandlerExceptionResolver (가장 높은 우선순위)
컨트롤러 내부의 @ExceptionHandler
@ControllerAdvice 클래스의 @ExceptionHandler
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
이러한 구조로 인해 ExceptionHandlerExceptionResolver가 @ExceptionHandler를 찾아 실행하는 것이 예외 처리 흐름의 첫 번째 단계가 되는 것입니다.
Spring Framework에서는 예외 처리를 위한 다양한 메커니즘을 제공하며, 예외 체인(Exception Chaining)은 이 중 하나의 중요한 개념
예외 체인은 한 예외가 다른 예외를 발생시킬 수 있는 기능을 말합니다4. 이를 통해 예외의 원인을 추적하고 더 상세한 예외 정보를 제공할 수 있습니다.
HandlerExceptionResolver를 직접 구현하면 예외 처리 로직이 복잡해질 수 있습니다. 각 예외 타입에 대해 별도의 처리 로직을 작성해야 하며, 이는 코드의 가독성과 유지보수성을 저하시킬 수 있습니다.
CustomHandlerExceptionResolver를 구현하면 Spring이 제공하는 @ExceptionHandler, @ControllerAdvice 등의 편리한 예외 처리 기능을 활용하기 어려워집니다. 이는 Spring의 장점을 충분히 활용하지 못하는 결과를 초래합니다.
개발자마다 다른 방식으로 HandlerExceptionResolver를 구현할 수 있어, 프로젝트 전체의 예외 처리 방식에 일관성이 떨어질 수 있습니다.
HandlerExceptionResolver를 직접 구현하면 단위 테스트 작성이 더 복잡해질 수 있습니다. @ExceptionHandler를 사용하는 방식에 비해 테스트 코드 작성과 유지보수가 어려워질 수 있습니다.
새로운 예외 타입이 추가될 때마다 HandlerExceptionResolver 구현체를 수정해야 하므로, 확장성이 떨어집니다.
대신, Spring에서 제공하는 @ExceptionHandler와 @ControllerAdvice를 사용하는 것이 권장됩니다. 이 방식은 코드가 더 간결하고, 유지보수가 쉬우며, Spring의 기능을 최대한 활용할 수 있습니다.
이런점은 알고 구현하기를 바란다.. 하지만 나의 경우에는 사내프레임워크 때문에 이 방법을 채택할 수밖에 없었다 ㅠㅠ