# SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
#### 6.6.2、@RequestParam綁定單個請求參數值
@RequestParam用于將請求參數區數據映射到功能處理方法的參數上。
```
public String requestparam1(@RequestParam String username)
```
請求中包含username參數(如/requestparam1?username=zhang),則自動傳入。
此處要特別注意:右擊項目,選擇“屬性”,打開“屬性對話框”,選擇“Java Compiler”然后再打開的選項卡將“Add variable attributes?to generated class files”取消勾選,意思是不將局部變量信息添加到類文件中,如圖6-12所示:

圖6-12
當你在瀏覽器輸入URL,如“requestparam1?username=123”時會報如下錯誤
Name for argument type [java.lang.String] not available, and parameter name information not found in class file either,表示得不到功能處理方法的參數名,此時我們需要如下方法進行入參:
```
public String requestparam2(@RequestParam("username") String username)
```
即通過@RequestParam("username")明確告訴Spring Web MVC使用username進行入參。
接下來我們看一下@RequestParam注解主要有哪些參數:
value:參數名字,即入參的請求參數名字,如username表示請求的參數區中的名字為username的參數的值將傳入;
required:是否必須,默認是true,表示請求中一定要有相應的參數,否則將報404錯誤碼;
defaultValue:默認值,表示如果請求中沒有同名參數時的默認值,默認值可以是SpEL表達式,如“#{systemProperties['java.vm.version']}”。
```
public String requestparam4(@RequestParam(value="username",required=false) String username)
```
表示請求中可以沒有名字為username的參數,如果沒有默認為null,此處需要注意如下幾點:
原子類型:必須有值,否則拋出異常,如果允許空值請使用包裝類代替。
Boolean包裝類型類型:默認Boolean.FALSE,其他引用類型默認為null。
```
public String requestparam5(
@RequestParam(value="username", required=true, defaultValue="zhang") String username)
```
表示如果請求中沒有名字為username的參數,默認值為“zhang”。
如果請求中有多個同名的應該如何接收呢?如給用戶授權時,可能授予多個權限,首先看下如下代碼:
```
public String requestparam7(@RequestParam(value="role") String roleList)
```
如果請求參數類似于url?role=admin&rule=user,則實際roleList參數入參的數據為“admin,user”,即多個數據之間使用“,”分割;我們應該使用如下方式來接收多個請求參數:
```
public String requestparam7(@RequestParam(value="role") String[] roleList) ?
```
或
```
public String requestparam8(@RequestParam(value="list") List<String> list)
```
到此@RequestParam我們就介紹完了,以上測試代碼在cn.javass.chapter6.web.controller. paramtype.RequestParamTypeController中。
#### 6.6.3、@PathVariable綁定URI模板變量值
@PathVariable用于將請求URL中的模板變量映射到功能處理方法的參數上。
```
@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(
@PathVariable(value="userId") int userId,
@PathVariable(value="topicId") int topicId)
```
如請求的URL為“控制器URL/users/123/topics/456”,則自動將URL中模板變量{userId}和{topicId}綁定到通過@PathVariable注解的同名參數上,即入參后userId=123、topicId=456。代碼在PathVariableTypeController中。
#### 6.6.4、@CookieValue綁定Cookie數據值
@CookieValue用于將請求的Cookie數據映射到功能處理方法的參數上。
```
public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId)?
```
如上配置將自動將JSESSIONID值入參到sessionId參數上,defaultValue表示Cookie中沒有JSESSIONID時默認為空。
```
public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId) ?
```
傳入參數類型也可以是javax.servlet.http.Cookie類型。
測試代碼在CookieValueTypeController中。@CookieValue也擁有和@RequestParam相同的三個參數,含義一樣。
#### 6.6.5、@RequestHeader綁定請求頭數據
@RequestHeader用于將請求的頭信息區數據映射到功能處理方法的參數上。
```
@RequestMapping(value="/header")
public String test(
@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value="Accept") String[] accepts)
```
如上配置將自動將請求頭“User-Agent”值入參到userAgent參數上,并將“Accept”請求頭值入參到accepts參數上。測試代碼在HeaderValueTypeController中。
@RequestHeader也擁有和@RequestParam相同的三個參數,含義一樣。
#### 6.6.6、@ModelAttribute綁定請求參數到命令對象
@ModelAttribute一個具有如下三個作用:
①綁定請求參數到命令對象:放在功能處理方法的入參上時,用于將多個請求參數綁定到一個命令對象,從而簡化綁定流程,而且自動暴露為模型數據用于視圖頁面展示時使用;
②暴露表單引用對象為模型數據:放在處理器的一般方法(非功能處理方法)上時,是為表單準備要展示的表單引用對象,如注冊時需要選擇的所在城市等,而且在執行功能處理方法(@RequestMapping注解的方法)之前,自動添加到模型對象中,用于視圖頁面展示時使用;
③暴露@RequestMapping方法返回值為模型數據:放在功能處理方法的返回值上時,是暴露功能處理方法的返回值為模型數據,用于視圖頁面展示時使用。
**一、綁定請求參數到命令對象**
如用戶登錄,我們需要捕獲用戶登錄的請求參數(用戶名、密碼)并封裝為用戶對象,此時我們可以使用@ModelAttribute綁定多個請求參數到我們的命令對象。
```
public String test1(@ModelAttribute("user") UserModel user)
```
和6.6.1一節中的五、命令/表單對象功能一樣。只是此處多了一個注解@ModelAttribute("user"),它的作用是將該綁定的命令對象以“user”為名稱添加到模型對象中供視圖頁面展示使用。我們此時可以在視圖頁面使用${user.username}來獲取綁定的命令對象的屬性。
綁定請求參數到命令對象支持對象圖導航式的綁定,如請求參數包含“?username=zhang&password=123&workInfo.city=bj”自動綁定到user中的workInfo屬性的city屬性中。
```
@RequestMapping(value="/model2/{username}")
public String test2(@ModelAttribute("model") DataBinderTestModel model) {?
```
DataBinderTestModel相關模型請從第三章拷貝過來,請求參數到命令對象的綁定規則詳見【4.16.1、數據綁定】一節,URI模板變量也能自動綁定到命令對象中,當你請求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked”會自動綁定到命令對象上。
當URI模板變量和請求參數同名時,URI模板變量具有高優先權。
**二、暴露表單引用對象為模型數據**
```
@ModelAttribute("cityList")
public List<String> cityList() {
return Arrays.asList("北京", "山東");
}?
```
如上代碼會在執行功能處理方法之前執行,并將其自動添加到模型對象中,在功能處理方法中調用Model?入參的containsAttribute("cityList")將會返回true。
```
@ModelAttribute("user") //①
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
//TODO 去數據庫根據用戶名查找用戶對象
UserModel user = new UserModel();
user.setRealname("zhang");
return user;
}?
```
如你要修改用戶資料時一般需要根據用戶的編號/用戶名查找用戶來進行編輯,此時可以通過如上代碼查找要編輯的用戶。
也可以進行一些默認值的處理。
```
@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model)?
```
此處我們看到①和②有同名的命令對象,那Spring Web MVC內部如何處理的呢:
(1、首先執行@ModelAttribute注解的方法,準備視圖展示時所需要的模型數據;@ModelAttribute注解方法形式參數規則和@RequestMapping規則一樣,如可以有@RequestParam等;
(2、執行@RequestMapping注解方法,進行模型綁定時首先查找模型數據中是否含有同名對象,如果有直接使用,如果沒有通過反射創建一個,因此②處的user將使用①處返回的命令對象。即②處的user等于①處的user。
**三、暴露@RequestMapping方法返回值為模型數據**
```
public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)
```
大家可以看到返回值類型是命令對象類型,而且通過@ModelAttribute("user2")注解,此時會暴露返回值到模型數據(名字為user2)中供視圖展示使用。那哪個視圖應該展示呢?此時Spring Web MVC會根據RequestToViewNameTranslator進行邏輯視圖名的翻譯,詳見【4.15.5、RequestToViewNameTranslator】一節。
此時又有問題了,@RequestMapping注解方法的入參user暴露到模型數據中的名字也是user2,其實我們能猜到:
(3、@ModelAttribute注解的返回值會覆蓋@RequestMapping注解方法中的@ModelAttribute注解的同名命令對象。
**四、匿名綁定命令參數**
```
public String test4(@ModelAttribute UserModel user, Model model)
或
public String test5(UserModel user, Model model)?
```
此時我們沒有為命令對象提供暴露到模型數據中的名字,此時的名字是什么呢?Spring Web MVC自動將簡單類名(首字母小寫)作為名字暴露,如“cn.javass.chapter6.model.UserModel”暴露的名字為“userModel”。
```
public @ModelAttribute List<String> test6()
或
public @ModelAttribute List<UserModel> test7()?
```
對于集合類型(Collection接口的實現者們,包括數組),生成的模型對象屬性名為“簡單類名(首字母小寫)”+“List”,如List<String>生成的模型對象屬性名為“stringList”,List<UserModel>生成的模型對象屬性名為“userModelList”。
其他情況一律都是使用簡單類名(首字母小寫)作為模型對象屬性名,如Map<String, UserModel>類型的模型對象屬性名為“map”。
#### 6.6.7、@SessionAttributes綁定命令對象到session
有時候我們需要在多次請求之間保持數據,一般情況需要我們明確的調用HttpSession的API來存取會話數據,如多步驟提交的表單。Spring Web MVC提供了@SessionAttributes進行請求間透明的存取會話數據。
```
//1、在控制器類頭上添加@SessionAttributes注解
@SessionAttributes(value = {"user"}) //①
public class SessionAttributeController
//2、@ModelAttribute注解的方法進行表單引用對象的創建
@ModelAttribute("user") //②
public UserModel initUser()
//3、@RequestMapping注解方法的@ModelAttribute注解的參數進行命令對象的綁定
@RequestMapping("/session1") //③
public String session1(@ModelAttribute("user") UserModel user)
//4、通過SessionStatus的setComplete()方法清除@SessionAttributes指定的會話數據
@RequestMapping("/session2") //③
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
if(true) { //④
status.setComplete();
}
return "success";
}?
```
@SessionAttributes(value = {"user"})含義:
@SessionAttributes(value = {"user"})?標識將模型數據中的名字為“user”?的對象存儲到會話中(默認HttpSession),此處value指定將模型數據中的哪些數據(名字進行匹配)存儲到會話中,此外還有一個types屬性表示模型數據中的哪些類型的對象存儲到會話范圍內,如果同時指定value和types屬性則那些名字和類型都匹配的對象才能存儲到會話范圍內。
包含@SessionAttributes的執行流程如下所示:
①?首先根據@SessionAttributes注解信息查找會話內的對象放入到模型數據中;
②?執行@ModelAttribute注解的方法:如果模型數據中包含同名的數據,則不執行@ModelAttribute注解方法進行準備表單引用數據,而是使用①步驟中的會話數據;如果模型數據中不包含同名的數據,執行@ModelAttribute注解的方法并將返回值添加到模型數據中;
③?執行@RequestMapping方法,綁定@ModelAttribute注解的參數:查找模型數據中是否有@ModelAttribute注解的同名對象,如果有直接使用,否則通過反射創建一個;并將請求參數綁定到該命令對象;
此處需要注意:如果使用@SessionAttributes注解控制器類之后,③步驟一定是從模型對象中取得同名的命令對象,如果模型數據中不存在將拋出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)
或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)異常。
④?如果會話可以銷毀了,如多步驟提交表單的最后一步,此時可以調用SessionStatus對象的setComplete()標識當前會話的@SessionAttributes指定的數據可以清理了,此時當@RequestMapping功能處理方法執行完畢會進行清理會話數據。
我們通過Spring Web MVC的源代碼驗證一下吧,此處我們分析的是Spring3.1的RequestMappingHandlerAdapter,讀者可以自行驗證Spring3.0的AnnotationMethodHandlerAdapter,流程一樣:
(1、RequestMappingHandlerAdapter.invokeHandlerMethod
```
//1、RequestMappingHandlerAdapter首先調用ModelFactory的initModel方法準備模型數據:
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
//2、調用@RequestMapping注解的功能處理方法
requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
//3、更新/合并模型數據
modelFactory.updateModel(webRequest, mavContainer);?
```
(2、ModelFactory.initModel
```
Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);
//1.1、將與@SessionAttributes注解相關的會話對象放入模型數據中
mavContainer.mergeAttributes(attributesInSession);
//1.2、調用@ModelAttribute方法添加表單引用對象
invokeModelAttributeMethods(request, mavContainer);
//1.3、驗證模型數據中是否包含@SessionAttributes注解相關的會話對象,不包含拋出異常
for (String name : findSessionAttributeArguments(handlerMethod)) {
if (!mavContainer.containsAttribute(name)) {
//1.4、此處防止在@ModelAttribute注解方法又添加了會話對象
//如在@ModelAttribute注解方法調用session.setAttribute("user", new UserModel());
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
}
mavContainer.addAttribute(name, value);
}?
```
(3、ModelFactory.invokeModelAttributeMethods
```
for (InvocableHandlerMethod attrMethod : this.attributeMethods) {
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
//1.2.1、如果模型數據中包含同名數據則不再添加
if (mavContainer.containsAttribute(modelName)) {
continue;
}
//1.2.2、調用@ModelAttribute注解方法并將返回值添加到模型數據中,此處省略實現代碼
}?
```
(4、requestMappingMethod.invokeAndHandle 調用功能處理方法,此處省略
(5、ModelFactory.updateMode 更新模型數據
```
//3.1、如果會話被標識為完成,此時從會話中清除@SessionAttributes注解相關的會話對象
if (mavContainer.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
//3.2、如果會話沒有完成,將模型數據中的@SessionAttributes注解相關的對象添加到會話中
else {
this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());
}
//省略部分代碼?
```
到此@SessionAtrribute介紹完畢,測試代碼在cn.javass.chapter6.web.controller.paramtype.SessionAttributeController中。
另外cn.javass.chapter6.web.controller.paramtype.WizardFormController是一個類似于【4.11、AbstractWizardFormController】中介紹的多步驟表單實現,此處不再貼代碼,多步驟提交表單需要考慮會話超時問題,這種方式可能對用戶不太友好,我們可以采取隱藏表單(即當前步驟將其他步驟的表單隱藏)或表單數據存數據庫(每步驟更新下數據庫數據)等方案解決。
#### 6.6.8、@Value綁定SpEL表示式
@Value用于將一個SpEL表達式結果映射到到功能處理方法的參數上。
```
public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)
```
到此數據綁定我們就介紹完了,對于沒有介紹的方法參數和注解(包括自定義注解)在后續章節進行介紹。接下來我們學習下數據類型轉換吧。
轉載請注明出處【[http://jinnianshilongnian.iteye.com/blog/1703694](/blog/1703694)】?
- 跟我學 Spring3
- 【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
- 【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我學Spring3
- 【第二章】 IoC 之 2.3 IoC的配置使用——跟我學Spring3
- 【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3
- 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
- 【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3
- 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
- 【第四章】 資源 之 4.1 基礎知識 ——跟我學spring3
- 【第四章】 資源 之 4.2 內置Resource實現 ——跟我學spring3
- 【第四章】 資源 之 4.3 訪問Resource ——跟我學spring3
- 【第四章】 資源 之 4.4 Resource通配符路徑 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.1 概述 5.2 SpEL基礎 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.3 SpEL語法 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3
- 【第六章】 AOP 之 6.1 AOP基礎 ——跟我學spring3
- 【第六章】 AOP 之 6.2 AOP的HelloWorld ——跟我學spring3
- 【第六章】 AOP 之 6.3 基于Schema的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.5 AspectJ切入點語法詳解 ——跟我學spring3
- 【第六章】 AOP 之 6.6 通知參數 ——跟我學spring3
- 【第六章】 AOP 之 6.7 通知順序 ——跟我學spring3
- 【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3
- 【第六章】 AOP 之 6.9 代理機制 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.1 概述 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.2 JDBC模板類 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.3 關系數據庫操作對象化 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.4 Spring提供的其它幫助 ——跟我學spring3【私塾在線原創】
- 【第七章】 對JDBC的支持 之 7.5 集成Spring JDBC及最佳實踐 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.1 概述 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.2 集成Hibernate3 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.3 集成iBATIS ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.4 集成JPA ——跟我學spring3
- 【第九章】 Spring的事務 之 9.1 數據庫事務概述 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.2 事務管理器 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.4 聲明式事務 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.1 概述 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.1 概述 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.2 實現通用層 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.3 實現積分商城層 ——跟我學spring3
- 【第十二章】零配置 之 12.1 概述 ——跟我學spring3
- 【第十二章】零配置 之 12.2 注解實現Bean依賴注入 ——跟我學spring3
- 【第十二章】零配置 之 12.3 注解實現Bean定義 ——跟我學spring3
- 【第十二章】零配置 之 12.4 基于Java類定義Bean配置元數據 ——跟我學spring3
- 【第十二章】零配置 之 12.5 綜合示例-積分商城 ——跟我學spring3
- 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
- 【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3
- 跟我學 Spring MVC
- SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常見問題總結
- Spring Web MVC中的頁面緩存支持 ——跟我學SpringMVC系列
- Spring3 Web MVC下的數據類型轉換(第一篇)——《跟我學Spring3 Web MVC》搶先看
- Spring3 Web MVC下的數據格式化(第二篇)——《跟我學Spring3 Web MVC》搶先看
- 第一章 Web MVC簡介 —— 跟開濤學SpringMVC
- 第二章 Spring MVC入門 —— 跟開濤學SpringMVC
- 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC
- 第四章 Controller接口控制器詳解(1)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(2)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(3)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解 (4)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(5)——跟著開濤學SpringMVC
- 跟著開濤學SpringMVC 第一章源代碼下載
- 第二章 Spring MVC入門 源代碼下載
- 第四章 Controller接口控制器詳解 源代碼下載
- 第四章 Controller接口控制器詳解(6)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(7 完)——跟著開濤學SpringMVC
- 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 源代碼下載 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 注解式控制器運行流程及處理器定義 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- 源代碼下載 第六章 注解式控制器詳解
- SpringMVC3強大的請求映射規則詳解 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- Spring MVC 3.1新特性 生產者、消費者請求限定 —— 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(1)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC數據類型轉換——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據驗證——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC