1. 程式人生 > >1.SpringMVC設計理念與DispatcherServlet

1.SpringMVC設計理念與DispatcherServlet

tlist etc exe 理念 安排 contain roo 和源 pat

SpringMVC作為Struts2之後異軍突起的一個表現層框架,正越來越流行,相信javaee的開發者們就算沒使用過SpringMVC,也應該對其略有耳聞。我試圖通過對SpringMVC的設計思想和源碼實現的剖析,從抽象意義上的設計層面和實現意義上的代碼層面兩個方面,逐一揭開SpringMVC神秘的面紗,本文的代碼,都是基於Spring的 3.1.3RELEASE版本。

任何一個框架,都有自己特定的適用領域,框架的設計和實現,必定是為了應付該領域內許多通用的,煩瑣的、基礎的工作而生。SpringMVC作為一個表現層框架,也必須直面Web開發領域中表現層中的幾大課題,並給出自己的回答:

  • URL到框架的映射。

  • http請求參數綁定

  • http響應的生成和輸出

這三大課題,組成一個完整的web請求流程,每一個部分都具有非常廣闊的外延。SpringMVC框架對這些課題的回答又是什麽呢?

學習一個框架,首要的是要先領會它的設計思想。從抽象、從全局上來審視這個框架。其中最具有參考價值的,就是這個框架所定義的核心接口。核心接口定義了框架的骨架,也在最抽象的意義上表達了框架的設計思想。

下面我以一個web請求流程為載體,依次介紹SpringMVC的核心接口和類。

用戶在瀏覽器中,輸入了http://www.xxxx.com/aaa/bbb.ccc的地址,回車後,瀏覽器發起一個http請求。請求到達你的服務器後,首先會被SpringMVC註冊在web.xml中的前端轉發器DispatcherServlet接收,DispatcherServlet是一個標準的Servlet,它的作用是接受和轉發web請求到內部框架處理單元。

下面看一下第一個出現在你面前的核心接口,它是在org.springframework.web.servlet包中定義的HandlerMapping接口:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

public interface HandlerMapping {

String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}
?

為了閱讀方便,我去掉了源碼中的註釋,但是我強烈建議你一定要記得去閱讀它,這樣你才能從框架的設計者口中得到最準確的關於這個類或者接口的設計說明。類中定義的幾個常量,我們先不去管它。關鍵在於這個接口中唯一的方法:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

這個方法就算對於一個java初學者來說,也很容易理解:它只有一個類型為HttpServletRequest的參數,throws Exception的聲明表示它不處理任何類型的異常,HandlerExecutionChain是它的返回類型。

回到DispatcherServlet的處理流程,當DispatcherServlet接收到web請求後,由標準Servlet類處理方法doGet或者doPost,經過幾次轉發後,最終註冊在DispatcherServlet類中的HandlerMapping實現類組成的一個List(有點拗口)會在一個循環中被遍歷。以該web請求的HttpServletRequest對象為參數,依次調用其getHandler方法,第一個不為null的調用結果,將被返回。DispatcherServlet類中的這個遍歷方法不長,貼一下,讓大家有更直觀的了解。

/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or <code>null</code> if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//遍歷處理器映射器
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name ‘" + getServletName() + "‘");
}
//獲取HandlerExecutionChain(處理執行鏈),並返回
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
?

是的,第一步處理就這麽簡單的完成了。一個web請求經過處理後,會得到一個HandlerExecutionChain對象,這就是SpringMVC對URl映射給出的回答。需要留意的是,HandlerMapping接口的getHandler方法參數是HttpServletRequest,這意味著,HandlerMapping的實現類可以利用HttpServletRequest中的 所有信息來做出這個HandlerExecutionChain對象的生成”決策“。這包括,請求頭、url路徑、cookie、session、參數等等一切你從一個web請求中可以得到的任何東西(最常用的是url路徑)。

SpirngMVC的第一個擴展點,就出現在這裏。我們可以編寫任意的HandlerMapping實現類,依據任何策略來決定一個web請求到HandlerExecutionChain對象的生成。可以說,從第一個核心接口的聲明開始,SpringMVC就把自己的靈活性和野心暴露無疑:哥玩的就是”Open-Closed“。

HandlerExecutionChain這個類,就是我們下一個要了解的核心類。從名字可以直觀的看得出,這個對象是一個執行鏈的封裝。熟悉Struts2的都知道,Action對象也是被層層攔截器包裝,這裏可以做個類比,說明SpringMVC確實是吸收了Struts2的部分設計思想。

HandlerExecutionChain類的代碼不長,它定義在org.springframework.web.servlet包中,為了更直觀的理解,先上代碼。

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();
}

}
?

