本文將介紹 Spring 2.5 新增的 Sping MVC 註解功能,講述如何使用註解配置替換傳統的基於 XML 的 Spring MVC 配置。java
使用太低版本 Spring MVC 的讀者都知道:當建立一個 Controller 時,咱們須要直接或間接地實現 org.springframework.web.servlet.mvc.Controller 接口。通常狀況下,咱們是經過繼承SimpleFormController 或 MultiActionController 來定義本身的 Controller 的。在定義 Controller 後,一個重要的事件是在SpringMVC的配置文件中經過 HandlerMapping 定義請求和控制器的映射關係,以便將二者關聯起來。來看一下基於註解的 Controller 是如何定義作到這一點的,下面是使用註解的 BbtForumController:web
一、BbtForumController.javaspring
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.util.Collection; @Controller //<——① @RequestMapping("/forum.do") public class BbtForumController { @Autowired private BbtForumService bbtForumService; @RequestMapping //<——② public String listAllBoard() { bbtForumService.getAllBoard(); System.out.println("call listAllBoard method."); return "listBoard"; } }
從上面代碼中,咱們能夠看出 BbtForumController 和通常的類並無區別,它沒有實現任何特殊的接口,於是是一個地道的 POJO。讓這個 POJO 不同凡響的魔棒就是 Spring MVC 的註解!json
在 ① 處使用了兩個註解,分別是 @Controller 和 @RequestMapping。在「使用 Spring 2.5 基於註解驅動的 IoC」這篇文章裏,筆者曾經指出過 @Controller、@Service 以及 @Repository 和 @Component 註解的做用是等價的:將一個類成爲 Spring 容器的 Bean。因爲 Spring MVC 的 Controller 必須事先是一個 Bean,因此 @Controller 註解是不可缺乏的。數組
真正讓 BbtForumController 具有 Spring MVC Controller 功能的是 @RequestMapping 這個註解。@RequestMapping 能夠標註在類定義處,將 Controller 和特定請求關聯起來;還能夠標註在方法簽名處,以便進一步對請求進行分流。在 ① 處,咱們讓 BbtForumController 關聯「/forum.do」的請求,而 ② 處,咱們具體地指定 listAllBoard() 方法來處理請求。因此在類聲明處標註的 @RequestMapping 至關於讓 POJO 實現了 Controller 接口,而在方法定義處的 @RequestMapping 至關於讓 POJO 擴展 Spring 預約義的 Controller(如 SimpleFormController 等)。session
爲了讓基於註解的 Spring MVC 真正工做起來,須要在 Spring MVC 對應的 xxx-servlet.xml 配置文件中作一些手腳。在此以前,仍是先來看一下 web.xml 的配置吧:mvc
二、web.xml:啓用 Spring 容器和 Spring MVC 框架app
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <display-name>Spring Annotation MVC Sample</display-name> <!-- Spring 服務層的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring 容器啓動監聽器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Spring MVC 的Servlet,它將加載WEB-INF/annomvc-servlet.xml 的 配置文件,以啓動Spring MVC模塊--> <servlet> <servlet-name>annomvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>annomvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
web.xml 中定義了一個名爲 annomvc 的 Spring MVC 模塊,按照 Spring MVC 的契約,須要在 WEB-INF/annomvc-servlet.xml 配置文件中定義 Spring MVC 模塊的具體配置。annomvc-servlet.xml 的配置內容以下所示:框架
三、annomvc-servlet.xmljsp
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- ①:對web包中的全部類進行掃描,以完成Bean建立和自動依賴注入的功能 --> <context:component-scan base-package="com.baobaotao.web"/> <!-- ②:啓動Spring MVC的註解功能,完成請求和註解POJO的映射 --> <bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"/> <!-- ③:對模型視圖名稱的解析,即在模型視圖名稱添加先後綴 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> </beans>
由於 Spring 全部功能都在 Bean 的基礎上演化而來,因此必須事先將 Controller 變成 Bean,這是經過在類中標註 @Controller 並在 annomvc-servlet.xml 中啓用組件掃描機制來完成的,如 ① 所示。
在 ② 處,配置了一個 AnnotationMethodHandlerAdapter,它負責根據 Bean 中的 Spring MVC 註解對 Bean 進行加工處理,使這些 Bean 變成控制器並映射特定的 URL 請求。
而 ③ 處的工做是定義模型視圖名稱的解析規則,這裏咱們使用了 Spring 2.5 的特殊命名空間,即 p 命名空間,它將原先須要經過 <property> 元素配置的內容轉化爲 <bean> 屬性配置,在必定程度上簡化了 <bean> 的配置。
啓動 Tomcat,發送 http://localhost/forum.do URL 請求,BbtForumController 的 listAllBoard() 方法將響應這個請求,並轉向 WEB-INF/jsp/listBoard.jsp 的視圖頁面。
在低版本的 Spring MVC 中,咱們能夠經過繼承 MultiActionController 讓一個 Controller 處理多個 URL 請求。使用 @RequestMapping 註解後,這個功能更加容易實現了。請看下面的代碼:
四、每一個請求處理參數對應一個 URL
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class BbtForumController { @Autowired private BbtForumService bbtForumService; @RequestMapping("/listAllBoard.do") // <—— ① public String listAllBoard() { bbtForumService.getAllBoard(); System.out.println("call listAllBoard method."); return "listBoard"; } @RequestMapping("/listBoardTopic.do") // <—— ② public String listBoardTopic(int topicId) { bbtForumService.getBoardTopics(topicId); System.out.println("call listBoardTopic method."); return "listTopic"; } }
在這裏,咱們分別在 ① 和 ② 處爲 listAllBoard() 和 listBoardTopic() 方法標註了 @RequestMapping 註解,分別指定這兩個方法處理的 URL 請求,這至關於將 BbtForumController 改造爲 MultiActionController。這樣 /listAllBoard.do 的 URL 請求將由 listAllBoard() 負責處理,而 /listBoardTopic.do?topicId=1 的 URL 請求則由 listBoardTopic() 方法處理。
對於處理多個 URL 請求的 Controller 來講,咱們傾向於經過一個 URL 參數指定 Controller 處理方法的名稱(如 method=listAllBoard),而非直接經過不一樣的 URL 指定 Controller 的處理方法。使用 @RequestMapping 註解很容易實現這個經常使用的需求。來看下面的代碼:
五、一個 Controller 對應一個 URL,由請求參數決定請求處理方法
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/bbtForum.do") // <—— ① 指定控制器對應URL請求 public class BbtForumController { @Autowired private BbtForumService bbtForumService; // <—— ② 若是URL請求中包括"method=listAllBoard"的參數,由本方法進行處理 @RequestMapping(params = "method=listAllBoard") public String listAllBoard() { bbtForumService.getAllBoard(); System.out.println("call listAllBoard method."); return "listBoard"; } // <—— ③ 若是URL請求中包括"method=listBoardTopic"的參數,由本方法進行處理 @RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(int topicId) { bbtForumService.getBoardTopics(topicId); System.out.println("call listBoardTopic method."); return "listTopic"; } }
在類定義處標註的 @RequestMapping 讓 BbtForumController 處理全部包含 /bbtForum.do 的 URL 請求,而 BbtForumController 中的請求處理方法對 URL 請求的分流規則在 ② 和 ③ 處定義分流規則按照 URL 的 method 請求參數肯定。因此分別在類定義處和方法定義處使用 @RequestMapping 註解,就能夠很容易經過 URL 參數指定 Controller 的處理方法了。
@RequestMapping 註解中除了 params 屬性外,還有一個經常使用的屬性是 method,它可讓 Controller 方法處理特定 HTTP 請求方式的請求,如讓一個方法處理 HTTP GET 請求,而另外一個方法處理 HTTP POST 請求,以下所示:
六、讓請求處理方法處理特定的 HTTP 請求方法
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/bbtForum.do") public class BbtForumController { @RequestMapping(params = "method=createTopic",method = RequestMethod.POST) public String createTopic(){ System.out.println("call createTopic method."); return "createTopic"; } }
這樣只有當 /bbtForum.do?method=createTopic 請求以 HTTP POST 方式提交時,createTopic() 方法纔會進行處理。
按契約綁定
Controller 的方法標註了 @RequestMapping 註解後,它就能處理特定的 URL 請求。咱們不由要問:請求處理方法入參是如何綁定 URL 參數的呢?在回答這個問題以前先來看下面的代碼:
七、按參數名匹配進行綁定
@RequestMapping(params = "method=listBoardTopic") //<—— ① topicId入參是如何綁定URL請求參數的? public String listBoardTopic(int topicId) { bbtForumService.getBoardTopics(topicId); System.out.println("call listBoardTopic method."); return "listTopic"; }
當咱們發送 http://localhost//bbtForum.do?method=listBoardTopic&topicId=10 的 URL 請求時,Spring 不但讓 listBoardTopic() 方法處理這個請求,並且還將 topicId 請求參數在類型轉換後綁定到 listBoardTopic() 方法的 topicId 入參上。而 listBoardTopic() 方法的返回類型是 String,它將被解析爲邏輯視圖的名稱。也就是說 Spring 在如何給處理方法入參自動賦值以及如何將處理方法返回值轉化爲 ModelAndView 中的過程當中存在一套潛在的規則,不熟悉這個規則就不可能很好地開發基於註解的請求處理方法,所以瞭解這個潛在規則無疑成爲理解 Spring MVC 框架基於註解功能的核心問題。
咱們不妨從最多見的開始提及:請求處理方法入參的類型能夠是 Java 基本數據類型或 String 類型,這時方法入參按參數名匹配的原則綁定到 URL 請求參數,同時還自動完成 String 類型的 URL 請求參數到請求處理方法參數類型的轉換。下面給出幾個例子:
特別的,若是入參是基本數據類型(如 int、long、float 等),URL 請求參數中必定要有對應的參數,不然將拋出 TypeMismatchException 異常,提示沒法將 null 轉換爲基本數據類型。
另外,請求處理方法的入參也能夠一個 JavaBean,以下面的 User 對象就能夠做爲一個入參:
八、User.java:一個 JavaBean
package com.baobaotao.web; public class User { private int userId; private String userName; //省略get/setter方法 public String toString(){ return this.userName +","+this.userId; } }
下面是將 User 做爲 listBoardTopic() 請求處理方法的入參:
九、使用 JavaBean 做爲請求處理方法的入參
@RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(int topicId,User user) { bbtForumService.getBoardTopics(topicId); System.out.println("topicId:"+topicId); System.out.println("user:"+user); System.out.println("call listBoardTopic method."); return "listTopic"; }
http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom這時,若是咱們使用如下的 URL 請求:
topicId URL 參數將綁定到 topicId 入參上,而 userId 和 userName URL 參數將綁定到 user 對象的 userId 和 userName 屬性中。和 URL 請求中不容許沒有 topicId 參數不一樣,雖然 User 的 userId 屬性的類型是基本數據類型,但若是 URL 中不存在 userId 參數,Spring 也不會報錯,此時 user.userId 值爲 0。若是 User 對象擁有一個 dept.deptId 的級聯屬性,那麼它將和 dept.deptId URL 參數綁定。
經過註解指定綁定的 URL 參數
若是咱們想改變這種默認的按名稱匹配的策略,好比讓 listBoardTopic(int topicId,User user) 中的 topicId 綁定到 id 這個 URL 參數,那麼能夠經過對入參使用 @RequestParam 註解來達到目的:
十、經過 @RequestParam 註解指定
package com.baobaotao.web; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; … @Controller @RequestMapping("/bbtForum.do") public class BbtForumController { @RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(@RequestParam("id") int topicId,User user) { bbtForumService.getBoardTopics(topicId); System.out.println("topicId:"+topicId); System.out.println("user:"+user); System.out.println("call listBoardTopic method."); return "listTopic"; } … }
這裏,對 listBoardTopic() 請求處理方法的 topicId 入參標註了 @RequestParam("id") 註解,因此它將和 id 的 URL 參數綁定。
綁定模型對象中某個屬性
Spring 2.0 定義了一個 org.springframework.ui.ModelMap 類,它做爲通用的模型數據承載對象,傳遞數據供視圖所用。咱們能夠在請求處理方法中聲明一個 ModelMap 類型的入參,Spring 會將本次請求模型對象引用經過該入參傳遞進來,這樣就能夠在請求處理方法內部訪問模型對象了。來看下面的例子:
十一、使用 ModelMap 訪問請示對應的隱含模型對象
@RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(@RequestParam("id")int topicId, User user,ModelMap model) { bbtForumService.getBoardTopics(topicId); System.out.println("topicId:" + topicId); System.out.println("user:" + user); //① 將user對象以currUser爲鍵放入到model中 model.addAttribute("currUser",user); return "listTopic"; }
對於當次請求所對應的模型對象來講,其全部屬性都將存放到 request 的屬性列表中。象上面的例子,ModelMap 中的 currUser 屬性將放到 request 的屬性列表中,因此能夠在 JSP 視圖頁面中經過 request.getAttribute(「currUser」) 或者經過 ${currUser} EL 表達式訪問模型對象中的 user 對象。從這個角度上看, ModelMap 至關因而一個向 request 屬性列表中添加對象的一條管道,藉由 ModelMap 對象的支持,咱們能夠在一個不依賴 Servlet API 的 Controller 中向 request 中添加屬性。
在默認狀況下,ModelMap 中的屬性做用域是 request 級別是,也就是說,當本次請求結束後,ModelMap 中的屬性將銷燬。若是但願在多個請求中共享 ModelMap 中的屬性,必須將其屬性轉存到 session 中,這樣 ModelMap 的屬性才能夠被跨請求訪問。
Spring 容許咱們有選擇地指定 ModelMap 中的哪些屬性須要轉存到 session 中,以便下一個請求屬對應的 ModelMap 的屬性列表中還能訪問到這些屬性。這一功能是經過類定義處標註 @SessionAttributes 註解來實現的。請看下面的代碼:
十二、使模型對象的特定屬性具備 Session 範圍的做用域
package com.baobaotao.web; … import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.SessionAttributes; @Controller @RequestMapping("/bbtForum.do") @SessionAttributes("currUser") //①將ModelMap中屬性名爲currUser的屬性 //放到Session屬性列表中,以便這個屬性能夠跨請求訪問 public class BbtForumController { … @RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(@RequestParam("id")int topicId, User user, ModelMap model) { bbtForumService.getBoardTopics(topicId); System.out.println("topicId:" + topicId); System.out.println("user:" + user); model.addAttribute("currUser",user); //②向ModelMap中添加一個屬性 return "listTopic"; } }
咱們在 ② 處添加了一個 ModelMap 屬性,其屬性名爲 currUser,而 ① 處經過 @SessionAttributes 註解將 ModelMap 中名爲 currUser 的屬性放置到 Session 中,因此咱們不但能夠在 listBoardTopic() 請求所對應的 JSP 視圖頁面中經過 request.getAttribute(「currUser」) 和 session.getAttribute(「currUser」) 獲取 user 對象,還能夠在下一個請求所對應的 JSP 視圖頁面中經過 session.getAttribute(「currUser」) 或ModelMap#get(「currUser」) 訪問到這個屬性。
這裏咱們僅將一個 ModelMap 的屬性放入 Session 中,其實 @SessionAttributes 容許指定多個屬性。你能夠經過字符串數組的方式指定多個屬性,如 @SessionAttributes({「attr1」,」attr2」})。此外,@SessionAttributes 還能夠經過屬性類型指定要 session 化的 ModelMap 屬性,如 @SessionAttributes(types = User.class),固然也能夠指定多個類,如 @SessionAttributes(types = {User.class,Dept.class}),還能夠聯合使用屬性名和屬性類型指定:@SessionAttributes(types = {User.class,Dept.class},value={「attr1」,」attr2」})。
上面講述瞭如何往ModelMap中放置屬性以及如何使ModelMap中的屬性擁有Session域的做用範圍。除了在JSP視圖頁面中經過傳統的方法訪問ModelMap中的屬性外,讀者朋友可能會問:是否能夠將ModelMap中的屬性綁定到請求處理方法的入參中呢?答案是確定的。Spring爲此提供了一個@ModelAttribute的註解,下面是使用@ModelAttribute註解的例子:
1三、使模型對象的特定屬性具備 Session 範圍的做用域
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.ModelAttribute; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/bbtForum.do") @SessionAttributes("currUser") //①讓ModelMap的currUser屬性擁有session級做用域 public class BbtForumController { @Autowired private BbtForumService bbtForumService; @RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(@RequestParam("id")int topicId, User user, ModelMap model) { bbtForumService.getBoardTopics(topicId); System.out.println("topicId:" + topicId); System.out.println("user:" + user); model.addAttribute("currUser",user); //②向ModelMap中添加一個屬性 return "listTopic"; } @RequestMapping(params = "method=listAllBoard") //③將ModelMap中的 public String listAllBoard(@ModelAttribute("currUser") User user) { //currUser屬性綁定到user入參中。 bbtForumService.getAllBoard(); System.out.println("user:"+user); return "listBoard"; } }
在 ② 處,咱們向 ModelMap 中添加一個名爲 currUser 的屬性,而 ① 外的註解使這個 currUser 屬性擁有了 session 級的做用域。因此,咱們能夠在 ③ 處經過 @ModelAttribute 註解將 ModelMap 中的 currUser 屬性綁定以請求處理方法的 user 入參中。
因此當咱們先調用如下 URL 請求:
http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12
以執行listBoardTopic()請求處理方法,而後再訪問如下URL: http://localhost/sample/bbtForum.do?method=listAllBoard
你將能夠看到 listAllBoard() 的 user 入參已經成功綁定到 listBoardTopic() 中註冊的 session 級的 currUser 屬性上了。
方法入參
咱們知道標註了 @RequestMapping 註解的 Controller 方法就成爲了請求處理方法,Spring MVC 容許極其靈活的請求處理方法簽名方式。對於方法入參來講,它容許多種類型的入參,經過下表進行說明:
請求處理方法入參的可選類型 | 說明 |
---|---|
Java 基本數據類型和 String | 默認狀況下將按名稱匹配的方式綁定到 URL 參數上,能夠經過 @RequestParam 註解改變默認的綁定規則 |
request/response/session | 既能夠是 Servlet API 的也能夠是 Portlet API 對應的對象,Spring 會將它們綁定到 Servlet 和 Portlet 容器的相應對象上 |
org.springframework.web.context.request.WebRequest | 內部包含了 request 對象 |
java.util.Locale | 綁定到 request 對應的 Locale 對象上 |
java.io.InputStream/java.io.Reader | 能夠藉此訪問 request 的內容 |
java.io.OutputStream / java.io.Writer | 能夠藉此操做 response 的內容 |
任何標註了 @RequestParam 註解的入參 | 被標註 @RequestParam 註解的入參將綁定到特定的 request 參數上。 |
java.util.Map / org.springframework.ui.ModelMap | 它綁定 Spring MVC 框架中每一個請求所建立的潛在的模型對象,它們能夠被 Web 視圖對象訪問(如 JSP) |
命令/表單對象(注:通常稱綁定使用 HTTP GET 發送的 URL 參數的對象爲命令對象,而稱綁定使用 HTTP POST 發送的 URL 參數的對象爲表單對象) | 它們的屬性將以名稱匹配的規則綁定到 URL 參數上,同時完成類型的轉換。而類型轉換的規則能夠經過 @InitBinder 註解或經過 HandlerAdapter 的配置進行調整 |
org.springframework.validation.Errors / org.springframework.validation.BindingResult | 爲屬性列表中的命令/表單對象的校驗結果,注意檢驗結果參數必須緊跟在命令/表單對象的後面 |
rg.springframework.web.bind.support.SessionStatus | 能夠經過該類型 status 對象顯式結束表單的處理,這至關於觸發 session 清除其中的經過 @SessionAttributes 定義的屬性 |
Spring MVC 框架的易用之處在於,你能夠按任意順序定義請求處理方法的入參(除了 Errors 和 BindingResult 必須緊跟在命令對象/表單參數後面之外),Spring MVC 會根據反射機制自動將對應的對象經過入參傳遞給請求處理方法。這種機制讓開發者徹底能夠不依賴 Servlet API 開發控制層的程序,當請求處理方法須要特定的對象時,僅僅須要在參數列表中聲明入參便可,不須要考慮如何獲取這些對象,Spring MVC 框架就象一個大管家同樣「任勞任怨」地爲咱們準備好了所需的一切。下面演示一下使用 SessionStatus 的例子:
1四、使用 SessionStatus 控制 Session 級別的模型屬性
@RequestMapping(method = RequestMethod.POST) public String processSubmit(@ModelAttribute Owner owner, BindingResult result, SessionStatus status) {//<——① new OwnerValidator().validate(owner, result); if (result.hasErrors()) { return "ownerForm"; } else { this.clinic.storeOwner(owner); status.setComplete();//<——② return "redirect:owner.do?ownerId=" + owner.getId(); } }
processSubmit() 方法中的 owner 表單對象將綁定到 ModelMap 的「owner」屬性中,result 參數用於存放檢驗 owner 結果的對象,而 status 用於控制表單處理的狀態。在 ② 處,咱們經過調用 status.setComplete() 方法,該 Controller 全部放在 session 級別的模型屬性數據將從 session 中清空。
方法返回參數
在低版本的 Spring MVC 中,請求處理方法的返回值類型都必須是 ModelAndView。而在 Spring 2.5 中,你擁有多種靈活的選擇。經過下表進行說明:
請求處理方法入參的可選類型 | 說明 | |
---|---|---|
void | 此時邏輯視圖名由請求處理方法對應的 URL 肯定,如如下的方法:
對應的邏輯視圖名爲「welcome」 |
|
String | 此時邏輯視圖名爲返回的字符,如如下的方法:
對應的邏輯視圖名爲「ownerForm」 |
|
org.springframework.ui.ModelMap | 和返回類型爲 void 同樣,邏輯視圖名取決於對應請求的 URL,以下面的例子:
對應的邏輯視圖名爲「vets」,返回的 ModelMap 將被做爲請求對應的模型對象,能夠在 JSP 視圖頁面中訪問到。 |
|
ModelAndView | 固然還能夠是傳統的 ModelAndView。 |
應該說使用 String 做爲請求處理方法的返回值類型是比較通用的方法,這樣返回的邏輯視圖名不會和請求 URL 綁定,具備很大的靈活性,而模型數據又能夠經過 ModelMap 控制。固然直接使用傳統的 ModelAndView 也不失爲一個好的選擇。
Spring MVC 有一套經常使用的屬性編輯器,這包括基本數據類型及其包裹類的屬性編輯器、String 屬性編輯器、JavaBean 的屬性編輯器等。但有時咱們還須要向 Spring MVC 框架註冊一些自定義的屬性編輯器,如特定時間格式的屬性編輯器就是其中一例。
Spring MVC 容許向整個 Spring 框架註冊屬性編輯器,它們對全部 Controller 都有影響。固然 Spring MVC 也容許僅向某個 Controller 註冊屬性編輯器,對其它的 Controller 沒有影響。前者能夠經過 AnnotationMethodHandlerAdapter 的配置作到,然後者則能夠經過 @InitBinder 註解實現。
下面先看向整個 Spring MVC 框架註冊的自定義編輯器:
1五、註冊框架級的自定義屬性編輯器
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="webBindingInitializer"> <bean class="com.baobaotao.web.MyBindingInitializer"/> </property> </bean>
MyBindingInitializer 實現了 WebBindingInitializer 接口,在接口方法中經過 binder 註冊多個自定義的屬性編輯器,其代碼以下所示:
1六、自定義屬性編輯器
package org.springframework.samples.petclinic.web; import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.samples.petclinic.Clinic; import org.springframework.samples.petclinic.PetType; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.context.request.WebRequest; public class MyBindingInitializer implements WebBindingInitializer { public void initBinder(WebDataBinder binder, WebRequest request) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); binder.registerCustomEditor(String.class, new StringTrimmerEditor(false)); } }
若是但願某個屬性編輯器僅做用於特定的 Controller,能夠在 Controller 中定義一個標註 @InitBinder 註解的方法,能夠在該方法中向 Controller 了註冊若干個屬性編輯器,來看下面的代碼:
1七、註冊 Controller 級的自定義屬性編輯器
@Controller public class MyFormController { @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } … }
注意被標註 @InitBinder 註解的方法必須擁有一個 WebDataBinder 類型的入參,以便 Spring MVC 框架將註冊屬性編輯器的 WebDataBinder 對象傳遞進來。
在編寫 Controller 時,經常須要在真正進入請求處理方法前準備一些數據,以便請求處理或視圖渲染時使用。在傳統的 SimpleFormController 裏,是經過複寫其 referenceData() 方法來準備引用數據的。在 Spring 2.5 時,能夠將任何一個擁有返回值的方法標註上 @ModelAttribute,使其返回值將會進入到模型對象的屬性列表中。來看下面的例子:
1八、定義爲處理請求準備數據的方法
package com.baobaotao.web; import com.baobaotao.service.BbtForumService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttributes; import java.util.ArrayList; import java.util.List; import java.util.Set; @Controller @RequestMapping("/bbtForum.do") public class BbtForumController { @Autowired private BbtForumService bbtForumService; @ModelAttribute("items")//<——①向模型對象中添加一個名爲items的屬性 public List<String> populateItems() { List<String> lists = new ArrayList<String>(); lists.add("item1"); lists.add("item2"); return lists; } @RequestMapping(params = "method=listAllBoard") public String listAllBoard(@ModelAttribute("currUser")User user, ModelMap model) { bbtForumService.getAllBoard(); //<——②在此訪問模型中的items屬性 System.out.println("model.items:" + ((List<String>)model.get("items")).size()); return "listBoard"; } }
在 ① 處,經過使用 @ModelAttribute 註解,populateItem() 方法將在任何請求處理方法執行前調用,Spring MVC 會將該方法返回值以「items」爲名放入到隱含的模型對象屬性列表中。
因此在 ② 處,咱們就能夠經過 ModelMap 入參訪問到 items 屬性,當執行 listAllBoard() 請求處理方法時,② 處將在控制檯打印出「model.items:2」的信息。固然咱們也能夠在請求的視圖中訪問到模型對象中的 items 屬性。
Spring 2.5 對 Spring MVC 進行了很大加強,如今咱們幾乎徹底可使用基於註解的 Spring MVC 徹底替換掉原來基於接口 Spring MVC 程序。基於註解的 Spring MVC 比之於基於接口的 Spring MVC 擁有如下幾點好處:
可是基於註解的 Spring MVC 並不完美,還存在優化的空間,由於在某些配置上它比基於 XML 的配置更繁瑣。好比對於處理多個請求的 Controller 來講,假設咱們使用一個 URL 參數指定調用的處理方法(如 xxx.do?method=listBoardTopic),當使用註解時,每一個請求處理方法都必須使用 @RequestMapping() 註解指定對應的 URL 參數(如 @RequestMapping(params = "method=listBoardTopic")),而在 XML 配置中咱們僅須要配置一個 ParameterMethodNameResolver 就能夠了。