1. 程式人生 > >DispatcherServlet程式碼分析及執行過程

DispatcherServlet程式碼分析及執行過程

1    首先該類有一靜態語塊,用以載入預設策略。

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)

    在該方法體內,首先將這個servlet視為一個bean對其包裝並將配置檔案中當前servlet下的初始引數值賦倒這個servlet中。然後呼叫子類(FrameworkServlet)的initServletBean(),

而子類通過模板模式再呼叫其子類(DispatcherServlet)的initFrameworkServlet()方法。在initServletBean()方法中主要程式碼片段如下:

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

而在initWebApplicationContext(),它首先是從ServletContext中獲得由ContextLoader類設定進的keyWebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

屬性的值(即Spring容器的例項),然後以例項為parent建立一個新的容器。這也是為什麼說一個DispatcherServlet就對應一個WebApplicationContext(每個DispatcherServlet都有各自的上下文)。最後將該WebApplicationContext繫結到到ServletContext

由此可以看出:

WebApplicationContextUtils.getWebApplicationContext(servletContext)

servletContext.getAttribute(SERVLET_CONTEXT_PREFIX+ getServletName())之間的主要區別在於:後者包括了該servlet的所有配置資訊而前者是全域性性的沒有這部分資訊

initLocaleResolver();

initThemeResolver();

initHandlerMappings();

initHandlerAdapters();

initHandlerExceptionResolvers();

initRequestToViewNameTranslator();

initViewResolvers();

如果在配置檔案沒有設定將預設的策略檔案DispatcherServlet.properties。由此可見,因為DispatcherServlet是單例的,所以設定的資訊是全域性的也就是所有的解析器不能動態的變化。

4    當呼叫類FrameworkServlet中的doGet()/doPost()/doPut()/doDelete()方法時

以上方法均呼叫相同方法processRequest(request, response)。注意:Spring放棄了對HttpServlet中service()方法的覆蓋而用具體的操作方法取代。

5    呼叫類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引用,除了執行doService()這個核心方法外,每次都會發佈一個ServletRequestHandledEvent事件如果publishEventstrue,其值可以在web.xml檔案中增加新增context引數,或servlet初始化引數(

<init-param>

<param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/application.xml</param-value>

</init-param>)

DispatcherServlet初始化引數

引數

描述

contextClass

實現WebApplicationContext介面的類,當前的servlet用它來建立上下文。如果這個引數沒有指定,預設使用XmlWebApplicationContext

contextConfigLocation

傳給上下文例項(由contextClass指定)的字串,用來指定上下文的位置。這個字串可以被分成多個字串(使用逗號作為分隔符)來支援多個上下文(在多上下文的情況下,如果同一個bean被定義兩次,後面一個優先)。

namespace

WebApplicationContext名稱空間。預設值是[server-name]-servlet

我們釋出的context是作為ServletContext的一個屬性嗎?預設值為true,屬性名為SERVLET_CONTEXT_PREFIX+ getServletName()

publishEvents

在執行每個請求後是否釋出一個ServletRequestHandledEvent事件?預設值為true

由此可見,DispatcherServlet的初始引數載入來源可分為兩部分,

5.1   web.xml檔案對於該servlet配置的<init-param>初始引數中

5.2   通過Spring的IOC容器例項化後,通過該類的初始化方法,將容器內的例項一一引用

6    呼叫類DispatcherServlet中的doService(request, response)方法

暴露在request屬性中DispatchServlet的一些特性,並且呼叫doDispatch(request, response)方法執行真正的分發動作。

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    呼叫類DispatcherServlet中的doDispatch(request, response)方法

