Spring/Spring MVC

DispatcherServlet 개념과 동작원리 및 내부 코드

taey 2025. 8. 2. 13:11

DispatcherServlet 개념

DispatcherServlet은 HTTP 프로토콜로 들어오는 모든 요청을 먼저 받아서 적합한 핸들러를 찾아, 컨트롤러 위임을 하는 역할입니다.

(쉽게 말하여, 제일 앞에서 요청을 받아, 적합한 박스를 찾아ispatcherServlet 개념

DispatcherServlet은 HTTP 프로토콜로 들어오는 모든 요청을 먼저 받아서 적합한 핸들러를 찾아, 컨트롤러 위임을 하는 역할입니다.

 

(쉽게 말하여, 제일 앞에서 요청을 받아, 적합한 컨트롤러로 보내주기)

 

 

DispatcherServlet을 쓰는 이유

DispatcherServlet이 없다면, 모든 서블릿에 대해 URL 매핑을 해주어야 합니다.(web.xml ...) 

하지만, DispatcherServlet을 사용하여, 핸들러 매핑 리스트에서 적합한 핸들러 매핑을 찾아, 적합한 컨트롤러로 위임해주기 때문에 web.xml을 작성할 필요가 없어졌습니다. 😁

 

 

DispatcherServlet 동작 원리 및 내부 코드

DispatcherServlet의 동작 과정에 대해 조금 더 자세히 살펴보면 다음과 같습니다.

  1. DispatcherServlet이 클라이언트의 모든 요청을 받습니다.
  2. 요청 정보에 대해 HandlerMappinng에 위임하여 처리할 Handler(Controller)을 찾습니다.
  3. 2번에서 찾은 Handler을 수행할 수 있는 HandlerAdapter를 찾습니다.
  4. HandlerAdapter는 Controller에 비즈니스 로직 처리를 호출합니다.
  5. Controller는 비즈니스 로직을 수행하고, 처리 결과를 Model에 설정하며 HandlerAdapter에 view name을 반환합니다.
    • 모델을 반환하면 View가 렌더링이 되고, 그렇지 않은 경우(ex. @RestController 등) View가 렌더링이 되지 않습니다. 
  6. 5번에서 반환받은 view name을 ViewResolver에게 전달하고, ViewResolver은 해당하는 View 객체를 반환합니다.
  7. DispatcherServlet은 View에게 Model을 전달하고 화면 표시를 요청합니다.
  8. 최종적으로 서버의 응답을 클라이언트에게 반환합니다.

Dispatcher Servlet의 계층 구조

 

예제에 사용될 API와 요청 형식

 

 

DispatcherServlet의 요청 처리 과정 with 코드

1. HttpServlet : 서블릿의 Request/Response를 HttpServlet의 Request / Response로 변환

2~5. FrameworkServlet : 클라이언트 요청의 Http Method에 따라 분기 처리 - doXXX 메서드

6. DispatcherServlet: 요청 정보에 대해 이를 처리할 Handler를 찾고, HandlerAdapter를 통해 Controller에 요청 위임

    6-1. 요청에 매핑되는 HandlerMapping(HandlerExecutionChain) 조회

    6-2. Handler을 수행할 수 있는 HandlerAdapter 조회

    6-3. HandlerAdapter를 통해 실제 Controller의 비즈니스 로직 호출

 

 

1. HttpServlet: Servlet의 Request, Response를 HttpServlet의 Request,Response로 변환

  • 진행 순서
  • HttpServlet.service()
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
	HttpServletRequest request;
	HttpServletResponse response;

	try {
		request = (HttpServletRequest) req;
		response = (HttpServletResponse) res;
	} catch (ClassCastException e) {
		throw new ServletException(lStrings.getString("http.non_http"));
	}
	service(request, response)
}

 

7, 8번째 라인에서 서블릿의 요청 및 응답을 HttpServlet의 요청 및 응답으로 변환합니다. 

이후, 12번째 라인에서 FrameworkServlet 추상 클래스에서 Override된 service() 메서드를 호출합니다.


