Sunday Study

Dispatcherservlet 1

Dispatcher Servlet(1) - 프론트 컨트롤러 , 동작 방식

ServletContainer와 SpringContainer는 무엇이 다른가?

주요 쟁점

Servlet

Servlet Container

서블릿 컨테이너는 오직 서블릿 API 만 지원하는 것을 말한다 (JSP, JSTL 까지 포함해서)

애플리케이션 서버는 Java EE (EJB, JMS, CDI, JTA, 서블릿 API) 의 전체를 지원한다

서블릿 컨테이너에서 서블릿을 관리하는 과정

  1. 사용자의 요청(url)에 대해 적절한 서블릿을 매핑
  2. 서블릿에 매핑되면 서블릿 컨테이너는 서블릿 인스턴스가 생성되었는지 체크하여 없을 경우 JVM에 의해 서블릿이 실행될 수 있도록 서블릿 인스턴스 생성
  3. 서블릿 컨테이너는 init → service → destroy 순으로 메소드 호출
  4. destroy : 서블릿 객체가 삭제되는 시점은 웹서버에서 웹 애플리케이션 서비스가 중지되는 시점

HttpServlet( Abstract Class)

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String msg = lStrings.getString("http.method_get_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String msg = lStrings.getString("http.method_post_not_supported");
    sendMethodNotAllowed(req, resp, msg);
}

서블릿이 GET , POST 요청을 처리할 수 있도록 서버가 서비스 메소드를 호출합니다. 해당 메소드를 overriding 하여 다양한 form의 데이터를 다루고 반환할 수 있다.

DispatcherServlet

FrontController

Front Controller Design Pattern - GeeksforGeeks

application의 모든 요청은 앞단의 handler에 의해 처리되며 해당 handler에서 적절한 다음 handler로 요청이 발송된다.

  • 서블릿 컨테이너의 제일 앞에서 모든 요청을 받아 처리( 세부 controller 로 요청을 위임)
  • 프론트 컨트롤러를 제외한 나머지 컨트롤러들은 서블릿을 사용하지 않아도 됨
  • 장점 : 한 곳에서 사용자의 모든 요청을 다룰수 있기 때문에 공통 패턴에 대한 적용이 중복적으로 사용되지 않을 수 있다.

한줄 정리

동작 흐름

  1. DispatcherServlet으로 request가 들어온다 . request → HttpServletRequest
  2. 공통 로직을 처리한 다음 HandlerMapping에 위임하여 요청을 처리할 Handler(Controller)를 찾는다.
  3. 해당 Handler를 처리할 수 있는 HandlerAdapter를 탐색한다.
    • HandlerAdapter : @Controller or @RestController
  4. HandlerAdapter를 통해 핸들링 메소드를 실행한다. 반환값은 Model and View이다.
  5. 반환 된 View 이름을 ViewResolver에 전달하고 ViewResolver는 해당 View객체를 반환한다.
  6. DispatcherServlet은 반환된 View에게 Model(데이터)를 전달한다. 해당 결과가 종합된 화면이 클라이언트에게 제공된다.
  7. DispatcherServlet은 위에서 종합된 결과를 클라이언트에게 HttpServletResponse형태로 제공한다.
    • @Controller와 달리 @RestController를 사용하면 위의 과정중에 5,6이 제외된다.
    • MessageConverter를 이용

코드적 흐름

  1. WAS는 DispatcherServlet에게 HttpServletRequest , HttpServletResponse 객체를 전달
  2. doService() 메소드 호출
    • 내부적으로 doDispatch() 메소드 호출 - doService()
    • DispatcherServlet에 대한 작업만 수행 , HttpServletRequest 에 대한 공통 작업

      attribute setting

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    logRequest(request);
    // attribute setting 

    try {
	doDispatch(request, response); // 내부적으로 doDispatch 호출
    } 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);
	}
    }
}
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);
                multipartRequestParsed = (processedRequest != request);

                // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest); // 적절한 Handler(Controller)를 찾음
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                // resource cache 여부 확인 , 동일한 request 재요청시에 대한 선 처리
                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;
                    }
                }
                // intercepter의 선 처리 ( 통과할 경우 true , 다음 작업 실행 )
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // view 를 찾고 model을 매핑 , @RestController일 경우 null이기 때문에 실행 x
                applyDefaultViewName(processedRequest, mv);
                // interceptor 의 후 처리
                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 NestedServletException("Handler dispatch failed", err);
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
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;
}
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");
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

ModelAndView object with the name of the view and the required model data, or null if the request has been handled directly

코드 상의 DispatcherServlet 작동 순서

  1. Dispatcher Servlet에서 doService() 호출
  2. doService() 내부적으로 doDispatch() 호출
  3. doDispatch()

    4 : getHandler() : HandlerMapping

    5 : getHandlerApdater() : Handler 를 처리할 수 있는 HandlerAdapter를 찾는다.

    Intercepteer preHandle

    6 : handle() : HandlerApdater에서 해당 컨트롤러를 통해 요청 처리

    Intercepter postHandle

    7 : applyDefaultViewName :

    8 : processDispatchResult :

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

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
}
protected void render(
    ModelAndView mv,
    HttpServletRequest request,
    HttpServletResponse response) throws Exception {

    View view;
    String viewName = mv.getViewName(); // view 의 논리 이름
    if (viewName != null) {
        // view의 논리 이름 ViewResolver에 전달하여 물리 이름으로 변환 , View 객체를 반환받는다.
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    }
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        // 반환받은 view 객체에 model(data)를 매핑
        view.render(mv.getModelInternal(), request, response);
    }
}
@Nullable
protected View resolveViewName(String viewName, @Nullable Map < String, Object > model,
    Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver: this.viewResolvers) {
            // view 이름을 바탕으로 viewResolver를 통해 view 객체를 반환
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

Intercepter가 한번 더 동작을 한다고 ?????

// doDispatch()
if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
}

//applyAfterConcurrentHandlingStarted()
void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        if (interceptor instanceof AsyncHandlerInterceptor) {
            try {
                AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;
                asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);
            } catch (Throwable ex) {
                if (logger.isErrorEnabled()) {
                    logger.error("Interceptor [" + interceptor + "] failed in afterConcurrentHandlingStarted", ex);
                }
            }
        }
    }
}

참고

DispatcherServlet - Part 1

DispatcherServlet - Part 2