亂七八糟一大堆,相信你也沒全看完,也沒必要全看。其實只需要看兩行足矣。

private final Object handler; //處理器,真正處理業務

private HandlerInterceptor[] interceptors; //攔截器數組
?

不出我們所料,一個實質執行對象,還有一堆攔截器。這不就是Struts2中的實現麽,SpringMVC沒有避嫌,還是采用了這種封裝。得到HandlerExecutionChain這個執行鏈(execution chain)之後,下一步的處理將圍繞其展開。

HandlerInterceptor也是SpringMVC的核心接口,定義如下:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerInterceptor {

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception;

void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;

void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;

}
?

至此,HandlerExecutionChain整個執行脈絡也就清楚了:在真正調用其handler對象前,HandlerInterceptor接口實現類組成的數組將會被遍歷,其preHandle方法會被依次調用,然後真正的handler對象將被調用。

handler對象被調用後,就生成了需要的響應數據,在將處理結果寫到HttpServletResponse對象之前(SpringMVC稱為渲染視圖),其postHandle方法會被依次調用。視圖渲染完成後,最後afterCompletion方法會被依次調用,整個web請求的處理過程就結束了。

在一個處理對象執行之前,之後利用攔截器做文章,這已經成為一種經典的框架設計套路。Struts2中的攔截器會做諸如參數綁定這類復雜的工作,那麽SpringMVC的攔截器具體做些什麽呢?我們暫且不關心,雖然這是很重要的細節,但細節畢竟是細節,我們先來理解更重要的東西。

HandlerInterceptor,是SpringMVC的第二個擴展點的暴露,通過自定義攔截器,我們可以在一個請求被真正處理之前、請求被處理但還沒輸出到響應中、請求已經被輸出到響應中之後這三個時間點去做任何我們想要做的事情。Struts2框架的成功,就是源於這種攔截器的設計,SpringMVC吸收了這種設計思想,並推陳出新,更合理的劃分了三個不同的時間點,從而給web請求處理這個流程,提供了更大的擴展性。

這個HandlerExecutionChain類中以Object引用所聲明的handler對象,到底是個什麽東東?它是怎麽被調用的?

回答這些問題之前,先看SpringMVC中的又一個核心接口,HandlerAdapter:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean supports(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

long getLastModified(HttpServletRequest request, Object handler);

}
?

在DispatcherServlet中,除了HandlerMapping實現類的列表,同樣也註冊了一個HandlerAdapter實現類組成的列表,有代碼為證。

/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;
?

接下來,我們再以DispatcherServlet類中另外一段代碼來回答上述的問題:

/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: Does your handler implement a supported interface like Controller?");
	}

這段代碼已經很明顯了,HandlerExecutionChain中的handler對象會被作為參數傳遞進去,在DispatcherServlet類中註冊的HandlerAdapter實現類列表會被遍歷,然後返回第一個supports方法返回true的HandlerAdapter對象,用這個HandlerAdapter實現類中的handle方法處理handler對象,並返回ModelAndView這個包含了視圖和數據的對象。HandlerAdapter就是SpringMVC提供的第三個擴展點,你可以提供自己的實現類來處理handler對象。

ModelAndView對象的代碼就不貼了,它是SpringMVC中對視圖和數據的一個聚合類。其中的視圖,就是由SpringMVC的最後一個核心接口View所抽象:

package org.springframework.web.servlet;
 
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public interface View {
 
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
 
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
 
	String getContentType();
 
	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
 
}

所有的數據,最後會作為一個Map對象傳遞到View實現類中的render方法,調用這個render方法,就完成了視圖到響應的渲染。這個View實現類,就是來自HandlerAdapter中的handle方法的返回結果。當然從ModelAndView到真正的View實現類有一個解析的過程,ModelAndView中可以有真正的視圖對象,也可以只是有一個視圖的名字,SpringMVC會負責將視圖名稱解析為真正的視圖對象。

至此,我們了解了一個典型的完整的web請求在SpringMVC中的處理過程和其中涉及到的核心類和接口。

在一個典型的SpringMVC調用中,HandlerExecutionChain中封裝handler對象就是用@Controller註解標識的類的一個實例,根據類級別和方法級別的@RequestMapping註解,由默認註冊的DefaultAnnotationHandlerMapping(3.1.3中更新為RequestMappingHandlerMapping類,但是為了向後兼容,DefaultAnnotationHandlerMapping也可以使用)生成HandlerExecutionChain對象,再由AnnotationMethodHandlerAdapter(3.1.3中更新為RequestMappingHandlerAdapter類,但是為了向後兼容,AnnotationMethodHandlerAdapter也可以使用)來執行這個HandlerExecutionChain對象,生成最終的ModelAndView對象後,再由具體的View對象的render方法渲染視圖。

