우리가 흔히 알고 있는 Spring MVC의 요청 처리 흐름 한 가운데에는 DispatcherServlet이 자리잡고 있습니다.

클라이언트로부터 들어오는 HTTP request는 Servlet Filter를 거쳐 DispatcherServlet에 전달되고, DispatcherServlet은 해당 request를 처리할 Controller를 검색하고 대상 Controller에게 request를 전달하기 위해 HandlerMapping과 HandlerAdapter 등의 컴포넌트를 이용합니다.

대부분 다 알고 있는 내용이기 때문에 자세한 설명은 필요없을 것 같습니다.

다만, 구글링을 통해 그림으로 표현한 Spring MVC의 요청 처리 흐름에 대한 자료는 많이 볼 수 있지만 코드 베이스의 자료는 생각보다 찾기 힘든 것 같아서 이 참에 코드 베이스의 흐름을 정리해 볼까 합니다.

 

SSR(Server Side Rendering) 방식의 코드 흐름

SSR(Server Side Rendering) 방식은 CSR(Client Side Rendering) 방식의 애플리케이션이 본격적으로 사용되기 이 전부터 사용되던 전통적인 방식으로 아파치 톰캣(Apache Tomcat) 같은 서블릿 컨테이너가 Thymeleaf 등의 템플릿 기술을 이용해 HTML 템플릿을 렌더링 한 후에 클라이언트 쪽으로 내려주는 방식입니다.

[그림 1-1] SSR 방식의 Spring MVC 요청 처리 흐름

[그림 1-1]은 우리가 흔히들 알고 있는 Spring MVC의 요청 처리 흐름 중에서 SSR(Server Side Rendering) 방식의 요청 처리 흐름입니다.

SSR 방식의 요청 처리 흐름의 특징은 ViewResolver가 적절한 뷰 정보를 해석한 후에 대상 View 객체에게 응답 데이터를 전달하고 View를 통해 이 응답 데이터를 HTML 같은 템플릿에 채워 넣어서 최종적으로 클라이언트에게 전송한다는 것입니다.

따라서 [그림 1-1]에 있는 Spring MVC 컴포넌트들을 모두 사용합니다.

SSR 방식의 요청 처리 흐름을 코드로 표현하면 다음과 같은 순서로 호출이 됩니다.

  1. DispatcherServlet
    1. doDispatch(HttpServletRequest request, HttpServletResponse response)
    2. getHandler(HttpServletRequest request)  // HandlerMapping을 통해 HandlerChain을 얻는다.
    3. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    4. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    5. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    6. render(mv, request, response);
    7. View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);  // ViewResolver가 View를 해석해 적절한 View 객체를 얻는다.
    8. view.render(mv.getModelInternal(), request, response);  // View를 렌더링한다.
  2. ThymeleafView (Thymeleaf 템플릿 기술을 사용할 경우)
    1. render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response)
    2. renderFragment(this.markupSelectors, model, request, response);
    3. String viewTemplateName = getTemplateName();
    4. ISpringTemplateEngine viewTemplateEngine = getTemplateEngine();
    5. viewTemplateEngine.process(templateName, processMarkupSelectors, context, templateWriter);
  3. TemplateEngine
    1. process(new TemplateSpec(template, templateSelectors, null,  null,null), context, writer);
    2. TemplateManager templateManager = this.configuration.getTemplateManager();
    3. templateManager.parseAndProcess(templateSpec, context, writer);
  4. TemplateManager
    1. TemplateModel cached =  this.templateCache.get(cacheKey);
    2. cached.process(processingHandlerChain);
  5. TemplateEngine
    1. writer.flush();

 

코드의 흐름을 보면 꽤 복잡한 흐름을 거치고 실제 내부 코드들은 더 복잡하지만 어쨌든 각각의 컴포넌트들이 각자 맡은바 역할에만 집중하고 있는 것을 볼 수 있습니다.

 

View를 렌더링하는 과정은 일반적으로 Thymeleaf 기술을 사용하고 있다고 가정했을 때, View 인터페이스의 구현 클래스인 ThymeleafViewTemplateEngineTemplateManager를 이용해 클라이언트에게 전송할 HTML에 응답 데이터를 채워 넣은 후, 최종 템플릿을 만듭니다.

그리고 마지막은 Writer가 write한 템플릿 데이터를 flush 해서 클라이언트에게 전달합니다.

템플릿 데이터는 TemplateEngineTemplateManager 어딘가에서 write 된다라고 생각하면 될 것 같습니다. ^^;

 

CSR(Client Side Rendering) 방식의 코드 흐름

CSR(Client Side Rendering) 방식은 SSR 방식처럼 HTML 템플릿을 렌더링 해서 클라이언트 쪽으로 내려주는것이 아니라 Controller가 리턴한 데이터를 주로 JSON 형태로 HTTP response body에 실어서 응답 데이터만 클라이언트 쪽에 전달해주는 방식입니다.

여기서 말하는 클라이언트는 Apache나 Nginx 같은 웹서버에서 실행되는 Frontend 측 웹 앱이 될 수도 있고, 스마트 폰에서 실행되는 네이티브 앱이 될 수도 있으며, 데스크탑에서 실행되는 데스크탑 애플리케이션이 될 수도 있습니다.

[그림 1-2] CSR 방식의 Spring MVC 요청 처리 흐름

[그림 1-2]은 Spring MVC의 요청 처리 흐름 중에서 CSR(Client Side Rendering) 방식의 요청 처리 흐름입니다.

그림에서 보다시피 View 자체가 필요 없기 때문에 ViewResolver와 View의 로직을 타지 않습니다.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

		...
		...

		// HandlerAdapter가 리턴한 ModelAndView가 null이면 뷰를 렌더링하지 않는다.
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response); 
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isTraceEnabled()) {
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}
		...
		...
}

위 코드는 DispatcherServlet의 processDispatchResult() 코드의 일부입니다.
코드를 보면 HandlerAdapter가 리턴 받은 ModelAndView 객체가 null이면 뷰를 렌더링하지 않는 것을 볼 수 있습니다.

즉, 이 말은 Controller에서 리턴한 객체가 ResponseEntity 같은 타입의 객체인 경우 내부적으로 ModelAndView 객체를 생성하지 않는다는 의미와도 같습니다.

CSR 방식의 요청 처리 흐름의 코드는 앞에서 살펴보았던 SSR 방식의 코드 흐름 중, DispatcherServlet의 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException) 내부에서 render(mv, request, response)의 호출부터 이 후 나머지 과정의 처리를 생략하면 됩니다.

 

이 글을 통해 Spring MVC의 요청 처리 흐름을 조금 더 깊게 이해하는데 조금이라도 도움이 될 수 있길 바랍니다.

+ Recent posts

출처: http://large.tistory.com/23 [Large]