2. FramworkServlet : HTTP 메서드에 따라 분기 처리

  • 진행 순서
  • HttpServlet.service() -> FrameworkServlet.service()
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    if (HTTP_SERVLET_METHODS.contains(request.getMethod())) {
       super.service(request, response);
    }
    else {
       processRequest(request, response);
    }
}

 

5번째 라인에서 HTTP 표준 Method면 HttpServlet의 service를 실행시키고, 표준 method가 아닌 커스텀일 경우, 바로 FrameworkServlet의 proceeRequest를 실행시킨다.

 


3. HttpServlet : service()

  • 진행 순서
  • HttpServlet.service() -> FrameworkServlet.service() -> HttpServlet.service()
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch (IllegalArgumentException iae) {
                // Invalid date header - proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req, resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req, resp);

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

 

HttpServlet 추상 클래스의 service()는 Http Method에 따라 doXXX 메서드를 호출합니다.

실제 호출은 HttpServlet 클래스슬 상속 받고, doXXX 메서드를 오버라이딩하고 있는 자식 클래스의 doXXX 메서드를 호출합니다.

지금은 POST 메소드를 사용했으므로 36번째 라인에서 FrameworkServlet의 doPost 메서드로 넘어갑니다.

 

위 코드는 템플릿 메서드 패턴을 사용한 것입니다.

상위 클래스(HttpServlet) : 알고리즘 골격(service()) 정의

하위 클래스 : 구체적인 단계들(doGet(), doPost() 등) 구현


4. FrameworkServlet : doXXX() -> processRequest() -> doService()

  • 진행 순서
  • HttpServlet.service() -> FrameworkServlet.service() -> HttpServlet.service() -> FrameworkServlet.doXXX(), processRequest(), doService()
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    processRequest(request, response);
}

/**
 * Delegate POST requests to {@link #processRequest}.
 * @see #doService
 */
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    processRequest(request, response);
}

/**
 * Delegate PUT requests to {@link #processRequest}.
 * @see #doService
 */
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    processRequest(request, response);
}

/**
 * Delegate DELETE requests to {@link #processRequest}.
 * @see #doService
 */
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    processRequest(request, response);
}

 

13번째 라인인, doPost 메서드에서 processRequest를 호출합니다.

 

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
       throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContext localeContext = buildLocaleContext(request);

    RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

    initContextHolders(request, localeContext, requestAttributes);

    try {
       doService(request, response);
    }
    catch (ServletException | IOException ex) {
       failureCause = ex;
       throw ex;
    }
    catch (Throwable ex) {
       failureCause = ex;
       throw new ServletException("Request processing failed: " + ex, ex);
    }

    finally {
       resetContextHolders(request, previousLocaleContext, previousAttributes);
       if (requestAttributes != null) {
          requestAttributes.requestCompleted();
       }
       logResult(request, response, failureCause, asyncManager);
       publishRequestHandledEvent(request, response, startTime, failureCause);
    }
}

 

FrameworkServlet 추상 클래스의 processRequest() 메서드에서 핵심 로직은 19번째 라인의 doService() 입니다.

doService() 메서드가 드디어 DispatcherServlet 클래스를 호출하는 진입점입니다!

 

그럼 DispatcherServlet의 doService() 메서드에서는 어떠한 과정이 일어나는지 살펴보겠습니다.

 


5. DispatcherServlet : doService()

  • HttpServlet.service() -> FrameworkServlet.service() -> HttpServlet.service() -> FrameworkServlet.doXXX(), processRequest(), doService() -> DispatcherServlet.doService(), doDispatch()
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);

    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
       attributesSnapshot = new HashMap<>();
       Enumeration<?> attrNames = request.getAttributeNames();
       while (attrNames.hasMoreElements()) {
          String attrName = (String) attrNames.nextElement();
          if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
             attributesSnapshot.put(attrName, request.getAttribute(attrName));
          }
       }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    if (this.flashMapManager != null) {
       FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
       if (inputFlashMap != null) {
          request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
       }
       request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
       request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    }

    RequestPath previousRequestPath = null;
    if (this.parseRequestPath) {
       previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
       ServletRequestPathUtils.parseAndCache(request);
    }

    try {
       doDispatch(request, response);
    }
    finally {
       if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
          // Restore the original attribute snapshot, in case of an include.
          if (attributesSnapshot != null) {
             restoreAttributesAfterInclude(request, attributesSnapshot);
          }
       }
       if (this.parseRequestPath) {
          ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
       }
    }
}

 