可以看到,作為一個表現層框架,SpringMVC沒有像Struts2那樣激進,並沒有采用和Web容器完全解耦的設計思想,而是以原生的Servlet框架對象為依托,通過合理的抽象,制定了嚴謹的的處理流程。這樣做的結果是,執行效率比Struts2要高,靈活性也上升了一個層次。

上一篇文章《SpringMVC源碼剖析(一)- 從抽象和接口說起》中,我介紹了一次典型的SpringMVC請求處理過程中,相繼粉墨登場的各種核心類和接口。我刻意忽略了源碼中的處理細節,只列出最簡單的類甚至是接口類,目的就是讓大家先從最高層次的抽象意義上來審視SpringMVC這個框架;我也刻意將SpringMVC和Struts2做對比,目的是讓大家看到,SpringMVC究竟吸取了Sturts2設計思想中的哪些精華,又彌補了它的哪些遺憾。

DispatcherServlet作為SpringMVC的核心之中的核心類,再怎麽強調它的重要性也不為過。SpringMVC所有的核心類和接口,都密集地出現在DispatcherServlet的源碼中,SpringMVC源碼剖析,很大程度上可以說也是在剖析DispatcherServlet這一個類。這一篇文章裏,我先說幾點關於DispatcherServlet的前世今生,希望能幫助你更好的理解它。

1.對擴展開放,對修改封閉

SpringMVC是一個基於著名的Open-Closed,即開閉原則進行設計的框架。在Spring官方文檔裏面關於SpringMVC的介紹開宗明義地進行了說明:

A key design principle in Spring Web MVC and in Spring in general is the “Open for extension,closed for modification” principle.

開閉原則是一個很寬泛的原則,具體體現到DispatcherServlet的源碼中,我們可以大致摸得到一些線索:

  • 類中所有的變量聲明,幾乎都以接口的形式給出,並沒有綁定在具體的實現類上。

  • 使用模版方法模式,在父類中對基礎行為進行定義,讓子類實現模版方法擴展行為。

其中第一點,在一個框架的設計中尤為重要,也是貫徹開閉原則最重要的一點。因為當你通過一些高層次的接口或者抽象類,將一個類完成的邏輯或流程編寫完成後(具體點說,是通過一個接口的引用調用接口方法),整個邏輯或流程的功能就被確實的在源碼中固定下來了。可是這時,這些接口或抽象類的具體實現者是誰,還沒有固定!這就給了你的系統或框架近乎無限的擴展性,因為你可以任意安排和實現這些類。

我認為,面向對象設計的精髓,是對現實世界中“行為和契約”的描述。這個“行為和契約”,體現在接口和抽象類的方法聲明中。軟件設計師要用面向對象的眼光去觀察和抽象這個世界中的事物,這裏的事物可以是一些商業邏輯、可以是一些處理流程,然後用高層次的接口去描述這些行為和契約。當你在越抽象的層次上將這些行為和契約描述清楚後,你所設計的系統就是越符合開閉原則的。

SpringMVC框架在面向對象設計上,做出了絕佳的示範。它通過高度抽象的接口,描述出了一次請求處理的流程,從而讓整個框架從一開始就是符合開閉原則的。同時它也提供了這些接口的一系列默認實現類,讓你不需要很復雜的配置,就能很好的使用SpringMVC進行開發。抽象的確是個利器,但是框架絕不能運行在空中樓閣中,SpringMVC提供的的這一系列默認實現類必須要有容身之所。聰明的你可能早已想到:Spring IOC容器。這就引出了我要說的第二點。

2.配置元素的對象化

所有的框架,都需要有這樣一個功能,叫做:配置元素的對象化。因為幾乎所有的框架,都將配置元素集中到外部的xml配置文件中,然後在框架的初始化流程中,對這些配置文件進行解析,再變成java世界中的一個個對象供框架使用,這整個過程,可以被稱為配置元素的對象化。為什麽要有配置文件呢?這個問題的回答也是很簡單,因為沒有人會想要使用一個配置散布在框架中各個java類源碼裏面的框架。框架也不允許使用者這樣子做,因為框架在發布的時候,提供的是一個個jar包文件,jar包內是已經編譯好的class文件。配置文件由使用者外部提供,框架對它進行解析,使用者能得到集中配置的好處,框架也樂於這樣子,可以說是合情合理。

