來自地址: https://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/
?
?
使用過低版本 Spring MVC 的讀者都知道:當創建一個 Controller 時,我們需要直接或間接地實現 org.springframework.web.servlet.mvc.Controller 接口。一般情況下,我們是通過繼承 SimpleFormController 或 MultiActionController 來定義自己的 Controller 的。在定義 Controller 后,一個重要的事件是在 Spring MVC 的配置文件中通過 HandlerMapping 定義請求和控制器的映射關系,以便將兩者關聯起來。
來看一下基于注解的 Controller 是如何定義做到這一點的,下面是使用注解的 BbtForumController:
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 的注解!
在 ① 處使用了兩個注解,分別是 @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 等)。
為了讓基于注解的 Spring MVC 真正工作起來,需要在 Spring MVC 對應的 xxx-servlet.xml 配置文件中做一些手腳。在此之前,還是先來看一下 web.xml 的配置吧:
清單 2. web.xml:啟用 Spring 容器和 Spring MVC 框架
<?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 的配置內容如下所示:
<?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 注解后,這個功能更加容易實現了。請看下面的代碼:
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 注解很容易實現這個常用的需求。來看下面的代碼:
清單 4. 一個 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 請求,如下所示:
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 請求參數到請求處理方法參數類型的轉換。下面給出幾個例子:
- listBoardTopic(int topicId):和 topicId URL 請求參數綁定;
- listBoardTopic(int topicId,String boardName):分別和 topicId、boardName URL 請求參數綁定;
?
特別的,如果入參是基本數據類型(如 int、long、float 等),URL 請求參數中一定要有對應的參數,否則將拋出 TypeMismatchException 異常,提示無法將 null 轉換為基本數據類型。
另外,請求處理方法的入參也可以一個 JavaBean,如下面的 User 對象就可以作為一個入參:
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() 請求處理方法的入參:
@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"; } |
這時,如果我們使用以下的 URL 請求:http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom
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 參數綁定。
如果我們想改變這種默認的按名稱匹配的策略,比如讓 listBoardTopic(int topicId,User user) 中的 topicId 綁定到 id 這個 URL 參數,那么可以通過對入參使用 @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 會將本次請求模型對象引用通過該入參傳遞進來,這樣就可以在請求處理方法內部訪問模型對象了。來看下面的例子:
清單 9. 使用 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 注解來實現的。請看下面的代碼:
清單 10. 使模型對象的特定屬性具有 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注解的例子:
清單 11. 使模型對象的特定屬性具有 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 的例子:
清單 12. 使用 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 框架注冊的自定義編輯器:
>bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"< >property name="webBindingInitializer"< >bean class="com.baobaotao.web.MyBindingInitializer"/< >/property< >/bean< |
MyBindingInitializer 實現了 WebBindingInitializer 接口,在接口方法中通過 binder 注冊多個自定義的屬性編輯器,其代碼如下所示:
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 了注冊若干個屬性編輯器,來看下面的代碼:
清單 15. 注冊 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,使其返回值將會進入到模型對象的屬性列表中。來看下面的例子:
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 屬性。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
