예외 처리기가 어떻게 동작하는지에 대해서 궁금했어서
업무중에 돌려보게 되었다. (예제코드는 다시 작성할 예정)
일단 동작과정은 DB에서 해당 id를 찾아 검색했을 때 없을 경우 예외를 던져주게 하는
간단하게 보면
public class UserService {
private final UserRepository userRepository;
public User findById(final int id) {
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("해당 유저를 찾을 수 없습니다"));
}
}
라고 로직을 구성했을 때 이 로직의 예외에 대한 핸들러 동작을 파보게 되었다.
일단 get 메소드로 조회 로직을 수행하고
그 요청을 RequestMappingHandlerAdapter
로 위임해서 처리를 요청한다.
그다음 주어진 값들로 컨트롤러에 대한 로직을 처리하는데
다음 그림이 InvocableHandlerMethod
클래스이다.
이 때, getBridgedMethod().invoke(getBean(), args);
부분이
cglibAopProxy쪽으로 조회 로직을 맡기게 되고 그 곳에서 repository에 대한 동작을 수행하다가
findById에서 못찾았을 경우에 에러를 던지게 된다.
다시 cglibAopProxy에서 요청결과에 대한 에러를 잡아서 다시 처리되는데
이것이 InvocableHandlerMethod
의 catch
로 넘어온다.
catch (InvocationTargetException ex)
이부분에서
RuntimeException
의 인자인지를 확인한다.
이 부분에서 내가 구현한 NotFoundException
의 상속도는
NotFoundException
-> NoSuchElementException
-> RuntimeException
-> Exception
순서기 때문에
instanceof RuntimeException
으로 처리가 되게 된다.
그렇게해서 exception
객체를 담아서 DispatcherServlet
으로 넘겨주게 된다.
이젠 디스패쳐 서블릿이 받은 에러를 처리해줄 누군가를 찾기 시작하는데
이 두 개중 HandlerExceptionResolverComposite
로 처리를 진행해준다.
그렇게 해서 resolveException
메소드를 실행해주는데
다음 이미지를 보게되면 resolvers에 3개가 들어있게 된다.
그 리졸버들이 바로 HandlerExceptionResolver
들을 구현한것들
그중에서도 나는 ResponseStatusException
을 날려준게 아니기 때문에
그중에서 최종적으로 HandlerExceptionResolver 인터페이스 구현체인
ExceptionHandlerExceptionResolver
, ResponseStatusExceptionResolver
, DefaultHandlerExceptionResolver
세개를 가지고 for문을 돌게 된다
InvocableTargetException
이라는 객체의 target
즉, 대상이 어떤 Exception
이냐에 따라서 Exception
에맞는 @ExceptionHandler
로 파싱되어 커스텀된 응답으로 나가게 된다.
음 똑같은 로직 이라고 가정했을때 미묘하게 최적화를 하고싶다? 한다면
HandlerExceptionResolver
리스트의 맨앞인 ExceptionHandlerExceptionResolver
를 사용 그러니까
커스텀으로 @ExceptionHandler
를 만들어 쓰는것이 ResponseStatusException
을 던지거나, @ResponseStatus
을 사용하는것보다
빠를 수 있겠다.