1 首先該類有一靜態(tài)語塊,用以加載缺省策略。
static {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
缺省的策略文件為當前包下的DispatcherServlet.properties,主要包括
LocaleResolver(本地化解析器,AcceptHeaderLocaleResolver)
ThemeResolver(主題解析器,FixedThemeResolver)
HandlerMapping(處理器映射,BeanNameUrlHandlerMapping)
HandlerAdapter(控制適配器,多個)
ViewResolver(視圖解析器,InternalResourceViewResolver)
RequestToViewNameTranslator(請求到視圖名的翻譯器,DefaultRequestToViewNameTranslator)
2 通過繼承關系調(diào)用HttpServletBean類的init()初始化方法
在該方法體內(nèi),首先將這個servlet視為一個bean對其包裝并將配置文件中當前servlet下的初始參數(shù)值賦倒這個servlet中。然后調(diào)用子類(FrameworkServlet)的initServletBean(),而子類通過模板模式再調(diào)用其子類(DispatcherServlet)的initFrameworkServlet()方法。在initServletBean()方法中主要代碼片段如下:
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
而在initWebApplicationContext()中,它首先是從ServletContext中獲得由ContextLoader類設置進的key為WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性的值(即Spring容器的實例),然后以實例為parent創(chuàng)建一個新的容器。這也是為什么說一個DispatcherServlet就對應一個WebApplicationContext(每個DispatcherServlet都有各自的上下文)。最后將該WebApplicationContext綁定到到ServletContext上,如果允許發(fā)布話。
由此可以看出:
WebApplicationContextUtils.getWebApplicationContext(servletContext)與
servletContext.getAttribute(SERVLET_CONTEXT_PREFIX+ getServletName())之間的主要區(qū)別在于:后者包括了該servlet的所有配置信息而前者是全局性的沒有這部分信息
3 類DispatcherServlet中initFrameworkServlet()方法初始化工作
initLocaleResolver();
initThemeResolver();
initHandlerMappings();
initHandlerAdapters();
initHandlerExceptionResolvers();
initRequestToViewNameTranslator();
initViewResolvers();
如果在配置文件沒有設置將缺省的策略文件DispatcherServlet.properties。由此可見,因為DispatcherServlet是單例的,所以設置的信息是全局的也就是所有的解析器不能動態(tài)的變化。
4 當調(diào)用類FrameworkServlet中的doGet()/doPost()/doPut()/doDelete()方法時
以上方法均調(diào)用相同方法processRequest(request, response)。注意:Spring放棄了對HttpServlet中service()方法的覆蓋而用具體的操作方法取代。
5 調(diào)用類FrameworkServlet中的processRequest(request, response)方法
try {
doService(request, response);
}
finally {
if (isPublishEvents()) {
this.webApplicationContext.publishEvent(
new ServletRequestHandledEvent(this,
request.getRequestURI(), request.getRemoteAddr(),
request.getMethod(), getServletConfig().getServletName(),
WebUtils.getSessionId(request), getUsernameForRequest(request),
processingTime, failureCause));
}
}
通過代碼摘要可以看出該類有一個webApplicationContext引用,除了執(zhí)行doService()這個核心方法外,每次都會發(fā)布一個ServletRequestHandledEvent事件如果publishEvents為true,其值可以在web.xml文件中增加添加context參數(shù),或servlet初始化參數(shù)(
<init-param>
<param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/application.xml</param-value>
</init-param>)。
DispatcherServlet初始化參數(shù)
參數(shù) |
描述 |
contextClass |
實現(xiàn)WebApplicationContext接口的類,當前的servlet用它來創(chuàng)建上下文。如果這個參數(shù)沒有指定,默認使用XmlWebApplicationContext。 |
contextConfigLocation |
傳給上下文實例(由contextClass指定)的字符串,用來指定上下文的位置。這個字符串可以被分成多個字符串(使用逗號作為分隔符)來支持多個上下文(在多上下文的情況下,如果同一個bean被定義兩次,后面一個優(yōu)先)。 |
namespace |
WebApplicationContext命名空間。默認值是[server-name]-servlet。 |
我們發(fā)布的context是作為ServletContext的一個屬性嗎?默認值為true,屬性名為SERVLET_CONTEXT_PREFIX+ getServletName() |
|
publishEvents |
在執(zhí)行每個請求后是否發(fā)布一個ServletRequestHandledEvent事件?默認值為true |
以上參數(shù)的初始加載是在HttpServletBean類的init()中,而所有DispatcherServlet的解析器是在initFrameworkServlet()方法中。
由此可見,DispatcherServlet的初始參數(shù)加載來源可分為兩部分,
5.1 在web.xml文件對于該servlet配置的<init-param>初始參數(shù)中
5.2 通過Spring的IOC容器實例化后,通過該類的初始化方法,將容器內(nèi)的實例一一引用
6 調(diào)用類DispatcherServlet中的doService(request, response)方法
暴露在request屬性中DispatchServlet的一些特性,并且調(diào)用doDispatch(request, response)方法執(zhí)行真正的分發(fā)動作。
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());
try {
doDispatch(request, response);
}
7 調(diào)用類DispatcherServlet中的doDispatch(request, response)方法
7.1 在區(qū)域上下文持有者(LocaleContextHolder)中當前線程下的值做一次替換。暴露當前localeResolver與request中的Locale
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContextHolder.setLocaleContext(new LocaleContext() {
public Locale getLocale() {
returnlocaleResolver.resolveLocale(request);
}});
由源碼可見,將當前線程下的LocaleContext取出,再從當前區(qū)域解析器(LocaleResolver)中得到Locale前創(chuàng)建為LocaleContext后再賦到LocaleContextHolder中
7.2 在請求上下文持有者(RequestContextHolder)中當前線程下的值做一次替換。暴露當前RequestAttributes到當前線程中
RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(requestAttributes);
在此注意,ServletRequestAttributes類只能獲得session或request有屬性,而無法獲得request的參數(shù)(parameter)
7.3 轉(zhuǎn)換request為MultipartHttpServletRequest類型的實例,如果不是就簡單的返回
protected HttpServletRequest checkMultipart(HttpServletRequest request) {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (request instanceof MultipartHttpServletRequest) {
}
else
returnthis.multipartResolver.resolveMultipart(request);
}
return request;
}
由代碼可以看出,如果要支持文件上傳要有兩個前提
1.在應用的配置文件中添加MultipartResolver(分段文件解析器),目前Spring支持兩種上傳文件的開源項目Commons FileUpload(http://jakarta.apache.org/commons/fileupload)和COSFileUpload (http://www.servlets.com/cos)
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartResolver"/>
注意:Bean的id必須為’multipartResolver’, DispatcherServlet中的成員屬性this.multipartResolver是在初始化時與容器中的實例引用在一起的。
2. 通過multipartResolver.isMultipart(request)判斷request的ContextType是否是’multipart/form-data’。所以表單格式應為
<form method="post" action="upload.form" enctype="multipart/form-data">
<input type="file" name="file"/><input type="submit"/>
這個元素的名字(“file”)和服務器端處理這個表單的bean(在下面將會提到)中類型為byte[]的屬性名相同。在這個表單里我們也聲明了編碼參數(shù)(enctype="multipart/form-data")以便讓瀏覽器知道如何對這個文件上傳表單進行編碼
最后由相應的MultipartResolver(分段文件解析器)將request封裝為與解析器對應的MultipartHttpServletRequest實例并返回
7.4 根據(jù)當前請求URL為其找到指定的處理器(handler)與欄截器(HandlerInterceptor),并將它們封裝為HandlerExecutionChain實例。
HandlerExecutionChain mappedHandler = getHandler(processedRequest, false);
在此過程中分為如下幾個步驟
1. 循環(huán)所有已注冊的HandlerMapping(處理器映射),對于不同的處理器映射對其映射的處理方式不同。BeanNameUrlHandlerMapping只要在配置文件中bean元素的name屬性以/開頭它都認為是一個映射如<bean name=”/editaccount”>。而SimpleUrlHandlerMapping只關注自身定義的<property name=”mappings”>下的那些映射,而且支持Ant風格的定義
2. 通過客戶請求的URL與處理器映映射的鍵值做匹配(也可以使用Ant風格匹配,參見AbstractUrlHandlerMapping.lookupHandler()方法),如果滿足匹配條件,就返回與其對應的處理器(Handler),一般情況下,都是Controller(控制器)接口子孫類的實例。
3. 根據(jù)匹配上的處理器映射取得攔截器數(shù)組,并與找到的處理器一同作為構(gòu)建函數(shù)的參數(shù)創(chuàng)建出HandlerExecutionChain實例
7.5 執(zhí)行所有攔截器的前置方法
HandlerInterceptor接口提供了如下三個方法:
1. preHandle(…)前置方法,在處理器執(zhí)行前調(diào)用,當返回false時,DispatcherServlet認為該攔截器已經(jīng)處理完了請求,而不再繼續(xù)執(zhí)行執(zhí)行鏈中的其它攔截器和處理器
2. postHandle(…)后置方法,在處理器執(zhí)行后調(diào)用
3. afterCompletion(…)完成前置方法,在整個請求完后調(diào)用。注意如果第n個攔截器的preHandle方法返回的是false則只會調(diào)用第n個攔截器之前所有該方法,而n++不會被調(diào)用
另:Spring還提供了一個adapter類HandlerInterceptorAdapter讓用戶更方便的擴展HandlerInterceptor接口
7.6 獲得與當前處理器相匹配的HandlerAdapter(處理器適配器),
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
Iterator it = this.handlerAdapters.iterator();
while (it.hasNext()) {
HandlerAdapter ha = (HandlerAdapter) it.next();
if (ha.supports(handler)) {
return ha;
}
}
循環(huán)所有已注冊的HandlerAdapter(處理器適配器),如果該適配器支持ha.supports(handler)當前處理器,就返回。由此可見,Spring通過HandlerAdapter使處理器解耦,實處理器(Handler)不只是僅僅局限于控制器(Controller)這一種形式。目前Spring可以支持,Servlet、HttpRequestHandler、ThrowawayController、Controller,從這一點上看Spring已為客戶留出了很大空間作為擴展的口子。
7.7 根據(jù)處理器適配器調(diào)用處理器的指定方法,執(zhí)行真正的處理操作。如Controller(控制器)接口調(diào)用handleRequest() 、ThrowawayController接口調(diào)用execute()、Servlet接口調(diào)用service()….但無論調(diào)用那種自理器,HandlerAdapter中的handle()方法都要求返回ModelAndView的一個實例
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
在Spring中最常用的處理器是以Controller接口下的所有子孫類控制器,繼承結(jié)構(gòu)如圖
在這些控制器中又以SimpleFormController控制器最為常用,下圖為該類的完整執(zhí)行過程
7.8 執(zhí)行所有攔截器的后置方法
7.9 根據(jù)處理器(Handle)返回的ModelAndView對象,找到或創(chuàng)建View對象,并調(diào)用View對象的render()方法
render(mv, processedRequest, response)
在此有幾個關鍵性的概念:
ModelAndView:它是模形(Model,實際上只是一個Map,最終會將模形存放到request的屬性中,以Map的key為request屬性的鍵,Map的value為值)與視圖(View)的持有者。而View又提供了兩種形態(tài),即以View接口為祖先的實例對象,以字符串的視圖的名字。該類通過isReference()方法判斷是一個字符串的引用還是一個真正的View對象。注意:該類有些奇怪:它將有狀態(tài)的Model與無狀態(tài)的View合并到一個類中,這就導致每次請求都到對該類型的實例進行創(chuàng)建,而不無緩存。View對象Spring提供了緩存功能。
ViewResolver(視圖解析器):主要提供的功能是從視圖名稱到實際視圖的映射。也就是說只有ModelAndView的isReference()返回true時,才會對視圖的名字做解析工作。這使視圖解析與真正的視圖相分離,這樣我們就可以通過不同的方式配置視圖,如可以采用properties、xml或是提供URI的前、后綴等形式配置視圖。但要注意視圖解析器與視圖之間還是緊密的關系,Sping在此沒實現(xiàn)全部的完全解耦
ViewResolver |
描述 |
AbstractCachingViewResolver |
抽象視圖解析器實現(xiàn)了對視圖的緩存。在視圖被投入使用之前,通常需要進行一些準備工作。從它繼承的視圖解析器將對要解析的視圖進行緩存。 |
InternalResourceViewResolver |
作為UrlBasedViewResolver的子類,它支持InternalResourceView(對Servlet和JSP的包裝),以及其子類JstlView和TilesView。通過setViewClass方法,可以指定用于該解析器生成視圖使用的視圖類。 |
UrlBasedViewResolver |
UrlBasedViewResolver實現(xiàn)ViewResolver,將視圖名直接解析成對應的URL,不需要顯式的映射定義。如果你的視圖名和視圖資源的名字是一致的,就可使用該解析器,而無需進行映射。 |
ResourceBundleViewResolver |
ResourceBundleViewResolver實現(xiàn)ViewResolver,在一個ResourceBundle中尋找所需bean的定義。這個bundle通常定義在一個位于classpath中的屬性文件中。默認的屬性文件是views.properties。 |
XmlViewResolver |
XmlViewResolver實現(xiàn)ViewResolver,支持XML格式的配置文件。該配置文件必須采用與Spring XML Bean Factory相同的DTD。默認的配置文件是/WEB-INF/views.xml。 |
VelocityViewResolver / FreeMarkerViewResolver |
作為UrlBasedViewResolver的子類,它能支持VelocityView(對Velocity模版的包裝)和FreeMarkerView以及它們的子類。 |
View(視圖):處理請求的準備工作,并將該請求提交給某種具體的視圖技術。
如,InternalResourceView會通過RequestDispatcher類調(diào)用include()或forward()方法
RedirectView會通過HttpServletResponse類調(diào)用sendRedirect()方法
AbstractExcelView會產(chǎn)生一個輸出流
區(qū)別:
1、forward與include區(qū)別在于forward本意是讓第一個頁面處理request,第二個頁面處理response.調(diào)用第二個頁面后,程序還會返回第一個頁面繼續(xù)執(zhí)行,但是此時再使用response輸出已經(jīng)沒有作用了;include服務器端的動態(tài)加載,執(zhí)行完第二個頁面的程序后可以回到第一個頁面繼續(xù)輸出,即將第二個頁面的輸出拉到第一個頁面中。
2、forward與include共享Request范圍內(nèi)的對象,而redirect則不行,即:如果一個javabean被聲明為request范圍的話,則被forward到的資源也可以訪問這個javabean,而redriect則不行。
3、forward與include基本上都是轉(zhuǎn)發(fā)到context內(nèi)部的資源,而redirect可以重定向到外部的資源,如: req.sendRedriect("http://www.mocuai.com");