SpringMVC啓動加載、請求分析

簡介

web.xml文件中的配置:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

下面看下DispatcherServlet的繼承關係:

DispatcherServlet其實是一個Servlet,用於初始化各個功能的實現類,比如異常處理、視圖處理、請求映射等;且繼承了FrameworkServlet類,FrameworkServlet是Spring的基礎Servlet,提供集成一個基於JavaBean的整體解決方案的Spring上下文;FrameworkServlet又繼承了HttpServletBean,HttpServletBean主要做一些初始化的工作,如將web.xml中的配置參數設置到Servlet中. 再直觀感受下DispatcherServlet上下文層次結構關係:

該圖有助於下面分析啓動、請求的分析理解,圖片來自https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc

啓動源碼分析

由於DispatcherServlet是一個Servlet,啓動時初始化首先調用init方法,進入其父類的 org.springframework.web.servlet.HttpServletBean#init方法中:

PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

獲取了配置文件dispatcher-servlet.xml,跟進 initServletBean()方法,此處用了委派方式,具體實現交給子類FrameworkServlet去實現,主要看方法裏這段代碼:

this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();

跟進第一段代碼,由於第一次啓動,wac爲nul,再進入初始化方法代碼, org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext),再進入 org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)方法:

Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
    this.logger.debug("Servlet with name '" + getServletName() +
            "' will try to create custom WebApplicationContext context of class '" +
            contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
        (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);

上面主要創建了ConfigurableWebApplicationContext對象,然後調用了configureAndRefreshWebApplicationContext方法用來做初始化工作,再進入到 org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext方法:

if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    // The application context id is still set to its original default value
    // -> assign a more useful id based on available information
    if (this.contextId != null) {
        wac.setId(this.contextId);
    }
    else {
        // Generate default id...
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
    }
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
    ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();

最後一句 wac.refresh()代碼,跟進去發現調用的是 org.springframework.context.support.AbstractApplicationContext#refresh的方法,ConfigurableWebApplicationContext沒有重寫refresh方法,所以調用了父類的默認實現方法,進入這個方法,正是spring解析配置文件、加載bean的方法,這裏不作分析,具體可參考之前的文章.

請求分析

服務起來後,在瀏覽器中輸入 http://localhost:8082/ok,由於FrameworkServlet重寫了Servlet的service方法,無疑會進入到該方法中:

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

if (HttpMethod.PATCH.matches(request.getMethod())) {
    processRequest(request, response);
}
else {
    super.service(request, response);
}
}

該方法會判斷請求的方式,無論哪種都會調用processRequest方法,進入該方法會看到這麼一段代碼:

doService(request, response);

直覺告訴我們,這個方法就是用來處理請求的,再跟進去,調用的是子類DispatcherServlet中的doService方法,該方法開始會設置請求頭信息,下面有這麼一段代碼:

doDispatch(request, response);

同上,直接跟進去,摘取部分代碼:

ModelAndView mv = null;
Exception dispatchException = null;
// Determine handler for the current request.
//獲取MappedHandler
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
//獲取HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

進入HandlerAdapter的處理方法, ha.handle(processedRequest,response,mappedHandler.getHandler()),發現這麼一段代碼:

invocableMethod.invokeAndHandle(webRequest, mavContainer);

進入 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle:

//根據HandlerMapping中請求路徑使用反射調用Controller中的求方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
    if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {
        mavContainer.setRequestHandled(true);
        return;
    }
}
else if (StringUtils.hasText(this.responseReason)) {
    mavContainer.setRequestHandled(true);
    return;
}
mavContainer.setRequestHandled(false);
try {
    //處理返回結果
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
    if (logger.isTraceEnabled()) {
        logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
    }
    throw ex;
}

進入到 org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest方法:

// 
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
    StringBuilder sb = new StringBuilder("Invoking [");
    sb.append(getBeanType().getSimpleName()).append(".");
    sb.append(getMethod().getName()).append("] method with arguments ");
    sb.append(Arrays.asList(args));
    logger.trace(sb.toString());
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
    logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;

getMethodArgumentValues方法用於獲取請求時所帶的參數,doInvoke方法使用反射調用Controller中的方法,所以當斷點執行到該方法中時,會調用OkController類中的ok方法,returnValue是ok方法的返回值,此時請求已經完成,下面再看響應.再回到 org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle中, this.returnValueHandlers.handleReturnValue(returnValue,getReturnValueType(returnValue),mavContainer,webRequest)方法用於處理響應,進入該方法 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue:

//根據returnValue、returnType獲取不同的HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
//handler有很多子類,會調用不同的實現類
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

由於OkController中的ok方法返回的是一個對象,所以進入到的handler的子類是 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue:

ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
mavContainer.setRequestHandled(true);
// Try even with null return value. ResponseBodyAdvice could get involved.
writeWithMessageConverters(returnValue, returnType, webRequest);

繼續跟進writeWithMessageConverters方法,繼續跟進 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)方法,在該方法中會把響應寫入到流中返回給客戶端. 至此服務端的響應也簡單的介紹完畢.

總結

實際的流程遠比筆者介紹的複雜的太多,感興趣的朋友可以打斷點調試去探索,其中涉及到很多知識點都沒有去過多的分析,後面的文章筆者會涉及;筆者非常想從tomcat容器啓動,到Servlet的加載,再到Spring中bean初始化,然後tomcat容器處理請求,分配給Servlet去處理,再到DispatcherServlet前端控制器分發、處理,整個過程,限於筆者目前水平,沒有把整個串聯起來,形成一條完整的調用鏈,希望有朋友可以分享.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章