7.1    在區域上下文持有者(LocaleContextHolder中當前執行緒下的值做一次替換。暴露當前localeResolverrequest中的Locale

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();

LocaleContextHolder.setLocaleContext(new LocaleContext() {

public Locale getLocale() {

returnlocaleResolver.resolveLocale(request);

}});

由原始碼可見,將當前執行緒下的LocaleContext取出,再從當前區域解析器(LocaleResolver)中得到Locale前建立為LocaleContext後再賦到LocaleContextHolder

7.2   在請求上下文持有者(RequestContextHolder中當前執行緒下的值做一次替換。暴露當前RequestAttributes到當前執行緒中

RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();

ServletRequestAttributes requestAttributes =new ServletRequestAttributes(request);

RequestContextHolder.setRequestAttributes(requestAttributes);

在此注意,ServletRequestAttributes類只能獲得sessionrequest有屬性,而無法獲得request的引數(parameter)

7.3   轉換request為MultipartHttpServletRequest型別的例項,如果不是就簡單的返回

    protected HttpServletRequest checkMultipart(HttpServletRequest request) {

if (this.multipartResolver != null &&this.multipartResolver.isMultipart(request)) {

if (requestinstanceof MultipartHttpServletRequest) {

}

else

returnthis.multipartResolver.resolveMultipart(request);

}

return request;

}

由程式碼可以看出,如果要支援檔案上傳要有兩個前提

<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)判斷requestContextType是否是 multipart/form-data。所以表單格式應為

    <form method="post" action="upload.form" enctype="multipart/form-data">
            <input type="file" name="file"/><input type="submit"/>
這個元素的名字(“file”)和伺服器端處理這個表單的bean(在下面將會提到)中型別為byte[]的屬性名相同。在這個表單裡我們也聲明瞭編碼引數(enctype="multipart/form-data")以便讓瀏覽器知道如何對這個檔案上傳表單進行編碼

最後由相應的MultipartResolver(分段檔案解析器)將request封裝為與解析器對應的MultipartHttpServletRequest例項並返回

7.4   根據當前請求URL為其找到指定的處理器(handler)與欄截器(HandlerInterceptor),並將它們封裝為HandlerExecutionChain例項。

HandlerExecutionChain mappedHandler = getHandler(processedRequest,false);

在此過程中分為如下幾個步驟

1.  迴圈所有已註冊的HandlerMapping(處理器對映),對於不同的處理器對映對其對映的處理方式不同。BeanNameUrlHandlerMapping只要在配置檔案中bean元素的name屬性以/開頭它都認為是一個對映如<bean name=”/editaccount”>。而SimpleUrlHandlerMapping只關注自身定義的<property name=”mappings”>下的那些對映,而且支援Ant風格的定義

2.  通過客戶請求的URL與處理器映對映的鍵值做匹配(也可以使用Ant風格匹配,參見AbstractUrlHandlerMapping.lookupHandler()方法),如果滿足匹配條件,就返回與其對應的處理器(Handler),一般情況下,都是Controller(控制器)介面子孫類的例項。

3.  根據匹配上的處理器對映取得攔截器陣列,並與找到的處理器一同作為構建函式的引數創建出HandlerExecutionChain例項

7.5   執行所有攔截器的前置方法

HandlerInterceptor介面提供瞭如下三個方法:

1.  preHandle(…)前置方法,在處理器執行前呼叫,當返回false時,DispatcherServlet認為該攔截器已經處理完了請求,而不再繼續執行執行鏈中的其它攔截器和處理器

2.  postHandle(…)後置方法,在處理器執行後呼叫

3.  afterCompletion(…)完成前置方法,在整個請求完後呼叫。注意如果第n個攔截器的preHandle方法返回的是false則只會呼叫第n個攔截器之前所有該方法,而n++不會被呼叫

另:Spring還提供了一個adapterHandlerInterceptorAdapter讓使用者更方便的擴充套件HandlerInterceptor介面

7.6   獲得與當前處理器相匹配的HandlerAdapter(處理器介面卡)

protected HandlerAdaptergetHandlerAdapter(Object handler) throws ServletException {

Iterator it = this.handlerAdapters.iterator();

while (it.hasNext()) {

HandlerAdapter ha = (HandlerAdapter) it.next();

if (ha.supports(handler)) {

return ha;

}

}

迴圈所有已註冊的HandlerAdapter(處理器介面卡),如果該介面卡支援ha.supports(handler)當前處理器,就返回。由此可見,Spring通過HandlerAdapter使處理器解耦,實處理器(Handler)不只是僅僅侷限於控制器(Controller)這一種形式。目前Spring可以支援,ServletHttpRequestHandlerThrowawayControllerController,從這一點上看Spring已為客戶留出了很大空間作為擴充套件的口子。

7.7   根據處理器介面卡呼叫處理器的指定方法,執行真正的處理操作。如Controller(控制器)介面呼叫handleRequest()ThrowawayController介面呼叫execute()Servlet介面呼叫service()….但無論呼叫那種自理器,HandlerAdapter中的handle()方法都要求返回ModelAndView的一個例項

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

在Spring中最常用的處理器是以Controller介面下的所有子孫類控制器,繼承結構如圖

在這些控制器中又以SimpleFormController控制器最為常用,下圖為該類的完整執行過程

7.8   執行所有攔截器的後置方法

7.9   根據處理器(Handle)返回的ModelAndView物件,找到或建立View物件,並呼叫View物件的render()方法

render(mv, processedRequest, response)

在此有幾個關鍵性的概念:

ModelAndView:它是模形(Model,實際上只是一個Map,最終會將模形存放到request的屬性中,以Map的key為request屬性的鍵,Map的value為值)與檢視(View)的持有者。而View又提供了兩種形態,即以View介面為祖先的例項物件,以字串的檢視的名字。該類通過isReference()方法判斷是一個字串的引用還是一個真正的View物件。注意:該類有些奇怪:它將有狀態的Model與無狀態的View合併到一個類中,這就導致每次請求都到對該型別的例項進行建立,而不無快取。View物件Spring提供了快取功能。

ViewResolver(檢視解析器):主要提供的功能是從檢視名稱到實際檢視的對映。也就是說只有ModelAndView的isReference()返回true時,才會對檢視的名字做解析工作。這使檢視解析與真正的檢視相分離,這樣我們就可以通過不同的方式配置檢視,如可以採用properties、xml或是提供URI的前、字尾等形式配置檢視。但要注意檢視解析器與檢視之間還是緊密的關係,Sping在此沒實現全部的完全解耦

ViewResolver

描述

AbstractCachingViewResolver

抽象檢視解析器實現了對檢視的快取。在檢視被投入使用之前,通常需要進行一些準備工作。從它繼承的檢視解析器將對要解析的檢視進行快取。

InternalResourceViewResolver

作為UrlBasedViewResolver的子類,它支援InternalResourceView(對ServletJSP的包裝),以及其子類JstlViewTilesView。通過setViewClass方法,可以指定用於該解析器生成檢視使用的檢視類。

UrlBasedViewResolver

UrlBasedViewResolver實現ViewResolver,將檢視名直接解析成對應的URL,不需要顯式的對映定義。如果你的檢視名和檢視資源的名字是一致的,就可使用該解析器,而無需進行對映。

ResourceBundleViewResolver

ResourceBundleViewResolver實現ViewResolver,在一個ResourceBundle中尋找所需bean的定義。這個bundle通常定義在一個位於classpath中的屬性檔案中。預設的屬性檔案是views.properties

XmlViewResolver

XmlViewResolver實現ViewResolver,支援XML格式的配置檔案。該配置檔案必須採用與Spring XML Bean Factory相同的DTD。預設的配置檔案是/WEB-INF/views.xml

VelocityViewResolver /FreeMarkerViewResolver

作為UrlBasedViewResolver的子類,它能支援VelocityView(對Velocity模版的包裝)和FreeMarkerView以及它們的子類。

View(檢視):處理請求的準備工作, 並將該請求提交給某種具體的檢視技術。

如,InternalResourceView會通過RequestDispatcher類呼叫include()forward()方法

 RedirectView會通過HttpServletResponse類呼叫sendRedirect()方法

AbstractExcelView會產生一個輸出流

區別:

1forwardinclude區別在於forward本意是讓第一個頁面處理request,第二個頁面處理response.呼叫第二個頁面後,程式還會返回第一個頁面繼續執行,但是此時再使用response輸出已經沒有作用了;include伺服器端的動態載入,執行完第二個頁面的程式後可以回到第一個頁面繼續輸出,即將第二個頁面的輸出拉到第一個頁面中。

2forwardinclude共享Request範圍內的物件,redirect則不行,即:如果一個javabean被宣告為request範圍的話,則被forward到的資源也可以訪問這個javabean,redriect則不行。

3forwardinclude基本上都是轉發到context內部的資源,而redirect可以重定向到外部的資源,如: req.sendRedriect("http://www.mocuai.com");