3번째 라인, logRequest() 메서드에서 로그를 남깁니다.

7번째 라인, attributesSnapshot 변수에 복원할 수 있도록 요청 정보를 저장합니다.

20번째 라인, 스프링 객체를 사용할 수 있도록 request에 저장합니다.

25번째 라인, Redirect 후에도 데이터를 유지하기 위한 FlashMap 설정

34번째 라인, 요청 경로를 파싱하고, 캐싱합니다.

위의 로직이 존재하지만, 핵심 로직은 아니기에 넘어가겠습니다.

 

중요 로직은 41번째 라인인 doDispatch() 입니다. 이 함수는 아래의 작업을 진행합니다.

  • 요청에 매핑되는 HandlerMapping(HandlerExecutionChain) 조회
  • Handler을 수행할 수 있는 HandlerAdapter 조회
  • HandlerAdapter를 통해 Controller 호출 (실제 로직)

 

6. DispatcherServlet : doDispatch() - HandlerMapping, HandlerAdapter, Handle Controller

  • HttpServlet.service() -> FrameworkServlet.service() -> HttpServlet.service() -> FrameworkServlet.doXXX(), processRequest(), doService() -> DispatcherServlet.doService(), doDispatch() - HandlerMapping, HandlerAdapter, handle Controller
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;
		
       // =============================================================
       // 1. 요청에 매핑되는 HandlerMapping 조회
       try {
          processedRequest = checkMultipart(request);
          multipartRequestParsed = (processedRequest != request);

          // 현재 요청의 적합한 핸들러를 찾습니다.
          mappedHandler = getHandler(processedRequest);
          if (mappedHandler == null) {
             noHandlerFound(processedRequest, response);
             return;
          }
			
          // =======================================================  
          // 2. Handler을 수행할 수 있는 handlerAdapter 조회
          HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

          // Process last-modified header, if supported by the handler.
          String method = request.getMethod();
          boolean isGet = HttpMethod.GET.matches(method);
          if (isGet || HttpMethod.HEAD.matches(method)) {
             long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
             if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                return;
             }
          }
			
           // 등록된 인터셉터의 preHandle 수행 (컨트롤러 수행 전)  
          if (!mappedHandler.applyPreHandle(processedRequest, response)) {
             return;
          }

          // ====================================================================
          // 3. HandlerAdapter를 통해 Controller 호출 (실제 로직)
          mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

          if (asyncManager.isConcurrentHandlingStarted()) {
             return;
          }

          applyDefaultViewName(processedRequest, mv);
          
          // 등록된 인터셉터의 postHandle 수행 (컨트롤러 실행 후)
          mappedHandler.applyPostHandle(processedRequest, response, mv);
       }
       catch (Exception ex) {
          dispatchException = ex;
       }
       catch (Throwable err) {
          // As of 4.3, we're processing Errors thrown from handler methods as well,
          // making them available for @ExceptionHandler methods and other scenarios.
          dispatchException = new ServletException("Handler dispatch failed: " + err, err);
       }
       processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
       triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
       triggerAfterCompletion(processedRequest, response, mappedHandler,
             new ServletException("Handler processing failed: " + err, err));
    }
    finally {
       if (asyncManager.isConcurrentHandlingStarted()) {
          // Instead of postHandle and afterCompletion
          if (mappedHandler != null) {
             mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
          }
          asyncManager.setMultipartRequestParsed(multipartRequestParsed);
       }
       else {
          // Clean up any resources used by a multipart request.
          if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
             cleanupMultipart(processedRequest);
          }
       }
    }
}

 

 

