SpringMVC完成初始化流程之後,就進入Servlet標準生命週期的第二個階段,即“service”階段。在“service”階段中,每一次Http請求到來,容器都會啟動一個請求執行緒,通過service()方法,委派到doGet()或者doPost()這些方法,完成Http請求的處理。
在初始化流程中,SpringMVC巧妙的運用依賴注入讀取引數,並最終建立一個與容器上下文相關聯的Spring子上下文。這個子上下文,就像Struts2中xwork容器一樣,為接下來的Http處理流程中各種程式設計元素提供了容身之所。如果說將Spring上下文關聯到Servlet容器中,是SpringMVC框架的第一個亮點,那麼在請求轉發流程中,SpringMVC對各種處理環節程式設計元素的抽象,就是另外一個獨具匠心的亮點。
Struts2採取的是一種完全和Web容器隔離和解耦的事件機制。諸如Action物件、Result物件、Interceptor物件,這些都是完全脫離Servlet容器的程式設計元素。Struts2將資料流和事件處理完全剝離開來,從Http請求中讀取資料後,下面的事件處理流程就只依賴於這些資料,而完全不知道有Web環境的存在。
反觀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事件,你可以註冊監聽器來監聽該事件。
可以看到,processRequest()方法只是做了一些執行緒安全的隔離,真正的請求處理,發生在doService()方法中。點開FrameworkServlet類中的doService()方法。
- protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
- throws Exception;
又是一個抽象方法,這也是SpringMVC類設計中的慣用伎倆:父類抽象處理流程,子類給予具體的實現。真正的實現是在DispatcherServlet類中。
讓我們接著看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的細節時,對你有幫助。哲學才是唯一的、最終的武器,在一個框架的設計上,尤其是如此。經常地體會一個框架設計者的設計思想,對你更好的使用它,是有莫大的益處的。