SpringMVC工作流程及程式碼分析
每談到SpringMVC的工作流程,首先接觸到的就是下面這個圖。從這個圖可以大致明白SpringMVC是如何工作的。但是我是一個喜歡探究來龍去脈的人,如果不告訴我為什麼這麼做,單單知道流程就是這樣,抱歉,我真的記不住,更不用提裡面這麼多專業名詞了。所以,通過翻閱了原始碼,大致知道流程是具體怎麼實現的,也學到了一些新的設計模式,所以我將閱讀原始碼的所得記錄下來,希望本文可以幫助到和我一樣喜歡探究來龍去脈的人。
1,SpringMVC是如何被初始化的?
是通過在web.xml配置檔案中配置servlet,servlet的class指向org.springframework.web.servlet.DispatcherServlet,並匹配指定的url(一般情況下使用“/”匹配所有url),指定的請求都會進入DispatcherServlet,由DispatcherServlet進行處理。
<!-- spring mvc核心:分發servlet --> <servlet> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- spring mvc的配置檔案 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springMVC.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
2,DispatcherServlet的例項化
為什麼要講DispatcherServlet的初始化,而不是直接說SpringMVC的工作流程,因為在初始化的過程中,建立了HandlerMappings,HandlerAdapters,我會在下面描述這兩個如何定義及使用的,有了這些概念,再接觸SpringMVC的工作流程,就會非常清晰流暢了。
Tomcat啟動時,就會對DispatcherServlet進行例項化,呼叫他的init()方法。這裡並沒有直接在DispatcherServlet中重寫init(),而是使用了模板方法模式。抽象類HttpServletBean中定義並實現了init(),FrameworkServlet繼承了HttpServletBean,DispatcherServlet繼而繼承了FrameworkServlet。如果對於模板方法模式不瞭解,可以檢視我的另外一篇文章
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } // Let subclasses do whatever initialization they like.讓子類去實現的方法 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
2.1 FrameworkServlet的initServeltBean()
@Override protected final void initServletBean() throws ServletException { ... try { // 初始化 WebApplicationContext (即SpringMVC的IOC容器) this.webApplicationContext = initWebApplicationContext(); initFrameworkServlet(); //這個沒有具體實現,如果後續發現會補上 } catch (ServletException ex) { } catch (RuntimeException ex) { } ... }
2.2 FrameworkServlet的initServeltBean()
protected WebApplicationContext initWebApplicationContext() {
//獲取在web.xml中定義的監聽器ContextLoaderListener,初始化並註冊在ServeltBean中的根容器,即Spring容器 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { // A context instance was injected at construction time -> use it 因為webApplicationContext不為空,說明在構造時已經注入。
wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { // The context has not yet been refreshed -> provide services such as // setting the parent context, setting the application context id, etc if (cwac.getParent() == null) { // The context instance was injected without an explicit parent -> set // the root application context (if any; may be null) as the parent cwac.setParent(rootContext); //將Spring容器設為SpringMVC容器的父容器,這也就是為什麼,SpringMVC容器可以呼叫Spring容器而反過來不可以。 } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = findWebApplicationContext(); //如果為空,則進行查詢,能找到就說明上下文已在別處初始化,詳見2.3 } if (wac == null) { // No context instance is defined for this servlet -> create a local one wac = createWebApplicationContext(rootContext); //如果webApplicationContext仍為空,則以Spring的容器作為父容器建立一個新的。
} if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. onRefresh(wac); //模板方法,由DispatcherServlet實現,詳見2.4 } if (this.publishContext) { // 釋出這個webApplicationContext容器到ServeltContext中。 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); if (this.logger.isDebugEnabled()) { this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); } } return wac; }
2.3 查詢webApplicationContext: findWebApplicationContext();
在servletContext中根據attrName查詢
protected WebApplicationContext findWebApplicationContext() { String attrName = getContextAttribute(); if (attrName == null) { return null; }
//從ServletContext中查詢已釋出的容器 WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } return wac; }
2.4 DispatcherServelt 中的
從原始碼中可以看到,在這個方法中,主要是對各個元件的初始化,包括在整個流程中非常重要的 處理器對映器(HandlerMapping) 和 處理器介面卡(HandlerAdapter)
protected void onRefresh(ApplicationContext context) { initStrategies(context); }
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
2.4.1 HanderMapping是什麼?
2.4.2 HanderAdapter是什麼?
3 DispatcherServelt處理請求
根據Servlet處理請求的流程,是要呼叫其doGet(),doPost()方法。DispatcherServelt本質也是個Servlet,所以這兩個方法是在其父類FrameworkServelt中實現的。
3.1 FrameworkServelt中的doGet(),doPost()
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
這些方法都統一呼叫了processRequest(request, response);
3.2 processRequest(request, response)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; // 返回與當前執行緒相關聯的 LocaleContext LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); // 根據請求構建 LocaleContext,公開請求的語言環境為當前語言環境 LocaleContext localeContext = buildLocaleContext(request); // 返回當前繫結到執行緒的 RequestAttributes RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); // 根據請求構建ServletRequestAttributes ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); //WebAsyncManager:用來管理非同步請求的處理。什麼時候要用到非同步處理呢?就是業務邏輯複雜(或者其他原因),為了避免請求執行緒阻塞,需要委託給另一個執行緒的時候。 // 獲取當前請求的 WebAsyncManager,如果沒有找到則建立 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 使 LocaleContext 和 requestAttributes 關聯 initContextHolders(request, localeContext, requestAttributes); try { // 由 DispatcherServlet 實現 doService(request, response); } catch (ServletException ex) { } catch (IOException ex) { } catch (Throwable ex) { } finally { // 重置 LocaleContext 和 requestAttributes,解除關聯 resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); }// 釋出 ServletRequestHandlerEvent 事件 publishRequestHandledEvent(request, startTime, failureCause); } }
3.3 DispatcherServetl 中的 doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; //processedRequest:加工過的請求 HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; //上傳相關的?後續補充 // 獲取當前請求的WebAsyncManager,如果沒找到則建立並與請求關聯 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // 檢查是否有 Multipart,有則將請求轉換為 Multipart 請求 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 遍歷所有的 HandlerMapping 找到與請求對應的 Handler,並將其與一堆攔截器封裝到 HandlerExecution 物件中。(如果對handler不理解,不要急,到第二章去看) mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // 遍歷所有的 HandlerAdapter,找到可以處理該 Handler 的 HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 處理 last-modified 請求頭 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 遍歷攔截器,執行它們的 preHandle() 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } try { // 執行實際的處理程式 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } finally { if (asyncManager.isConcurrentHandlingStarted()) { return; } } applyDefaultViewName(request, mv); // 遍歷攔截器,執行它們的 postHandle() 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } // 處理執行結果,是一個 ModelAndView 或 Exception,然後進行渲染 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { } catch (Error err) { } finally { if (asyncManager.isConcurrentHandlingStarted()) { // 遍歷攔截器,執行它們的 afterCompletion() 方法 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); return; } // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } }
通過閱讀原始碼,已經可以整理出最開始的流程圖和原始碼之間的對應了,還有一些東西有空會補充上。知其然,更要知其所以然,才是與別人拉開差距的真正意義。