6-1. 요청에 매핑되는 HandlerMapping(HandlerExecutionChain) 조회

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
       for (HandlerMapping mapping : this.handlerMappings) {
          HandlerExecutionChain handler = mapping.getHandler(request);
          if (handler != null) {
             return handler;
          }
       }
    }
    return null;
}

 

HandlerExecutionChain HandlerMethod, Interceptor들로 구성되어 HandlerMapping에 해당하는 클래스 입니다.

handlerMappings 에는 6가지 핸들러 매핑 클래스가 있습니다.

제가 정의한 api 방식인 @RequestMapping 기반 매핑은 RequestMappingHandlerMapping에서 처리합니다.

위 핸들러 매핑에 대한 설명은 다른 포스팅에서 작성하도록 하겠습니다.

 

RequestMappingHandlerMapping의 계층도

 

위 코드에서 5번 라인의 mapping.getHandler(request) 메서드를 내부로 들어가보겠습니다.

 

(DispatcherServlet.getHandler() -> AbstractHandlerMapping.getHandler() -> RequestMappingInfoHandlerMapping.getHandlerInternal() -> AbstractHandlerMethodMapping.getHandlerInternal())

 

// AbstractHandlerMapping 추상 클래스

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = getHandlerInternal(request);
    if (handler == null) {
       handler = getDefaultHandler();
    }
    if (handler == null) {
       return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String handlerName) {
       handler = obtainApplicationContext().getBean(handlerName);
    }

    // Ensure presence of cached lookupPath for interceptors and others
    if (!ServletRequestPathUtils.hasCachedPath(request)) {
       initLookupPath(request);
    }

    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

    if (request.getAttribute(SUPPRESS_LOGGING_ATTRIBUTE) == null) {
       if (logger.isTraceEnabled()) {
          logger.trace("Mapped to " + handler);
       }
       else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
          logger.debug("Mapped to " + executionChain.getHandler());
       }
    }

    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
       CorsConfiguration config = getCorsConfiguration(handler, request);
       if (getCorsConfigurationSource() != null) {
          CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
          config = (globalConfig != null ? globalConfig.combine(config) : config);
       }
       if (config != null) {
          config.validateAllowCredentials();
          config.validateAllowPrivateNetwork();
       }
       executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

 

7번째 라인의 getHandlerInternal(request) 에서 가장 구체화된 메서드인 RequestMappingInfoHandlerMapping의 getHandlerInternal(request)가 호출됩니다.

// RequestMappingInfoHandlerMapping 추상 클래스

@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
       return super.getHandlerInternal(request);
    }
    finally {
       ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}

8번째 라인의 getHandlerInternal(request) 에서 부모 클래스의 AbstractHandlerMethodMapping의 getHandlerInternal(request)가 호출됩니다.

// AbstractHandlerMethodMapping 추상 클래스

@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    String lookupPath = initLookupPath(request);
    this.mappingRegistry.acquireReadLock();
    try {
       HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
       return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
       this.mappingRegistry.releaseReadLock();
    }
}

 

getHandler() 메서드를 계속 타고 들어가보면 AbstractHandlerMethodMapping 추상 클래스의 getHandlerInternal() 메서드에서 HandlerMethod를 찾고, AbstractHandlerMapping 추상 클래스에서 HandlerExecutionChain 객체를 생성하여 리턴하는 것을 확인할 수 있습니다.

 

이제 빠져나와서 AbstractHandlerMapping.getHandler() 부분을 살펴보겠습니다.

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain handlerExecutionChain ?
          handlerExecutionChain : new HandlerExecutionChain(handler));

    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
       if (interceptor instanceof MappedInterceptor mappedInterceptor) {
          if (mappedInterceptor.matches(request)) {
             chain.addInterceptor(mappedInterceptor.getInterceptor());
          }
       }
       else {
          chain.addInterceptor(interceptor);
       }
    }
    return chain;
}

 

