[Spring MVC] Request 처리 과정 [1] - DispatcherServlet
Spring MVC Reuest 처리의 과정 1편 DispatcherServlet
스프링 프레임워크를 공부할 때 가장 먼저 접하는 내용 중 하나가 Request 처리 과정에 대한 내용이다.
스프링 공식 홈페이지에서 제공하는 간략화 된 요청 처리 과정은 다음과 같다.

위 그림에서 Front Controller 로 표기된 부분이 DispatcherServlet이다. 이를 기반으로 좀 더 상세히 처리과정을 살펴보면 아래 그림과 같다.

- 사용자가 서버로 요청을 보낸다.
- DispatcherServlet이 사용자 요청을 가로채 이 요청을 처리할 Handler를 찾는다.(HandlerMapping)
- 해당 Handler를 실행할 수 있는 HandlerAdapter를 찾는다.
- HandlerAdapter 를 통해 Handler를 특정 실행한다.
- HandlerAdapter 의 실행 결과로 반환된 ModelAndView를 반환 받는다.
- DispatcherServlet ModelAndView에서 view name을 획득하고, ViewResolver를 통해 view name에 해당하는 view를 찾는다.
- view에 model 데이터를 전달하여 실제 페이지를 rendering한다.
- 사용자에게 렌더링 페이지를 전송한다.
간략화 된 이 플로우를 보면 마치 HandlerMapping, HandlerAdapter가 Concrete Class로 보이지만 이것들은 모두 Interface이기 때문에 여기서는 실제로 어떤 구현체로 동작하는지 살펴본다.
DispatcherServlet
요청을 제일 먼저 받아들이는 DispatcherServlet은 기본 구현체로 모든 요청을 처리할 HandlerMapping를 검색하고 HandlerAdapter 찾아 Handler 를 실행 한 뒤 ModelAndView를 반환 받아 View를 검색하고 Model을 주입하여 실제 페이지를 렌더링한 뒤 사용자에게 전달하는 역할을 담당한다.
이 과정을 시작하는 메소드는 doService 메소드이지만 핵심 처리 로직은 doDispatch 메소드에 존재한다. doService 메소드는 요청에 대한 로그를 남기고 추후 리플레이를 위한 snapshot 등을 남기는 로직이 존재하고 그 후 doDispatch 를 호출하는 로직이 존재한다.
doDispatch 메소드를 살펴보자
public class DispatcherServlet extends FrameworkServlet {
...
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request); // MultipartResolver를 이용하여 처리된다.
multipartRequestParsed = (processedRequest != request);
mappedHandler = getHandler(processedRequest); // 1. HandlerMapping 탐색
if (mappedHandler == null) {
// 핸들러가 없다면 404 에러를 발생시킨다.
noHandlerFound(processedRequest, response);
return;
}
// 2. Handler를 이용할 수 있는 HandlerAdapter 를 찾는다.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
//...
// 3. HandlerAdapter를 통해 Handler를 실행시키고 ModelAndView를 반환받는다.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv); // 4. viewName을 세팅
mappedHandler.applyPostHandle(processedRequest, response, mv); // 5. 후처리 과정을 수행한다. handler에 등록된 interceptor들이 실행되는 부분
}
//... 예외처리
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); // 6. Page 렌더링 및 사용자에게 응답 전송
}
// ... 이하 생략
}
...
}
위의 코드에 주석이 달린 순으로 동작하게 된다. 우선 DispatcherServlet이 상속받고 있는 FrameworkServlet의 역할을 간단하게 살펴보면 아래와 같다. 기본적으로 HttpServletBean을 상속받아 아래 메소드들을 Overriding한다.
- override service 메소드
- override doXXX(doPost, doGet...)
- doXXX 메소드들에서는 사실 전부 processRequest라는 메소드를 호출한다.
- processRequest 에서는 doService라는 메소드를 호출하는데 이는 템플릿 메서드 패턴이 적용되어 있어 구현체에서 이를 구현해야한다.
간단하게 FrameworkServlet은 템플릿 메서드 패턴이 적용되어 Http Request 를 처리할 수 있는 틀을 제공하는 추상 클래스이다.
DispatcherServlet은 doService에서 시작되고 doDispatch 를 통해 핵심 로직이 수행된다.
핵심 초기화 과정
initHandlerMappings
DispatcherServlet의 초기화 과정에서 initHandlerMapping 메소드가 호출되어, ApplicaitonContext 상에 등록되어 있는 HandlerMapping Bean들을 찾아 등록하게 된다.
...
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings); // 찾은 handlerMappings을 @Order 등의 어노테이션에 입력된 우선순위 값에 따라 정렬
}
}
...
}
HandlerMapping 은 언급했듯 Interface이다. 이 인터페이스의 핵심 기능은 HandlerExecutionChain 을 반환하는 것이다. 기본 구현체는 BeanNameUrlHandlerMapping 이며, @RequestMapping 어노테이션을 사용할 때 동작하는 RequestMappingHandlerMapping 등이 있다.
initHandlerAdapters
HandlerMapping 을 실행할 HandlerAdapter 를 찾아 등록하는 메소드이다.
...
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
if (this.detectAllHandlerAdapters) {
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
...
}
handler 등록 과정과 유사하게 진행된다.
이후 ViewNameTranslator, ViewResolver 등을 등록하는 과정이 수행되며 이렇게 등록된 컴포넌트들을 기반으로 Spring MVC의 요청이 처리된다. 다음편에서는 HandlerMapping과 HandlerAdapter에 대해 알아본다.