SpringMVC完成初始化流程以後,就進入Servlet標準生命週期的第二個階段,即「service」階段。在「service」階段中,每一次Http請求到來,容器都會啓動一個請求線程,經過service()方法,委派到doGet()或者doPost()這些方法,完成Http請求的處理。 java
在初始化流程中,SpringMVC巧妙的運用依賴注入讀取參數,並最終創建一個與容器上下文相關聯的Spring子上下文。這個子上下文,就像Struts2中xwork容器同樣,爲接下來的Http處理流程中各類編程元素提供了容身之所。若是說將Spring上下文關聯到Servlet容器中,是SpringMVC框架的第一個亮點,那麼在請求轉發流程中,SpringMVC對各類處理環節編程元素的抽象,就是另一個獨具匠心的亮點。 web
Struts2採起的是一種徹底和Web容器隔離和解耦的事件機制。諸如Action對象、Result對象、Interceptor對象,這些都是徹底脫離Servlet容器的編程元素。Struts2將數據流和事件處理徹底剝離開來,從Http請求中讀取數據後,下面的事件處理流程就只依賴於這些數據,而徹底不知道有Web環境的存在。 spring
反觀SpringMVC,不管HandlerMapping對象、HandlerAdapter對象仍是View對象,這些核心的接口所定義的方法中,HttpServletRequest和HttpServletResponse對象都是直接做爲方法的參數出現的。這也就意味着,框架的設計者,直接將SpringMVC框架和容器綁定到了一塊兒。或者說,整個SpringMVC框架,都是依託着Servlet容器元素來設計的。下面就來看一下,源碼中是如何體現這一點的。 編程
1.請求轉發的入口 設計模式
就像任何一個註冊在容器中的Servlet同樣,DispatcherServlet也是經過本身的service()方法來接收和轉發Http請求到具體的doGet()或doPost()這些方法的。以一次典型的GET請求爲例,通過HttpServlet基類中service()方法的委派,請求會被轉發到doGet()方法中。doGet()方法,在DispatcherServlet的父類FrameworkServlet類中被覆寫。 安全
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
能夠看到,這裏只是簡單的轉發到processRequest()這個方法。 服務器
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { long startTime = System.currentTimeMillis(); Throwable failureCause = null; // Expose current LocaleResolver and request as LocaleContext. LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable); // Expose current RequestAttributes to current thread. RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = null; if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) { requestAttributes = new ServletRequestAttributes(request); RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); } if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } try { doService(request, response); } catch (ServletException ex) { failureCause = ex; throw ex; } catch (IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally { // Clear request attributes and reset thread-bound context. LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable); if (requestAttributes != null) { RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable); requestAttributes.requestCompleted(); } if (logger.isTraceEnabled()) { logger.trace("Cleared thread-bound request context: " + request); } if (logger.isDebugEnabled()) { if (failureCause != null) { this.logger.debug("Could not complete request", failureCause); } else { this.logger.debug("Successfully completed request"); } } if (this.publishEvents) { // Whether or not we succeeded, publish an event. long processingTime = System.currentTimeMillis() - startTime; this.webApplicationContext.publishEvent( new ServletRequestHandledEvent(this, request.getRequestURI(), request.getRemoteAddr(), request.getMethod(), getServletConfig().getServletName(), WebUtils.getSessionId(request), getUsernameForRequest(request), processingTime, failureCause)); } } }
代碼有點長,理解的要點是以doService()方法爲區隔,前一部分是將當前請求的Locale對象和屬性,分別設置到LocaleContextHolder和RequestContextHolder這兩個抽象類中的ThreadLocal對象中,也就是分別將這兩個東西和請求線程作了綁定。在doService()處理結束後,再恢復回請求前的LocaleContextHolder和RequestContextHolder,也即解除線程綁定。每次請求處理結束後,容器上下文都發布了一個ServletRequestHandledEvent事件,你能夠註冊監聽器來監聽該事件。 app
能夠看到,processRequest()方法只是作了一些線程安全的隔離,真正的請求處理,發生在doService()方法中。點開FrameworkServlet類中的doService()方法。 框架
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
又是一個抽象方法,這也是SpringMVC類設計中的慣用伎倆:父類抽象處理流程,子類給予具體的實現。真正的實現是在DispatcherServlet類中。 ide
讓咱們接着看DispatcherServlet類中實現的doService()方法。
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() + " request for [" + requestUri + "]"); } // 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)) { logger.debug("Taking snapshot of request attributes before include"); attributesSnapshot = new HashMap<String, Object>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) { 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()); 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); try { doDispatch(request, response); } finally { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } }
幾個requet.setAttribute()方法的調用,將前面在初始化流程中實例化的對象設置到http請求的屬性中,供下一步處理使用,其中有容器的上下文對象、本地化解析器等SpringMVC特有的編程元素。不一樣於Struts2中的ValueStack,SpringMVC的數據並無從HttpServletRequest對象中抽離出來再存進另一個編程元素,這也跟SpringMVC的設計思想有關。由於從一開始,SpringMVC的設計者就認爲,不該該將請求處理過程和Web容器徹底隔離。
因此,你能夠看到,真正發生請求轉發的方法doDispatch()中,它的參數是HttpServletRequest和HttpServletResponse對象。這給咱們傳遞的意思也很明確,從request中能獲取到一切請求的數據,從response中,咱們又能夠往服務器端輸出任何響應,Http請求的處理,就應該圍繞這兩個對象來設計。咱們不妨能夠將SpringMVC這種設計方案,是從Struts2的過分設計中吸收教訓,而向Servlet編程的一種迴歸和簡化。
2.請求轉發的抽象描述
接下來讓咱們看看doDispatch()這個整個請求轉發流程中最核心的方法。DispatcherServlet所接收的Http請求,通過層層轉發,最終都是彙總到這個方法中來進行最後的請求分發和處理。doDispatch()這個方法的內容,就是SpringMVC整個框架的精華所在。它經過高度抽象的接口,描述出了一個MVC(Model-View-Controller)設計模式的實現方案。Model、View、Controller三種層次的編程元素,在SpringMVC中都有大量的實現類,各類處理細節也是千差萬別。可是,它們最後都是由,也都能由doDispatch()方法來統一描述,這就是接口和抽象的威力,萬變不離其宗。
先來看一下doDispatch()方法的廬山真面目。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; int interceptorIndex = -1; try { ModelAndView mv; boolean errorView = false; try { processedRequest = checkMultipart(request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest, false); if (mappedHandler == null || mappedHandler.getHandler() == 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. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { String requestUri = urlPathHelper.getRequestUri(request); logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // Apply preHandle methods of registered interceptors. HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) { triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); return; } interceptorIndex = i; } } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // Do we need view name translation? if (mv != null && !mv.hasView()) { mv.setViewName(getDefaultViewName(request)); } // Apply postHandle methods of registered interceptors. if (interceptors != null) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv); } } } catch (ModelAndViewDefiningException ex) { logger.debug("ModelAndViewDefiningException encountered", ex); mv = ex.getModelAndView(); } catch (Exception ex) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(processedRequest, response, handler, ex); errorView = (mv != null); } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, processedRequest, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isDebugEnabled()) { logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() + "': assuming HandlerAdapter completed request handling"); } } // Trigger after-completion for successful outcome. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null); } catch (Exception ex) { // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } catch (Error err) { ServletException ex = new NestedServletException("Handler processing failed", err); // Trigger after-completion for thrown exception. triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex); throw ex; } finally { // Clean up any resources used by a multipart request. if (processedRequest != request) { cleanupMultipart(processedRequest); } } }
真是千呼萬喚始出來,猶抱琵琶半遮面。咱們在第一篇《SpringMVC源碼剖析(一)- 從抽象和接口提及》中所描述的各類編程元素,依次出如今該方法中。HandlerMapping、HandlerAdapter、View這些接口的設計,咱們在第一篇中已經講過。如今咱們來重點關注一下HandlerExecutionChain這個對象。
從上面的代碼中,很明顯能夠看出一條線索,整個方法是圍繞着如何獲取HandlerExecutionChain對象,執行HandlerExecutionChain對象獲得相應的視圖對象,再對視圖進行渲染這條主線來展開的。HandlerExecutionChain對象顯得異常重要。
由於Http請求要進入SpringMVC的處理體系,必須由HandlerMapping接口的實現類映射Http請求,獲得一個封裝後的HandlerExecutionChain對象。再由HandlerAdapter接口的實現類來處理這個HandlerExecutionChain對象所包裝的處理對象,來獲得最後渲染的視圖對象。
視圖對象是用ModelAndView對象來描述的,名字已經很是直白,就是數據和視圖,其中的數據,由HttpServletRequest的屬性獲得,視圖就是由HandlerExecutionChain封裝的處理對象處理後獲得。固然HandlerExecutionChain中的攔截器列表HandlerInterceptor,會在處理過程的先後依次被調用,爲處理過程留下充足的擴展點。
全部的SpringMVC框架元素,都是圍繞着HandlerExecutionChain這個執行鏈來發揮效用。咱們來看看,HandlerExecutionChain類的代碼。
package org.springframework.web.servlet; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.util.CollectionUtils; public class HandlerExecutionChain { private final Object handler; private HandlerInterceptor[] interceptors; private List<HandlerInterceptor> interceptorList; public HandlerExecutionChain(Object handler) { this(handler, null); } public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) { if (handler instanceof HandlerExecutionChain) { HandlerExecutionChain originalChain = (HandlerExecutionChain) handler; this.handler = originalChain.getHandler(); this.interceptorList = new ArrayList<HandlerInterceptor>(); CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList); CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList); } else { this.handler = handler; this.interceptors = interceptors; } } public Object getHandler() { return this.handler; } public void addInterceptor(HandlerInterceptor interceptor) { initInterceptorList(); this.interceptorList.add(interceptor); } public void addInterceptors(HandlerInterceptor[] interceptors) { if (interceptors != null) { initInterceptorList(); this.interceptorList.addAll(Arrays.asList(interceptors)); } } private void initInterceptorList() { if (this.interceptorList == null) { this.interceptorList = new ArrayList<HandlerInterceptor>(); } if (this.interceptors != null) { this.interceptorList.addAll(Arrays.asList(this.interceptors)); this.interceptors = null; } } public HandlerInterceptor[] getInterceptors() { if (this.interceptors == null && this.interceptorList != null) { this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]); } return this.interceptors; } @Override public String toString() { if (this.handler == null) { return "HandlerExecutionChain with no handler"; } StringBuilder sb = new StringBuilder(); sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]"); if (!CollectionUtils.isEmpty(this.interceptorList)) { sb.append(" and ").append(this.interceptorList.size()).append(" interceptor"); if (this.interceptorList.size() > 1) { sb.append("s"); } } return sb.toString(); } }
一個攔截器列表,一個執行對象,這個類的內容十分的簡單,它蘊含的設計思想,卻十分的豐富。
1.攔截器組成的列表,在執行對象被調用的先後,會依次執行。這裏能夠當作是一個的AOP環繞通知,攔截器能夠對處理對象爲所欲爲的進行處理和加強。這裏明顯是吸取了Struts2中攔截器的設計思想。這種AOP環繞式的擴展點設計,也幾乎成爲全部框架必備的內容。
2.實際的處理對象,即handler對象,是由Object對象來引用的。
private final Object handler;
之因此要用一個java世界最基礎的Object對象引用來引用這個handler對象,是由於連特定的接口也不但願綁定在這個handler對象上,從而使handler對象具備最大程度的選擇性和靈活性。
咱們常說,一個框架最高層次的抽象是接口,可是這裏SpringMVC更進了一步。在最後的處理對象上面,SpringMVC沒有對它作任何的限制,只要是java世界中的對象,均可以用來做爲最後的處理對象,來生成視圖。極端一點來講,你甚至能夠將另一個MVC框架集成到SpringMVC中來,也就是爲何SpringMVC官方文檔中,竟然還有集成其餘表現層框架的內容。這一點,在全部表現層框架中,是獨領風騷,冠絕羣雄的。
3.結語
SpringMVC的成功,源於它對開閉原則的運用和遵照。也正所以,才使得整個框架具備如此強大的描述和擴展能力。這也許和SpringMVC出現和興起的時間有關,正是經歷了Struts1到Struts2這些Web開發領域MVC框架的更新換代,它的設計者才能站在前人的肩膀上。知道了如何將事情作的糟糕以後,你或許才知道如何將事情作得好。
但願在這個系列裏面分享的SpringMVC源碼閱讀經驗,能幫助讀者們從更高的層次來審視SpringMVC框架的設計,也但願這裏所描述的一些基本設計思想,能在你更深刻的瞭解SpringMVC的細節時,對你有幫助。哲學纔是惟一的、最終的武器,在一個框架的設計上,尤爲是如此。常常地體會一個框架設計者的設計思想,對你更好的使用它,是有莫大的益處的。