Deep Dive /Java Web /17

17. Controller → DispatcherServlet: 응답 분기 시작

컨트롤러가 반환값을 만들고 나면, Spring MVC는 그것을 어떤 방식으로 응답할지 결정합니다. 오픈소스 기준으로는 ServletInvocableHandlerMethod.invokeAndHandle()가 반환값을 returnValueHandlers에 넘기고, 이후 DispatcherServlet.processDispatchResult()가 뷰 렌더링 여부를 판단합니다.


이번 단계의 역할

이 단계는 컨트롤러 반환값을 뷰 렌더링 흐름응답 본문(JSON 등) 작성 흐름으로 나누는 분기점입니다. 즉 웹 응답이 HTML 페이지인지 REST 응답인지 갈라지는 순간입니다.


호출 흐름 요약

  1. 컨트롤러 메서드가 반환값을 만듭니다.
  2. ServletInvocableHandlerMethod.invokeAndHandle()가 반환값 처리기를 호출합니다.
  3. mavContainer 상태에 따라 요청이 이미 처리됐는지 여부가 결정됩니다.
  4. DispatcherServlet.processDispatchResult()가 뷰 렌더링 여부를 확인합니다.
  5. 이후 View 경로 또는 Response Body 경로로 나뉩니다.

호출 흐름 다이어그램

sequenceDiagram participant Controller participant SIHM as ServletInvocableHandlerMethod participant Handlers as HandlerMethodReturnValueHandler participant DS as DispatcherServlet Controller-->>SIHM: returnValue SIHM->>Handlers: handleReturnValue(...) Handlers-->>DS: ModelAndView or requestHandled=true alt View response DS->>DS: render(mv, request, response) else Body response DS->>DS: no view rendering end

핵심 코드

// ServletInvocableHandlerMethod.java
mavContainer.setRequestHandled(false);
this.returnValueHandlers.handleReturnValue(
        returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
// DispatcherServlet.java
if (mv != null && !mv.wasCleared()) {
    render(mv, request, response);
}
else {
    logger.trace("No view rendering, null ModelAndView returned.");
}

코드 해설

컨트롤러가 무엇을 반환했는지는 중요하지만, 더 중요한 것은 그 반환값을 어떤 HandlerMethodReturnValueHandler가 처리하느냐입니다. ModelAndView라면 뷰 렌더링으로, @ResponseBody 계열이면 응답 본문 작성으로 이어집니다.


설계 의도

컨트롤러 반환 타입을 하나의 방식으로 고정하지 않고, 여러 응답 전략을 플러그인처럼 처리하기 위한 구조입니다. 이 덕분에 같은 MVC 파이프라인 안에서 템플릿 렌더링과 REST 응답을 모두 지원할 수 있습니다.


다음 단계 연결

뷰 렌더링 경로는 17-V1부터, JSON 응답 경로는 17-R1부터 이어집니다.

← 이전: 16. Service → Controller: 결과 반환 | 다음: 17-V1. Controller → DispatcherServlet: ModelAndView 반환