getHandlerInternal() 메서드를 수행한 후 handler의 타입은 HandlerMethod 이고, 수행될 수 있는 인터셉터들을 포함하여 getHandlerExecutionChain() 메서드를 통해 HandlerExecutionChain 객체를 생성합니다.

 

위와 같은 과정을 통해 HandlerMapping을 찾고, HandlerExecutionChain 객체를 생성하여 반환합니다.

다음으로 HandlerExecutionChain을 통해 HandlerAdapter를 찾는 과정을 살펴보겠습니다

 

 

6-2. Handler을 수행할 수 있는 HandlerAdapter 조회 (27번째 라인)

getHandlerAdapter(): HandlerExecutionChain를 통해 HandlerAdapter를 찾습니다.

 

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
       for (HandlerAdapter adapter : this.handlerAdapters) {
          if (adapter.supports(handler)) {
             return adapter;
          }
       }
    }
    throw new ServletException("No adapter for handler [" + handler +
          "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

 

handlerAdapters에는 4개의 어댑터들이 있습니다.

 

예제 코드에서 작성했던 @PostMapping 어노테이션 내부에 @RequestMapping 어노테이션이 존재하기 때문에 위의 for문에서는 RequestMappingHandlerAdapter 어댑터를 반환합니다.

 

또한 위에서 handler은 HandlerMethod로 매핑되는 컨트롤러의 메서드 및 빈, 빈 팩토리 등이 저장되어 있습니다.

 

6-3. HandlerAdapter를 통해 실제 Controller의 비즈니스 로직 호출 (40번째 라인)

6-1, 6-2에서 HandlerExecutionChain, HandlerAdapter를 찾는 과정을 살펴보았습니다.

마지막으로 HandlerAdapter를 통해 실제 Controller의 로직을 실행하는(invoke) 과정을 살펴보겠습니다.

 

HandlerAdapter의 handle() 메서드를 타고 들어가 보겠습니다. 

AbstractHandlerMethodAdapter.handle() -> RequestMappingHandlerAdapter.handleInternal() 

// AbstractHandlerMethodAdapter 추상 클래스

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
       throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}

 

// RequestMappingHandlerAdapter 클래스

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

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
       HttpSession session = request.getSession(false);
       if (session != null) {
          Object mutex = WebUtils.getSessionMutex(session);
          synchronized (mutex) {
             mav = invokeHandlerMethod(request, response, handlerMethod);
          }
       }
       else {
          // No HttpSession available -> no mutex necessary
          mav = invokeHandlerMethod(request, response, handlerMethod);
       }
    }
    else {
       // No synchronization on session demanded at all...
       mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
       if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
          applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
       }
       else {
          prepareResponse(response);
       }
    }

    return mav;
}

 

RequestMappingHandlerAdapter 클래스의 handleInternal() 메서드에서 Controller를 호출하는 로직이 존재합니다. (invokeHandlerMethod())

 

 

// RequestMappingHandlerAdapter 클래스

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

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

    // Obtain wrapped response to enforce lifecycle rule from Servlet spec, section 2.3.3.4
    response = asyncWebRequest.getNativeResponse(HttpServletResponse.class);

    ServletWebRequest webRequest = (asyncWebRequest instanceof ServletWebRequest ?
          (ServletWebRequest) asyncWebRequest : new ServletWebRequest(request, response));

    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
       invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
       invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    invocableMethod.setMethodValidator(this.methodValidator);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    if (asyncManager.hasConcurrentResult()) {
       Object result = asyncManager.getConcurrentResult();
       Object[] resultContext = asyncManager.getConcurrentResultContext();
       Assert.state(resultContext != null && resultContext.length > 0, "Missing result context");
       mavContainer = (ModelAndViewContainer) resultContext[0];
       asyncManager.clearConcurrentResult();
       LogFormatUtils.traceDebug(logger, traceOn -> {
          String formatted = LogFormatUtils.formatValue(result, !traceOn);
          return "Resume with async result [" + formatted + "]";
       });
       invocableMethod = invocableMethod.wrapConcurrentResult(result);
    }

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
       return null;
    }

    return getModelAndView(mavContainer, modelFactory, webRequest);
}

 

