Spring 2.5 注解驅(qū)動(dòng)的 Spring MVC
Posted on 2008-06-20 16:13 Astro.Qi 閱讀(365) 評(píng)論(0) 編輯 收藏 所屬分類: Spring基于注解的配置有越來(lái)越流行的趨勢(shì),Spring 2.5 順應(yīng)這種趨勢(shì),為 Spring MVC 提供了完全基于注解的配置。本文將介紹 Spring 2.5 新增的 Sping MVC 注解功能,講述如何使用注解配置替換傳統(tǒng)的基于 XML 的 Spring MVC 配置。
繼 Spring 2.0 對(duì) Spring MVC 進(jìn)行重大升級(jí)后,Spring 2.5 又為 Spring MVC 引入了注解驅(qū)動(dòng)功能。現(xiàn)在你無(wú)須讓 Controller 繼承任何接口,無(wú)需在 XML 配置文件中定義請(qǐng)求和 Controller 的映射關(guān)系,僅僅使用注解就可以讓一個(gè) POJO 具有 Controller 的絕大部分功能 —— Spring MVC 框架的易用性得到了進(jìn)一步的增強(qiáng).在框架靈活性、易用性和擴(kuò)展性上,Spring MVC 已經(jīng)全面超越了其它的 MVC 框架,伴隨著 Spring 一路高唱猛進(jìn),可以預(yù)見(jiàn) Spring MVC 在 MVC 市場(chǎng)上的吸引力將越來(lái)越不可抗拒。
本文將介紹 Spring 2.5 新增的 Sping MVC 注解功能,講述如何使用注解配置替換傳統(tǒng)的基于 XML 的 Spring MVC 配置。
![]() ![]() |
![]()
|
一個(gè)簡(jiǎn)單的基于注解的 Controller
使用過(guò)低版本 Spring MVC 的讀者都知道:當(dāng)創(chuàng)建一個(gè) Controller 時(shí),我們需要直接或間接地實(shí)現(xiàn) org.springframework.web.servlet.mvc.Controller 接口。一般情況下,我們是通過(guò)繼承 SimpleFormController 或 MultiActionController 來(lái)定義自己的 Controller 的。在定義 Controller 后,一個(gè)重要的事件是在 Spring MVC 的配置文件中通過(guò) HandlerMapping 定義請(qǐng)求和控制器的映射關(guān)系,以便將兩者關(guān)聯(lián)起來(lái)。
來(lái)看一下基于注解的 Controller 是如何定義做到這一點(diǎn)的,下面是使用注解的 BbtForumController:
清單 1. BbtForumController.java
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 和一般的類并沒(méi)有區(qū)別,它沒(méi)有實(shí)現(xiàn)任何特殊的接口,因而是一個(gè)地道的 POJO。讓這個(gè) POJO 與眾不同的魔棒就是 Spring MVC 的注解!
在 ① 處使用了兩個(gè)注解,分別是 @Controller 和 @RequestMapping。在“使用 Spring 2.5 基于注解驅(qū)動(dòng)的 IoC”這篇文章里,筆者曾經(jīng)指出過(guò) @Controller、@Service 以及 @Repository 和 @Component 注解的作用是等價(jià)的:將一個(gè)類成為 Spring 容器的 Bean。由于 Spring MVC 的 Controller 必須事先是一個(gè) Bean,所以 @Controller 注解是不可缺少的。
真正讓 BbtForumController 具備 Spring MVC Controller 功能的是 @RequestMapping 這個(gè)注解。@RequestMapping 可以標(biāo)注在類定義處,將 Controller 和特定請(qǐng)求關(guān)聯(lián)起來(lái);還可以標(biāo)注在方法簽名處,以便進(jìn)一步對(duì)請(qǐng)求進(jìn)行分流。在 ① 處,我們讓 BbtForumController 關(guān)聯(lián)“/forum.do”的請(qǐng)求,而 ② 處,我們具體地指定 listAllBoard() 方法來(lái)處理請(qǐng)求。所以在類聲明處標(biāo)注的 @RequestMapping 相當(dāng)于讓 POJO 實(shí)現(xiàn)了 Controller 接口,而在方法定義處的 @RequestMapping 相當(dāng)于讓 POJO 擴(kuò)展 Spring 預(yù)定義的 Controller(如 SimpleFormController 等)。
為了讓基于注解的 Spring MVC 真正工作起來(lái),需要在 Spring MVC 對(duì)應(yīng)的 xxx-servlet.xml 配置文件中做一些手腳。在此之前,還是先來(lái)看一下 web.xml 的配置吧:
清單 2. web.xml:?jiǎn)⒂?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 服務(wù)層的配置文件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring 容器啟動(dòng)監(jiān)聽(tīng)器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- Spring MVC 的Servlet,它將加載WEB-INF/annomvc-servlet.xml 的 配置文件,以啟動(dòng)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 中定義了一個(gè)名為 annomvc 的 Spring MVC 模塊,按照 Spring MVC 的契約,需要在 WEB-INF/annomvc-servlet.xml 配置文件中定義 Spring MVC 模塊的具體配置。annomvc-servlet.xml 的配置內(nèi)容如下所示:
<?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"> <!-- ①:對(duì)web包中的所有類進(jìn)行掃描,以完成Bean創(chuàng)建和自動(dòng)依賴注入的功能 --> <context:component-scan base-package="com.baobaotao.web"/> <!-- ②:?jiǎn)?dòng)Spring MVC的注解功能,完成請(qǐng)求和注解POJO的映射 --> <bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"/> <!-- ③:對(duì)模型視圖名稱的解析,即在模型視圖名稱添加前后綴 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> </beans> |
因?yàn)?Spring 所有功能都在 Bean 的基礎(chǔ)上演化而來(lái),所以必須事先將 Controller 變成 Bean,這是通過(guò)在類中標(biāo)注 @Controller 并在 annomvc-servlet.xml 中啟用組件掃描機(jī)制來(lái)完成的,如 ① 所示。
在 ② 處,配置了一個(gè) AnnotationMethodHandlerAdapter,它負(fù)責(zé)根據(jù) Bean 中的 Spring MVC 注解對(duì) Bean 進(jìn)行加工處理,使這些 Bean 變成控制器并映射特定的 URL 請(qǐng)求。
而 ③ 處的工作是定義模型視圖名稱的解析規(guī)則,這里我們使用了 Spring 2.5 的特殊命名空間,即 p 命名空間,它將原先需要通過(guò) <property> 元素配置的內(nèi)容轉(zhuǎn)化為 <bean> 屬性配置,在一定程度上簡(jiǎn)化了 <bean> 的配置。
啟動(dòng) Tomcat,發(fā)送 http://localhost/forum.do URL 請(qǐng)求,BbtForumController 的 listAllBoard() 方法將響應(yīng)這個(gè)請(qǐng)求,并轉(zhuǎn)向 WEB-INF/jsp/listBoard.jsp 的視圖頁(yè)面。
![]() ![]() |
![]()
|
讓一個(gè) Controller 處理多個(gè) URL 請(qǐng)求
在低版本的 Spring MVC 中,我們可以通過(guò)繼承 MultiActionController 讓一個(gè) Controller 處理多個(gè) URL 請(qǐng)求。使用 @RequestMapping 注解后,這個(gè)功能更加容易實(shí)現(xiàn)了。請(qǐng)看下面的代碼:
清單 3. 每個(gè)請(qǐng)求處理參數(shù)對(duì)應(yīng)一個(gè) 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() 方法標(biāo)注了 @RequestMapping 注解,分別指定這兩個(gè)方法處理的 URL 請(qǐng)求,這相當(dāng)于將 BbtForumController 改造為 MultiActionController。這樣 /listAllBoard.do 的 URL 請(qǐng)求將由 listAllBoard() 負(fù)責(zé)處理,而 /listBoardTopic.do?topicId=1 的 URL 請(qǐng)求則由 listBoardTopic() 方法處理。
對(duì)于處理多個(gè) URL 請(qǐng)求的 Controller 來(lái)說(shuō),我們傾向于通過(guò)一個(gè) URL 參數(shù)指定 Controller 處理方法的名稱(如 method=listAllBoard),而非直接通過(guò)不同的 URL 指定 Controller 的處理方法。使用 @RequestMapping 注解很容易實(shí)現(xiàn)這個(gè)常用的需求。來(lái)看下面的代碼:
清單 4. 一個(gè) Controller 對(duì)應(yīng)一個(gè) URL,由請(qǐng)求參數(shù)決定請(qǐng)求處理方法
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") // <—— ① 指定控制器對(duì)應(yīng)URL請(qǐng)求 public class BbtForumController { @Autowired private BbtForumService bbtForumService; // <—— ② 如果URL請(qǐng)求中包括"method=listAllBoard"的參數(shù),由本方法進(jìn)行處理 @RequestMapping(params = "method=listAllBoard") public String listAllBoard() { bbtForumService.getAllBoard(); System.out.println("call listAllBoard method."); return "listBoard"; } // <—— ③ 如果URL請(qǐng)求中包括"method=listBoardTopic"的參數(shù),由本方法進(jìn)行處理 @RequestMapping(params = "method=listBoardTopic") public String listBoardTopic(int topicId) { bbtForumService.getBoardTopics(topicId); System.out.println("call listBoardTopic method."); return "listTopic"; } } |
在類定義處標(biāo)注的 @RequestMapping 讓 BbtForumController 處理所有包含 /bbtForum.do 的 URL 請(qǐng)求,而 BbtForumController 中的請(qǐng)求處理方法對(duì) URL 請(qǐng)求的分流規(guī)則在 ② 和 ③ 處定義分流規(guī)則按照 URL 的 method 請(qǐng)求參數(shù)確定。所以分別在類定義處和方法定義處使用 @RequestMapping 注解,就可以很容易通過(guò) URL 參數(shù)指定 Controller 的處理方法了。
@RequestMapping 注解中除了 params 屬性外,還有一個(gè)常用的屬性是 method,它可以讓 Controller 方法處理特定 HTTP 請(qǐng)求方式的請(qǐng)求,如讓一個(gè)方法處理 HTTP GET 請(qǐng)求,而另一個(gè)方法處理 HTTP POST 請(qǐng)求,如下所示:
清單 4. 讓請(qǐng)求處理方法處理特定的 HTTP 請(qǐng)求方法
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"; } } |
這樣只有當(dāng) /bbtForum.do?method=createTopic 請(qǐng)求以 HTTP POST 方式提交時(shí),createTopic() 方法才會(huì)進(jìn)行處理。
![]() ![]() |
![]()
|
Controller 的方法標(biāo)注了 @RequestMapping 注解后,它就能處理特定的 URL 請(qǐng)求。我們不禁要問(wèn):請(qǐng)求處理方法入?yún)⑹侨绾谓壎?URL 參數(shù)的呢?在回答這個(gè)問(wèn)題之前先來(lái)看下面的代碼:
@RequestMapping(params = "method=listBoardTopic") //<—— ① topicId入?yún)⑹侨绾谓壎║RL請(qǐng)求參數(shù)的? public String listBoardTopic(int topicId) { bbtForumService.getBoardTopics(topicId); System.out.println("call listBoardTopic method."); return "listTopic"; } |
當(dāng)我們發(fā)送 http://localhost//bbtForum.do?method=listBoardTopic&topicId=10 的 URL 請(qǐng)求時(shí),Spring 不但讓 listBoardTopic() 方法處理這個(gè)請(qǐng)求,而且還將 topicId 請(qǐng)求參數(shù)在類型轉(zhuǎn)換后綁定到 listBoardTopic() 方法的 topicId 入?yún)⑸稀6?listBoardTopic() 方法的返回類型是 String,它將被解析為邏輯視圖的名稱。也就是說(shuō) Spring 在如何給處理方法入?yún)⒆詣?dòng)賦值以及如何將處理方法返回值轉(zhuǎn)化為 ModelAndView 中的過(guò)程中存在一套潛在的規(guī)則,不熟悉這個(gè)規(guī)則就不可能很好地開(kāi)發(fā)基于注解的請(qǐng)求處理方法,因此了解這個(gè)潛在規(guī)則無(wú)疑成為理解 Spring MVC 框架基于注解功能的核心問(wèn)題。
我們不妨從最常見(jiàn)的開(kāi)始說(shuō)起:請(qǐng)求處理方法入?yún)⒌念愋涂梢允?Java 基本數(shù)據(jù)類型或 String 類型,這時(shí)方法入?yún)磪?shù)名匹配的原則綁定到 URL 請(qǐng)求參數(shù),同時(shí)還自動(dòng)完成 String 類型的 URL 請(qǐng)求參數(shù)到請(qǐng)求處理方法參數(shù)類型的轉(zhuǎn)換。下面給出幾個(gè)例子:
- listBoardTopic(int topicId):和 topicId URL 請(qǐng)求參數(shù)綁定;
- listBoardTopic(int topicId,String boardName):分別和 topicId、boardName URL 請(qǐng)求參數(shù)綁定;
特別的,如果入?yún)⑹腔緮?shù)據(jù)類型(如 int、long、float 等),URL 請(qǐng)求參數(shù)中一定要有對(duì)應(yīng)的參數(shù),否則將拋出 TypeMismatchException 異常,提示無(wú)法將 null 轉(zhuǎn)換為基本數(shù)據(jù)類型。
另外,請(qǐng)求處理方法的入?yún)⒁部梢砸粋€(gè) JavaBean,如下面的 User 對(duì)象就可以作為一個(gè)入?yún)ⅲ?/p>
清單 6. User.java:一個(gè) 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() 請(qǐng)求處理方法的入?yún)ⅲ?/p>
清單 7. 使用 JavaBean 作為請(qǐng)求處理方法的入?yún)?/strong>
@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"; } |
這時(shí),如果我們使用以下的 URL 請(qǐng)求:http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom
topicId URL 參數(shù)將綁定到 topicId 入?yún)⑸希?userId 和 userName URL 參數(shù)將綁定到 user 對(duì)象的 userId 和 userName 屬性中。和 URL 請(qǐng)求中不允許沒(méi)有 topicId 參數(shù)不同,雖然 User 的 userId 屬性的類型是基本數(shù)據(jù)類型,但如果 URL 中不存在 userId 參數(shù),Spring 也不會(huì)報(bào)錯(cuò),此時(shí) user.userId 值為 0。如果 User 對(duì)象擁有一個(gè) dept.deptId 的級(jí)聯(lián)屬性,那么它將和 dept.deptId URL 參數(shù)綁定。
如果我們想改變這種默認(rèn)的按名稱匹配的策略,比如讓 listBoardTopic(int topicId,User user) 中的 topicId 綁定到 id 這個(gè) URL 參數(shù),那么可以通過(guò)對(duì)入?yún)⑹褂?@RequestParam 注解來(lái)達(dá)到目的:
清單 8. 通過(guò) @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"; } … } |
這里,對(duì) listBoardTopic() 請(qǐng)求處理方法的 topicId 入?yún)?biāo)注了 @RequestParam("id") 注解,所以它將和 id 的 URL 參數(shù)綁定。
Spring 2.0 定義了一個(gè) org.springframework.ui.ModelMap 類,它作為通用的模型數(shù)據(jù)承載對(duì)象,傳遞數(shù)據(jù)供視圖所用。我們可以在請(qǐng)求處理方法中聲明一個(gè) ModelMap 類型的入?yún)ⅲ琒pring 會(huì)將本次請(qǐng)求模型對(duì)象引用通過(guò)該入?yún)鬟f進(jìn)來(lái),這樣就可以在請(qǐng)求處理方法內(nèi)部訪問(wèn)模型對(duì)象了。來(lái)看下面的例子:
清單 9. 使用 ModelMap 訪問(wèn)請(qǐng)示對(duì)應(yīng)的隱含模型對(duì)象
@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對(duì)象以currUser為鍵放入到model中 model.addAttribute("currUser",user); return "listTopic"; } |
對(duì)于當(dāng)次請(qǐng)求所對(duì)應(yīng)的模型對(duì)象來(lái)說(shuō),其所有屬性都將存放到 request 的屬性列表中。象上面的例子,ModelMap 中的 currUser 屬性將放到 request 的屬性列表中,所以可以在 JSP 視圖頁(yè)面中通過(guò) request.getAttribute(“currUser”) 或者通過(guò) ${currUser} EL 表達(dá)式訪問(wèn)模型對(duì)象中的 user 對(duì)象。從這個(gè)角度上看, ModelMap 相當(dāng)于是一個(gè)向 request 屬性列表中添加對(duì)象的一條管道,借由 ModelMap 對(duì)象的支持,我們可以在一個(gè)不依賴 Servlet API 的 Controller 中向 request 中添加屬性。
在默認(rèn)情況下,ModelMap 中的屬性作用域是 request 級(jí)別是,也就是說(shuō),當(dāng)本次請(qǐng)求結(jié)束后,ModelMap 中的屬性將銷毀。如果希望在多個(gè)請(qǐng)求中共享 ModelMap 中的屬性,必須將其屬性轉(zhuǎn)存到 session 中,這樣 ModelMap 的屬性才可以被跨請(qǐng)求訪問(wèn)。
Spring 允許我們有選擇地指定 ModelMap 中的哪些屬性需要轉(zhuǎn)存到 session 中,以便下一個(gè)請(qǐng)求屬對(duì)應(yīng)的 ModelMap 的屬性列表中還能訪問(wèn)到這些屬性。這一功能是通過(guò)類定義處標(biāo)注 @SessionAttributes 注解來(lái)實(shí)現(xiàn)的。請(qǐng)看下面的代碼:
清單 10. 使模型對(duì)象的特定屬性具有 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屬性列表中,以便這個(gè)屬性可以跨請(qǐng)求訪問(wèn) 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中添加一個(gè)屬性 return "listTopic"; } } |
我們?cè)?② 處添加了一個(gè) ModelMap 屬性,其屬性名為 currUser,而 ① 處通過(guò) @SessionAttributes 注解將 ModelMap 中名為 currUser 的屬性放置到 Session 中,所以我們不但可以在 listBoardTopic() 請(qǐng)求所對(duì)應(yīng)的 JSP 視圖頁(yè)面中通過(guò) request.getAttribute(“currUser”) 和 session.getAttribute(“currUser”) 獲取 user 對(duì)象,還可以在下一個(gè)請(qǐng)求所對(duì)應(yīng)的 JSP 視圖頁(yè)面中通過(guò) session.getAttribute(“currUser”) 或 ModelMap#get(“currUser”) 訪問(wèn)到這個(gè)屬性。
這里我們僅將一個(gè) ModelMap 的屬性放入 Session 中,其實(shí) @SessionAttributes 允許指定多個(gè)屬性。你可以通過(guò)字符串?dāng)?shù)組的方式指定多個(gè)屬性,如 @SessionAttributes({“attr1”,”attr2”})。此外,@SessionAttributes 還可以通過(guò)屬性類型指定要 session 化的 ModelMap 屬性,如 @SessionAttributes(types = User.class),當(dāng)然也可以指定多個(gè)類,如 @SessionAttributes(types = {User.class,Dept.class}),還可以聯(lián)合使用屬性名和屬性類型指定:@SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})。
上面講述了如何往ModelMap中放置屬性以及如何使ModelMap中的屬性擁有Session域的作用范圍。除了在JSP視圖頁(yè)面中通過(guò)傳統(tǒng)的方法訪問(wèn)ModelMap中的屬性外,讀者朋友可能會(huì)問(wèn):是否可以將ModelMap中的屬性綁定到請(qǐng)求處理方法的入?yún)⒅心兀看鸢甘强隙ǖ摹pring為此提供了一個(gè)@ModelAttribute的注解,下面是使用@ModelAttribute注解的例子:
清單 11. 使模型對(duì)象的特定屬性具有 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級(jí)作用域 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中添加一個(gè)屬性 return "listTopic"; } @RequestMapping(params = "method=listAllBoard") //③將ModelMap中的 public String listAllBoard(@ModelAttribute("currUser") User user) { //currUser屬性綁定到user入?yún)⒅小? bbtForumService.getAllBoard(); System.out.println("user:"+user); return "listBoard"; } } |
在 ② 處,我們向 ModelMap 中添加一個(gè)名為 currUser 的屬性,而 ① 外的注解使這個(gè) currUser 屬性擁有了 session 級(jí)的作用域。所以,我們可以在 ③ 處通過(guò) @ModelAttribute 注解將 ModelMap 中的 currUser 屬性綁定以請(qǐng)求處理方法的 user 入?yún)⒅小?/p>
所以當(dāng)我們先調(diào)用以下 URL 請(qǐng)求: http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12
以執(zhí)行l(wèi)istBoardTopic()請(qǐng)求處理方法,然后再訪問(wèn)以下URL: http://localhost/sample/bbtForum.do?method=listAllBoard
你將可以看到 listAllBoard() 的 user 入?yún)⒁呀?jīng)成功綁定到 listBoardTopic() 中注冊(cè)的 session 級(jí)的 currUser 屬性上了。
![]() ![]() |
![]()
|
我們知道標(biāo)注了 @RequestMapping 注解的 Controller 方法就成為了請(qǐng)求處理方法,Spring MVC 允許極其靈活的請(qǐng)求處理方法簽名方式。對(duì)于方法入?yún)?lái)說(shuō),它允許多種類型的入?yún)ⅲㄟ^(guò)下表進(jìn)行說(shuō)明:
請(qǐng)求處理方法入?yún)⒌目蛇x類型 | 說(shuō)明 |
---|---|
Java 基本數(shù)據(jù)類型和 String | 默認(rèn)情況下將按名稱匹配的方式綁定到 URL 參數(shù)上,可以通過(guò) @RequestParam 注解改變默認(rèn)的綁定規(guī)則 |
request/response/session | 既可以是 Servlet API 的也可以是 Portlet API 對(duì)應(yīng)的對(duì)象,Spring 會(huì)將它們綁定到 Servlet 和 Portlet 容器的相應(yīng)對(duì)象上 |
org.springframework.web.context.request.WebRequest | 內(nèi)部包含了 request 對(duì)象 |
java.util.Locale | 綁定到 request 對(duì)應(yīng)的 Locale 對(duì)象上 |
java.io.InputStream/java.io.Reader | 可以借此訪問(wèn) request 的內(nèi)容 |
java.io.OutputStream / java.io.Writer | 可以借此操作 response 的內(nèi)容 |
任何標(biāo)注了 @RequestParam 注解的入?yún)?/td> | 被標(biāo)注 @RequestParam 注解的入?yún)⒔壎ǖ教囟ǖ?request 參數(shù)上。 |
java.util.Map / org.springframework.ui.ModelMap | 它綁定 Spring MVC 框架中每個(gè)請(qǐng)求所創(chuàng)建的潛在的模型對(duì)象,它們可以被 Web 視圖對(duì)象訪問(wèn)(如 JSP) |
命令/表單對(duì)象(注:一般稱綁定使用 HTTP GET 發(fā)送的 URL 參數(shù)的對(duì)象為命令對(duì)象,而稱綁定使用 HTTP POST 發(fā)送的 URL 參數(shù)的對(duì)象為表單對(duì)象) | 它們的屬性將以名稱匹配的規(guī)則綁定到 URL 參數(shù)上,同時(shí)完成類型的轉(zhuǎn)換。而類型轉(zhuǎn)換的規(guī)則可以通過(guò) @InitBinder 注解或通過(guò) HandlerAdapter 的配置進(jìn)行調(diào)整 |
org.springframework.validation.Errors / org.springframework.validation.BindingResult | 為屬性列表中的命令/表單對(duì)象的校驗(yàn)結(jié)果,注意檢驗(yàn)結(jié)果參數(shù)必須緊跟在命令/表單對(duì)象的后面 |
rg.springframework.web.bind.support.SessionStatus | 可以通過(guò)該類型 status 對(duì)象顯式結(jié)束表單的處理,這相當(dāng)于觸發(fā) session 清除其中的通過(guò) @SessionAttributes 定義的屬性 |
Spring MVC 框架的易用之處在于,你可以按任意順序定義請(qǐng)求處理方法的入?yún)ⅲǔ?Errors 和 BindingResult 必須緊跟在命令對(duì)象/表單參數(shù)后面以外),Spring MVC 會(huì)根據(jù)反射機(jī)制自動(dòng)將對(duì)應(yīng)的對(duì)象通過(guò)入?yún)鬟f給請(qǐng)求處理方法。這種機(jī)制讓開(kāi)發(fā)者完全可以不依賴 Servlet API 開(kāi)發(fā)控制層的程序,當(dāng)請(qǐng)求處理方法需要特定的對(duì)象時(shí),僅僅需要在參數(shù)列表中聲明入?yún)⒓纯桑恍枰紤]如何獲取這些對(duì)象,Spring MVC 框架就象一個(gè)大管家一樣“不辭辛苦”地為我們準(zhǔn)備好了所需的一切。下面演示一下使用 SessionStatus 的例子:
清單 12. 使用 SessionStatus 控制 Session 級(jí)別的模型屬性
@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 表單對(duì)象將綁定到 ModelMap 的“owner”屬性中,result 參數(shù)用于存放檢驗(yàn) owner 結(jié)果的對(duì)象,而 status 用于控制表單處理的狀態(tài)。在 ② 處,我們通過(guò)調(diào)用 status.setComplete() 方法,該 Controller 所有放在 session 級(jí)別的模型屬性數(shù)據(jù)將從 session 中清空。
在低版本的 Spring MVC 中,請(qǐng)求處理方法的返回值類型都必須是 ModelAndView。而在 Spring 2.5 中,你擁有多種靈活的選擇。通過(guò)下表進(jìn)行說(shuō)明:
請(qǐng)求處理方法入?yún)⒌目蛇x類型 | 說(shuō)明 | |
---|---|---|
void |
此時(shí)邏輯視圖名由請(qǐng)求處理方法對(duì)應(yīng)的 URL 確定,如以下的方法:
對(duì)應(yīng)的邏輯視圖名為“welcome” |
|
String |
此時(shí)邏輯視圖名為返回的字符,如以下的方法:
對(duì)應(yīng)的邏輯視圖名為“ownerForm” |
|
org.springframework.ui.ModelMap |
和返回類型為 void 一樣,邏輯視圖名取決于對(duì)應(yīng)請(qǐng)求的 URL,如下面的例子:
對(duì)應(yīng)的邏輯視圖名為“vets”,返回的 ModelMap 將被作為請(qǐng)求對(duì)應(yīng)的模型對(duì)象,可以在 JSP 視圖頁(yè)面中訪問(wèn)到。 |
|
ModelAndView | 當(dāng)然還可以是傳統(tǒng)的 ModelAndView。 |
應(yīng)該說(shuō)使用 String 作為請(qǐng)求處理方法的返回值類型是比較通用的方法,這樣返回的邏輯視圖名不會(huì)和請(qǐng)求 URL 綁定,具有很大的靈活性,而模型數(shù)據(jù)又可以通過(guò) ModelMap 控制。當(dāng)然直接使用傳統(tǒng)的 ModelAndView 也不失為一個(gè)好的選擇。
![]() ![]() |
![]()
|
Spring MVC 有一套常用的屬性編輯器,這包括基本數(shù)據(jù)類型及其包裹類的屬性編輯器、String 屬性編輯器、JavaBean 的屬性編輯器等。但有時(shí)我們還需要向 Spring MVC 框架注冊(cè)一些自定義的屬性編輯器,如特定時(shí)間格式的屬性編輯器就是其中一例。
Spring MVC 允許向整個(gè) Spring 框架注冊(cè)屬性編輯器,它們對(duì)所有 Controller 都有影響。當(dāng)然 Spring MVC 也允許僅向某個(gè) Controller 注冊(cè)屬性編輯器,對(duì)其它的 Controller 沒(méi)有影響。前者可以通過(guò) AnnotationMethodHandlerAdapter 的配置做到,而后者則可以通過(guò) @InitBinder 注解實(shí)現(xiàn)。
下面先看向整個(gè) Spring MVC 框架注冊(cè)的自定義編輯器:
清單 13. 注冊(cè)框架級(jí)的自定義屬性編輯器
>bean class="org.springframework.web.servlet.mvc.annotation. AnnotationMethodHandlerAdapter"< >property name="webBindingInitializer"< >bean class="com.baobaotao.web.MyBindingInitializer"/< >/property< >/bean< |
MyBindingInitializer 實(shí)現(xiàn)了 WebBindingInitializer 接口,在接口方法中通過(guò) binder 注冊(cè)多個(gè)自定義的屬性編輯器,其代碼如下所示:
清單 14.自定義屬性編輯器
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)); } } |
如果希望某個(gè)屬性編輯器僅作用于特定的 Controller,可以在 Controller 中定義一個(gè)標(biāo)注 @InitBinder 注解的方法,可以在該方法中向 Controller 了注冊(cè)若干個(gè)屬性編輯器,來(lái)看下面的代碼:
清單 15. 注冊(cè) Controller 級(jí)的自定義屬性編輯器
@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)); } … } |
注意被標(biāo)注 @InitBinder 注解的方法必須擁有一個(gè) WebDataBinder 類型的入?yún)ⅲ员?Spring MVC 框架將注冊(cè)屬性編輯器的 WebDataBinder 對(duì)象傳遞進(jìn)來(lái)。
![]() ![]() |
![]()
|
在編寫(xiě) Controller 時(shí),常常需要在真正進(jìn)入請(qǐng)求處理方法前準(zhǔn)備一些數(shù)據(jù),以便請(qǐng)求處理或視圖渲染時(shí)使用。在傳統(tǒng)的 SimpleFormController 里,是通過(guò)復(fù)寫(xiě)其 referenceData() 方法來(lái)準(zhǔn)備引用數(shù)據(jù)的。在 Spring 2.5 時(shí),可以將任何一個(gè)擁有返回值的方法標(biāo)注上 @ModelAttribute,使其返回值將會(huì)進(jìn)入到模型對(duì)象的屬性列表中。來(lái)看下面的例子:
清單 16. 定義為處理請(qǐng)求準(zhǔn)備數(shù)據(jù)的方法
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")//<——①向模型對(duì)象中添加一個(gè)名為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(); //<——②在此訪問(wèn)模型中的items屬性 System.out.println("model.items:" + ((List<String>)model.get("items")).size()); return "listBoard"; } } |
在 ① 處,通過(guò)使用 @ModelAttribute 注解,populateItem() 方法將在任何請(qǐng)求處理方法執(zhí)行前調(diào)用,Spring MVC 會(huì)將該方法返回值以“items”為名放入到隱含的模型對(duì)象屬性列表中。
所以在 ② 處,我們就可以通過(guò) ModelMap 入?yún)⒃L問(wèn)到 items 屬性,當(dāng)執(zhí)行 listAllBoard() 請(qǐng)求處理方法時(shí),② 處將在控制臺(tái)打印出“model.items:2”的信息。當(dāng)然我們也可以在請(qǐng)求的視圖中訪問(wèn)到模型對(duì)象中的 items 屬性。
![]() ![]() |
![]()
|
Spring 2.5 對(duì) Spring MVC 進(jìn)行了很大增強(qiáng),現(xiàn)在我們幾乎完全可以使用基于注解的 Spring MVC 完全替換掉原來(lái)基于接口 Spring MVC 程序。基于注解的 Spring MVC 比之于基于接口的 Spring MVC 擁有以下幾點(diǎn)好處:
- 方便請(qǐng)求和控制器的映射;
- 方便請(qǐng)求處理方法入?yún)⒔壎║RL參數(shù);
- Controller 不必繼承任何接口,它僅是一個(gè)簡(jiǎn)單的 POJO。
但是基于注解的 Spring MVC 并不完美,還存在優(yōu)化的空間,因?yàn)樵谀承┡渲蒙纤然?XML 的配置更繁瑣。比如對(duì)于處理多個(gè)請(qǐng)求的 Controller 來(lái)說(shuō),假設(shè)我們使用一個(gè) URL 參數(shù)指定調(diào)用的處理方法(如 xxx.do?method=listBoardTopic),當(dāng)使用注解時(shí),每個(gè)請(qǐng)求處理方法都必須使用 @RequestMapping() 注解指定對(duì)應(yīng)的 URL 參數(shù)(如 @RequestMapping(params = "method=listBoardTopic")),而在 XML 配置中我們僅需要配置一個(gè) ParameterMethodNameResolver 就可以了。