簡介
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前端控制器分發、處理,整個過程,限於筆者目前水平,沒有把整個串聯起來,形成一條完整的調用鏈,希望有朋友可以分享.