第二章 Spring MVC入門
2.1、Spring Web MVC是什麼
Spring Web MVC是一種基於Java的實現了Web MVC設計模式的請求驅動型別的輕量級Web框架,即使用了MVC架構模式的思想,將web層進行職責解耦,基於請求驅動指的就是使用請求-響應模型,框架的目的就是幫助我們簡化開發,Spring Web MVC也是要簡化我們日常Web開發的。
Spring Web MVC也是服務到工作者模式的實現,但進行可優化。前端控制器是DispatcherServlet;
應用控制器其實拆為處理器對映器(Handler Mapping)進行處理器管理和檢視解析器(View Resolver)進行檢視管理;頁面控制器/動作/處理器為Controller介面ModelAndView handleRequest(request, response)
方法)的實現(也可以是任何的POJO類);支援本地化(Locale)解析、主題(Theme)解析及檔案上傳等;提供了非常靈活的資料驗證、格式化和資料繫結機制;提供了強大的約定大於配置(慣例優先原則)的契約式程式設計支援。
2、Spring Web MVC能幫我們做什麼
√進行更簡潔的Web層的開發;
√天生與Spring框架整合(如IoC容器、AOP等);
√提供強大的約定大於配置的契約式程式設計支援;
√能簡單的進行Web層的單元測試;
√支援靈活的URL到頁面控制器的對映;
√非常容易與其他檢視技術整合,如Velocity、FreeMarker等等,因為模型資料不放在特定的API裡,而是放在一個Model裡(Map
√非常靈活的資料驗證、格式化和資料繫結機制,能使用任何物件進行資料繫結,不必實現特定框架的API;
√提供一套強大的JSP標籤庫,簡化JSP開發;
√支援靈活的本地化、主題等解析;
√更加簡單的異常處理;
√對靜態資源的支援;
√支援Restful風格。
2.3、Spring Web MVC架構
Spring Web MVC框架也是一個基於請求驅動的Web框架,並且也使用了前端控制器模式來進行設計,再根據請求對映規則分發給相應的頁面控制器(動作/處理器)進行處理。首先讓我們整體看一下Spring Web MVC處理請求的流程:
2.3.1、Spring Web MVC處理請求的流程
如圖2-1
圖2-1
具體執行步驟如下:
1、 首先使用者傳送請求————>前端控制器,前端控制器根據請求資訊(如URL)來決定選擇哪一個頁面控制器進行處理並把請求委託給它,即以前的控制器的控制邏輯部分;圖2-1中的1、2步驟;
2、 頁面控制器接收到請求後,進行功能處理,首先需要收集和繫結請求引數到一個物件,這個物件在Spring Web MVC中叫命令物件,並進行驗證,然後將命令物件委託給業務物件進行處理;處理完畢後返回一個ModelAndView(模型資料和邏輯檢視名);圖2-1中的3、4、5步驟;
3、 前端控制器收回控制權,然後根據返回的邏輯檢視名,選擇相應的檢視進行渲染,並把模型資料傳入以便檢視渲染;圖2-1中的步驟6、7;
4、 前端控制器再次收回控制權,將響應返回給使用者,圖2-1中的步驟8;至此整個結束。
問題:
1、 請求如何給前端控制器?
2、 前端控制器如何根據請求資訊選擇頁面控制器進行功能處理?
3、 如何支援多種頁面控制器呢?
4、 如何頁面控制器如何使用業務物件?
5、 頁面控制器如何返回模型資料?
6、 前端控制器如何根據頁面控制器返回的邏輯檢視名選擇具體的檢視進行渲染?
7、 不同的檢視技術如何使用相應的模型資料?
首先我們知道有如上問題,那這些問題如何解決呢?請讓我們先繼續,在後邊依次回答。
2.3.2、Spring Web MVC架構
1、Spring Web MVC核心架構圖,如圖2-2
圖2-2
架構圖對應的DispatcherServlet核心程式碼如下:
//前端控制器分派方法
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 {
//檢查是否是請求是否是multipart(如檔案上傳),如果是將通過MultipartResolver解析
processedRequest = checkMultipart(request);
//步驟2、請求到處理器(頁面控制器)的對映,通過HandlerMapping進行對映
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
//步驟3、處理器適配,即將我們的處理器包裝成相應的介面卡(從而支援多種型別的處理器)
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 304 Not Modified快取支援
//此處省略具體程式碼
// 執行處理器相關的攔截器的預處理(HandlerInterceptor.preHandle)
//此處省略具體程式碼
// 步驟4、由介面卡執行處理器(呼叫處理器相應功能處理方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
// 執行處理器相關的攔截器的後處理(HandlerInterceptor.postHandle)
//此處省略具體程式碼
}
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);
}
//步驟5 步驟6、解析檢視並進行檢視的渲染
//步驟5 由ViewResolver解析View(viewResolver.resolveViewName(viewName, locale))
//步驟6 檢視在渲染時會把Model傳入(view.render(mv.getModelInternal(), request, response);)
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");
}
}
// 執行處理器相關的攔截器的完成後處理(HandlerInterceptor.afterCompletion)
//此處省略具體程式碼
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);
}
}
}
核心架構的具體流程步驟如下:
1、 首先使用者傳送請求——>DispatcherServlet,前端控制器收到請求後自己不進行處理,而是委託給其他的解析器進行處理,作為統一訪問點,進行全域性的流程控制;
2、 DispatcherServlet——>HandlerMapping, HandlerMapping將會把請求對映為HandlerExecutionChain物件(包含一個Handler處理器(頁面控制器)物件、多個HandlerInterceptor攔截器)物件,通過這種策略模式,很容易新增新的對映策略;
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter將會把處理器包裝為介面卡,從而支援多種型別的處理器,即介面卡設計模式的應用,從而很容易支援很多型別的處理器;
4、 HandlerAdapter——>處理器功能處理方法的呼叫,HandlerAdapter將會根據適配的結果呼叫真正的處理器的功能處理方法,完成功能處理;並返回一個ModelAndView物件(包含模型資料、邏輯檢視名);
5、 ModelAndView的邏輯檢視名——> ViewResolver, ViewResolver將把邏輯檢視名解析為具體的View,通過這種策略模式,很容易更換其他檢視技術;
6、 View——>渲染,View會根據傳進來的Model模型資料進行渲染,此處的Model實際是一個Map資料結構,因此很容易支援其他檢視技術;
7、返回控制權給DispatcherServlet,由DispatcherServlet返回響應給使用者,到此一個流程結束。
此處我們只是講了核心流程,沒有考慮攔截器、本地解析、檔案上傳解析等,後邊再細述。
到此,再來看我們前邊提出的問題:
1、 請求如何給前端控制器?這個應該在web.xml中進行部署描述。
2、 前端控制器如何根據請求資訊選擇頁面控制器進行功能處理? 我們需要配置HandlerMapping進行對映
3、 如何支援多種頁面控制器呢?配置HandlerAdapter從而支援多種型別的頁面控制器
4、 如何頁面控制器如何使用業務物件?可以預料到,肯定利用Spring IoC容器的依賴注入功能
5、 頁面控制器如何返回模型資料?使用ModelAndView返回
6、 前端控制器如何根據頁面控制器返回的邏輯檢視名選擇具體的檢視進行渲染? 使用ViewResolver進行解析
7、 不同的檢視技術如何使用相應的模型資料? 因為Model是一個Map資料結構,很容易支援其他檢視技術
在此我們可以看出具體的核心開發步驟:
1、 DispatcherServlet在web.xml中的部署描述,從而攔截請求到Spring Web MVC
2、 HandlerMapping的配置,從而將請求對映到處理器
3、 HandlerAdapter的配置,從而支援多種型別的處理器
4、 ViewResolver的配置,從而將邏輯檢視名解析為具體檢視技術
5、處理器(頁面控制器)的配置,從而進行功能處理
2.4、Spring Web MVC優勢
1、清晰的角色劃分:前端控制器(DispatcherServlet
)、請求到處理器對映(HandlerMapping)、處理器介面卡(HandlerAdapter)、檢視解析器(ViewResolver)、處理器或頁面控制器(Controller)、驗證器( Validator)、命令物件(Command 請求引數繫結到的物件就叫命令物件)、表單物件(Form Object 提供給表單展示和提交到的物件就叫表單物件)。
2、分工明確,而且擴充套件點相當靈活,可以很容易擴充套件,雖然幾乎不需要;
3、由於命令物件就是一個POJO,無需繼承框架特定API,可以使用命令物件直接作為業務物件;
4、和Spring 其他框架無縫整合,是其它Web框架所不具備的;
5、可適配,通過HandlerAdapter可以支援任意的類作為處理器;
6、可定製性,HandlerMapping、ViewResolver等能夠非常簡單的定製;
7、功能強大的資料驗證、格式化、繫結機制;
8、利用Spring提供的Mock物件能夠非常簡單的進行Web層單元測試;
9、本地化、主題的解析的支援,使我們更容易進行國際化和主題的切換。
10、強大的JSP標籤庫,使JSP編寫更容易。
………………還有比如RESTful風格的支援、簡單的檔案上傳、約定大於配置的契約式程式設計支援、基於註解的零配置支援等等。