Deep Dive /Java Web /13

13. DispatcherServlet → Controller: handle()

Step 12에서 컨트롤러 파라미터 준비가 끝나면, 이제 실제 컨트롤러 메서드가 호출됩니다. 겉으로는 DispatcherServlet이 컨트롤러를 바로 호출하는 것처럼 보이지만, 실제 내부는 HandlerAdapter와 InvocableHandlerMethod 계층을 거쳐 실행됩니다.


시퀀스 다이어그램

sequenceDiagram participant DS as DispatcherServlet participant HA as RequestMappingHandlerAdapter participant SIHM as ServletInvocableHandlerMethod participant IHM as InvocableHandlerMethod participant Controller DS->>HA: handle(request, response, handler) HA->>HA: invokeHandlerMethod(...) HA->>SIHM: invokeAndHandle(webRequest, mavContainer) SIHM->>IHM: invokeForRequest(...) IHM->>IHM: getMethodArgumentValues(...) IHM->>IHM: doInvoke(args) IHM->>Controller: method.invoke(bean, args) Controller-->>IHM: returnValue IHM-->>SIHM: returnValue SIHM-->>HA: returnValue 처리 HA-->>DS: ModelAndView / null 반환

핵심 코드 흐름

// RequestMappingHandlerAdapter.java
protected @Nullable ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav = invokeHandlerMethod(request, response, handlerMethod);
    return mav;
}

protected @Nullable ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    invocableMethod.invokeAndHandle(webRequest, mavContainer);

    return getModelAndView(mavContainer, modelFactory, webRequest);
}
// InvocableHandlerMethod.java
public @Nullable Object invokeForRequest(...) throws Exception {
    Object[] args = getMethodArgumentValues(...);
    Object returnValue = doInvoke(args);
    return returnValue;
}

protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
    Method method = getBridgedMethod();
    return method.invoke(getBean(), args); // 실제 컨트롤러 메서드 실행 지점
}

왜 HandlerAdapter를 거칠까?

DispatcherServlet이 직접 컨트롤러를 호출하지 않고 HandlerAdapter를 거치는 이유는 “호출 전략 분리”입니다.

  1. 다양한 핸들러 타입 지원(확장성)
  2. 파라미터 해석/반환값 처리 공통화
  3. 호출 전후 부가기능(검증, 모델 초기화, 비동기 처리) 중앙집중화

즉, 컨트롤러 메서드 호출은 단순 Reflection 호출이 아니라, MVC 파이프라인 위에서 실행됩니다.


이 단계에서 실패하면?

컨트롤러 내부에서 예외가 발생하면 InvocationTargetException으로 감싸져 상위로 전파되고, DispatcherServlet의 예외 처리 경로(processHandlerException)로 이동합니다.

이것이 Step 12-3에서 본 ExceptionResolver 체인과 연결됩니다.


탐구 질문

  1. doInvoke가 Reflection으로 메서드를 호출하는데도, 왜 성능보다 구조적 일관성이 더 중요할까요?
  2. 컨트롤러에서 런타임 예외가 터지면 postHandle은 호출될까요? afterCompletion은 어떻게 될까요?
  3. HandlerAdapter 없이 DispatcherServlet이 직접 컨트롤러를 호출한다면, 어떤 확장 포인트들이 사라질까요?