invokeHandlerMethod 함수에서 살펴볼 부분은 크게 두 가지 입니다.

  • 25번째 라인: ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(...)
  • 54번째 라인: invokeAndHandle(...)

25번째 라인에서 createInvocableHandlerMethod(handlerMethod) 메서드를 통해 ServletInvocableHandlerMethod 객체를 생성하고 있습니다.

 

그럼 ServletInvocableHandlerMethod 클래스는 무엇일까요??

 

HandlerMethod를 상속받고 있는 클래스 중 하나입니다. 

 

등록된 HandlerMethodReturnValueHandler를 통해 반환 값을 처리하는 기능으로 InvocableHandlerMethod를 상속받고, 메서드 레벨에서의 @ResponseStatus 어노테이션을 통해 응답 상태를 설정할 수 있습니다.

 

그럼 InvocableHandlerMethod는??

HandlerMethodArgumentResolver 리스트를 통해 현재 HTTP 요청에서 받은 인수 값으로 기본 메서드를 호출(invoke)하는 HandlerMethod를 상속받는 클래스입니다.

 

정리하자면, ServletInvocableHandlerMethod는 HttpServletRequest에서 파라미터들을 뽑아내어, 이를 컨트롤러 메서드에 주입 시켜주고, 어노테이션에 따라 반환값을 처리해주는 역할을 합니다. 

 

다시, RequestMappingHandlerAdapter의 코드로 돌아가보겠습니다. 

 

위 23번째 라인의 createInvocableHandlerMethod() 메서드를 통해 ServletInvocableHandlerMethod 객체를 생성하는 코드를 따라가보면 bean, beanFactory, method, parameters 등의 변수가 초기화되어 생성이 됩니다.

 

ServletInvocableHandlerMethod -&gt; InvocableHandlerMethod -&gt; HandlerMethod
ServletInvocableHandlerMethod -> InvocableHandlerMethod -> HandlerMethod

 

ServletInvocableHandlerMethod 객체의 생성 및 설정이 끝나면 위 54번째 라인의 invokeAndHandle() 메서드가 실행됩니다.

// ServletInvocableHandlerMethod 클래스

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
       Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    if (returnValue == null) {
       if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
          disableContentCachingIfNecessary(webRequest);
          mavContainer.setRequestHandled(true);
          return;
       }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
       mavContainer.setRequestHandled(true);
       return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
       this.returnValueHandlers.handleReturnValue(
             returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
       if (logger.isTraceEnabled()) {
          logger.trace(formatErrorForReturnValue(returnValue), ex);
       }
       throw ex;
    }
}

 

invokeAndHandle() Controller의 메서드를 실행하고 반환 값을 처리하는 메서드입니다.

4번째 라인의 invokeForRequest() 메서드 내부를 보면 실제 컨트롤러를 실행하는 로직이 들어갑니다.

// InvocableHandlerMethod 클래스

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
       Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
       logger.trace("Arguments: " + Arrays.toString(args));
    }

    if (shouldValidateArguments() && this.methodValidator != null) {
       this.methodValidator.applyArgumentValidation(
             getBean(), getBridgedMethod(), getMethodParameters(), args, this.validationGroups);
    }

    Object returnValue = doInvoke(args);

    if (shouldValidateReturnValue() && this.methodValidator != null) {
       this.methodValidator.applyReturnValueValidation(
             getBean(), getBridgedMethod(), getReturnType(), returnValue, this.validationGroups);
    }

    return returnValue;
}

 

5번째 라인인 getMethodArgumentValues()에서 Controller의 메서드를 호출하기 위해 필요한 인자 값들을 처리합니다.

@RequestParam, @PathVariable, @RequestHeader 등의 어노테이션이 스프링의 ArgumentResolver에 의해 처리가 되고, 이와 관련한 작업이 수행되는 곳이 getMethodArgumentValues() 메서드입니다.

 

// InvocableHandlerMethod 클래스

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
       Object... providedArgs) throws Exception {

    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
       return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
       MethodParameter parameter = parameters[i];
       parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
       args[i] = findProvidedArgument(parameter, providedArgs);
       if (args[i] != null) {
          continue;
       }
       if (!this.resolvers.supportsParameter(parameter)) {
          throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
       }
       try {
          args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
       }
       catch (Exception ex) {
          // Leave stack trace for later, exception may actually be resolved and handled...
          if (logger.isDebugEnabled()) {
             String exMsg = ex.getMessage();
             if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                logger.debug(formatArgumentError(parameter, exMsg));
             }
          }
          throw ex;
       }
    }
    return args;
}

 

12번째 라인에서 파라미터 개수만큼 순회하여, 파라미터마다 적합한 리졸버를 찾아 해결합니다.

23번째 라인에서 HandlerMethodArgumentResolverComposite.resolveArgument()를 호출하면 

 

// HandlerMethodArgumentResolverComposite 클래스

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
       NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
       throw new IllegalArgumentException("Unsupported parameter type [" +
             parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

@Nullable
public HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
	HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
	if (result == null) {
		for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
			if (resolver.supportsParameter(parameter)) {
				result = resolver;
				this.argumentResolverCache.put(parameter, result);
				break;
			}
		}
	}
	return result;
}

 

getArgumentResolver를 호출하여, 파라미터에 적합한 리졸버를 찾아, resolveArgument()로 파라미터를 해결하여 return합니다.

resolvers에는 27개의 리졸버가 있습니다.

 

 

파라미터를 모두 해결하고, InvocableHandlerMethod 클래스의 invokeForRequest()로 돌아갑시다.

15번째 라인의 doInvoke(args)를 호출하여 실제 컨트롤러를 호출하고, 응답값을 반환합니다.

 

컨트롤러의 응답값이 반환되면, ServletInvocableHandlerMethod로 돌아가서, 22번째 라인에서 반환값을 처리합니다.

(여기에서 리턴 타입에 따라 ModelAndView, ReponseEntity, ResponseBody 등을 각 Handler가 처리합니다.)

 

HandlerMethodReturnValueHandlerComposite.handleReturnValue() -> selectHandler() 

// HandlerMethodReturnValueHandlerComposite 클래스

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
       ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
       throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}

 

returnValueHandlers에는 15개의 핸들러가 있습니다.

 

 

 HandlerMethodReturnValueHandlerComposite 클래스는 List에 등록되어 있는 Handler 중에 위임하여 메서드 반환값을 처리하는 컴포지트 패턴을 적용하고 있습니다.

HandlerMethodReturnValueHandler
: handler method 실행 후 반환 값을 처리하기 위한 전략 인터페이스
 - supportsReturnType() 메서드를 통해 주어진 메서드의 리턴 타입이 처리가 가능한지 판별
 - handleReturnValue() 메서드에서 주어진 반환 값을 토대로 처리

 

 

API의 return type은 String인데요, 여기에 맞는 returnValueHandlers는 RequestResponseBodyMethodProcessor 입니다.

(주로 사용하는 ResponseEntity가 리턴 타입인 경우 HttpEntityMethodProcessor을 Handler로 사용합니다.)

 

또한 @RestController 어노테이션을 사용했기 때문에 ModelAndView가 아닌 String 문자열이 그대로 응답 값으로 반환이 됩니다.

 

RequestResponseBodyMethodProcessor 내부에서는 input, output Message를 생성하여  AbstractMessageConverterMethodProcessor 추상 클래스의 writeWithMessageConverters() 메서드를 호출하고, MediaType을 검사하여 적절한 HttpMessageConverter를 찾아 응답을 처리하고 결과를 반환합니다.

 


정리

DispatcherServlet의 동작 원리에 대해서 살펴보았습니다.

흐름 위주로 따라갔기에, 확인하지 못한 코드들도 많지만, 일단은 동작 흐름을 파악했다는 것에 의의를,,,,

 

간단하게 DispatcherServlet을 구현해보는 것으로 다음 포스팅에서 뵙겠습니다.