Deep Dive /Java Web /17

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

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


이번 단계의 역할 — 응답이 HTML 페이지인지 REST 응답인지 갈라지는 순간

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


호출 흐름 요약 — 반환값 생성 → invokeAndHandle()이 처리기 호출 → mavContainer 상태 결정

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

호출 흐름 다이어그램 — handleReturnValue()의 결과가 ModelAndView인지 requestHandled인지로 갈린다

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

핵심 코드 — setRequestHandled(false)로 시작해 mv 유무가 render() 호출을 가른다

// 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가 처리하느냐가 경로를 가른다

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


설계 의도 — 응답 전략을 하나로 고정하지 않고 플러그인처럼 갈아끼운다

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


다음 단계 연결 — 뷰 경로는 17-V1, JSON 경로는 17-R1로 갈라진다

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

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