您好,登錄后才能下訂單哦!
這篇文章主要介紹SpringMVC設計理念與DispatcherServlet的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
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 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設計理念與DispatcherServlet的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。