那麽作為Spring產品族的新成員,SpringMVC在設計的時候,相信設計者們不做它想,這一個“配置元素的對象化”功能既然不可避免,那麽使用Spring IOC容器,通過bean配置文件來配置SpringMVC,絕對是不二之選。不可能像Struts2一樣,內部再搞一個別的容器,因為Spring容器本身已經是被高度設計,而且已經在java世界獲得巨大成功。從推廣的角度上來說,如果對spring容器的所有知識,都可以完整的應用到SpringMVC,那麽對於開發者無疑是一個極大的吸引力。

剩下的問題就只有:到底該如何將Spring容器和SpringMVC的初始化過程整合起來呢?

答案就是WebApplicationContext接口,更具體點說,是XmlWebApplicationContext這個Spring上下文實現類。SpringMVC也使用了這一個為了將Spring容器和Web環境整合而特意設計的Spring上下文類。我們打開WebApplicationContext的源碼:

package org.springframework.web.context;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
public interface WebApplicationContext extends ApplicationContext {
 
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
 
	String SCOPE_REQUEST = "request";
 
	String SCOPE_SESSION = "session";
 
	String SCOPE_GLOBAL_SESSION = "globalSession";
 
	String SCOPE_APPLICATION = "application";
 
	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
 
	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
 
	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
 
	ServletContext getServletContext();
 
}

發現它是繼承於ApplicationContext這個普通Spring容器所使用的上下文接口類,除了一些常量的聲明,只多了一個可以獲取到ServletContext的getServletContext()方法。回到上面提到的“行為和契約的描述”上,我們可以大膽的斷言,Spring容器和Web環境的整合,是在ServletContext上做文章。

打開所有使用了Spring的Web項目的web.xml文件,必定有這樣一段配置:

<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

ContextLoaderListener實現了ServletContextListener接口,在Servlet容器啟動的時候,會初始化一個WebApplicationContext的實現類,並將其作為ServletContext的一個屬性設置到Servlet環境中,摘抄源碼如下:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE的值,在上面WebApplicationContext的源碼中的第一個常量中就被聲明,是WebApplicationContext.class.getName() + ".ROOT",更直接一點,它是“org.springframework.web.context.WebApplicationContext.ROOT”。ContextLoaderListener所初始化的這個Spring容器上下文,被稱為根上下文。

SpringMVC在DispatcherServlet的初始化過程中,同樣會初始化一個WebApplicationContext的實現類,作為自己獨有的上下文,這個獨有的上下文,會將上面的根上下文作為自己的父上下文,來存放SpringMVC的配置元素,然後同樣作為ServletContext的一個屬性,被設置到ServletContext中,只不過它的key就稍微有點不同,key和具體的DispatcherServlet註冊在web.xml文件中的名字有關,從這一點也決定了,我們可以在web.xml文件中註冊多個DispatcherServlet,因為Servlet容器中註冊的Servlet名字肯定不一樣,設置到Servlet環境中的key也肯定不同。

由於在Spring容器中,子上下文可以訪問到所有父上下文中的信息,而父上下文訪問不到子上下文的信息,這個根上下文,就很適合作為多個子上下文配置的集中點。以官方文檔中的圖來說明:

技術分享圖片

3.前端控制器

前端控制器,即所謂的Front Controller,體現的是設計模式中的前端控制器模式。前端控制器處理所有從用戶過來的請求。所有用戶的請求都要通過前端控制器。SpringMVC框架和其他請求驅動的表示層框架一樣,也是圍繞一個將請求分發到相應控制器的核心Servlet來設計的。DispatcherServlet和其他框架中的Servlet不一樣的地方在於,它和Spring容器無縫整合在了一起,因此你可以在SpringMVC中使用Spring容器所有的特性。

DispatcherServlet這個前端控制器,在SpringMVC中的作用,以官方文檔中的配圖來說明:

技術分享圖片

整個流程可以被大致描述為:一個http請求到達服務器,被DispatcherServlet接收。DispatcherServlet將請求委派給合適的處理器Controller,此時處理控制權到達Controller對象。Controller內部完成請求的數據模型的創建和業務邏輯的處理,然後再將填充了數據後的模型即model和控制權一並交還給DispatcherServlet,委派DispatcherServlet來渲染響應。DispatcherServlet再將這些數據和適當的數據模版視圖結合,向Response輸出響應。

可以看到Model-View-Controller這三樣東西協同合作,共同體現出MVC的設計理念,三個層次可以分別獨立演化,整個系統架構又清晰又簡潔。這是SpringMVC為我們描述的美好願景,後面我們也將看到,SpringMVC為了實現這一承諾,究竟做出了什麽樣的努力

1.SpringMVC設計理念與DispatcherServlet