27.1.1 @RequestMapping
使用
之前,我們是把@RequestMapping
注解放在方法之上,用來給方法綁定一個請求映射。除此以外,@RequestMapping
注解還可以放在類的上面。例如,我們給之前的請求處理類(FirstSpringDemo.java
)的類名上也加一個@RequestMapping
注解,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping("/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/firstSpringMVC")
public String welcomeToSpringMVC()
{
System.out.println("welcome to springMVC");
return "success";
}
}
類前面加了@RequestMapping
注解以后,前端發來的請求就不能再直接去匹配方法上面的@RequestMapping
了。而是應該先匹配類前的@RequestMapping
值,再匹配方法前的@RequestMapping
。
因此,方法前面@RequestMapping
的值,是相對於類前的@RequestMapping
值。如果類的前面不存在@RequestMapping
,則方法前的@RequestMapping
值就是相對於項目根目錄。 例如,在類前加了@ RequestMapping("/FirstSpringDemo")
以后,前台就必須通過以下路徑來訪問:
index.jsp
…
<body>
<a href="FirstSpringDemo/firstSpringMVC">
My First SpringMVC Demo
</a>
</body>
…
即先通過“FirstSpringDemo
”匹配類前的@RequestMapping("/FirstSpringDemo")
,再通過“ firstSpringMVC
”匹配方法前的@RequestMapping("/firstSpringMVC")
。
27.1.2 @RequestMapping
屬性
@RequestMapping
注解的常用屬性如下,
屬性 | 簡介 |
value | 指定請求的實際URL地址,屬性名value可省。例如@RequestMapping("/firstSpringMVC")等價於@RequestMapping(value="/firstSpringMVC") |
method | 指定請求方式,包含 GET(默認)、POST、PUT、DELETE等;可以通過枚舉類RequestMethod設置,如method=RequestMethod.POST。 |
params | 規定請求中的些參數值,必須滿足一定的條件。params本身是一個字符串數組。 |
headers | 規定請求中的請求頭(header)必須滿足一定的條件。 |
(1)method
屬性
例如,因為超鏈接本身是采用GET
方式提交請求。因此,若前台仍然是通過<ahref="FirstSpringDemo/firstSpringMVC">...</a>
發送請求,則處理類必須使用GET
方式才能接受到此請求。如果使用“POST
”等其他方式,是無法獲取到該請求的,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/firstSpringMVC"
,method=RequestMethod.POST)
public String welcomeToSpringMVC()
{
return "success";
}
}
…
如果再點擊上面超鏈接,運行結果如下,
圖27-01
提示我們“請求方法不支持GET
方式”。
如果把超鏈接,替換成以下POST
方式的表單提交,
index.jsp
…
<body>
<form action="FirstSpringDemo/firstSpringMVC" method="POST">
<input type="submit" value="POST方式提交"/>
</form>
</body>
…
點擊“POST
方式提交”后,就又會正常運行。
(2)params
屬性
例如,我們通過超鏈接加入兩個請求參數,如下,
index.jsp
…
<body>
<a href=
"FirstSpringDemo/requestWithParams?name=zhangsan&age=20">
requestWithParams...
</a>
</body>
…
再通過params
來檢查請求中的參數是否符合要求,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithParams"
,params={"name","age!=23"})
public String requestWithParams()
{
return "success";
}
}
…
以上請求通過params
規定請求參數必須包含“name
”參數,並且“age!=23
”,我們之前發來的請求“…?name=zhangsan&age=20
”符合要求,因此可以被該方法接收並處理。如果發送的請求參數是“…?name=zhangsan&age=23
”或“…? age=23
”等不符合params
規定,就會引發我們所熟悉 “404”異常。
params
支持以下表達式:
表達式 | 簡介 |
paramName | 必須包含參數名為“paramName”的參數。 |
!paramName | 不能包含參數名為“paramName”的參數。 |
paramName!=paramValue | 必須包含參數名為“paramName”的參數,但參數值不能是“paramValue”。 |
(3)headers
屬性
SpringMVC用headers
來約束“參數”,用headers
來約束“請求頭”。我們可以在火狐瀏覽器里打開“firebug”查看每一次請求的“請求頭”,如下,
圖27-02
“請求頭”指明了請求所攜帶的MIME
類型、字符集等信息。
例如,可以通過“headers
”指定請求頭中的“Accept-Language”必須是“zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
”,以及“Accept-Encoding
”必須是“gzip, deflate
”,如下
FirstSpringDemo.java
…
@RequestMapping(value="/requestWithHeaders",
headers={"Accept-Language=zh-CN,zh;
q=0.8,en-US;q=0.5,en;q=0.3",
"Accept-Encoding=gzip, deflate"})
public String requestWithHeaders()
{
return "success";
}
…
關於“請求頭”的知識,讀者可以查閱相關資料,本書不作為重點講解。
27.2 基於@RequestMapping
的配置
27.2.1 Ant風格的請求路徑
SpringMVC除了支持傳統方式的請求外,還支持Ant風格的請求路徑。
Ant風格的請求路徑支持以下3種通配符:
通配符 | 簡介 |
? | 匹配任何單字符 |
* | 匹配0或者任意數量的字符 |
** | 匹配0或者更多的目錄 |
例如,在處理方法前配置@RequestMapping(value="/requestWithAntPath/*/test")
,表示請求路徑的“requestWithAntPath
”和“test
”之間可以填任意字符,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithAntPath/*/test")
public String requestWithAntPath()
{
return "success";
}
…
}
如果前端發送以下請求,是可以匹配到requestWithAntPath()
方法的。
index.jsp
…
<body>
<a href="FirstSpringDemo/requestWithAntPath/lanqiao/test">
requestWithAntPath...
</a>
…
</body>
…
其他Ant風格的示例如下表,
請求路徑 | 匹配的示例 |
/requestWithAntPath/**/test | /requestWithAntPath/a/b/test、/requestWithAntPath/test等 |
/requestWithAntPath/test?? | /requestWithAntPath/testxy、/requestWithAntPath/testqq等 |
27.2.2 使用@PathVariable
獲取動態參數
在SpringMVC中,可以使用@PathVariable
來獲得請求路徑中的動態參數。
如下,通過前端傳入一個參數“9527”,
index.jsp
…
<body>
<a href="FirstSpringDemo/requestWithPathVariable/9527">
requestWithPathVariable...
</a>
</body>
…
處理方法就可以通過@PathVariable
來獲取此參數值,如下
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithPathVariable/{id}")
public String
requestWithPathVariable(@PathVariable("id") Integer id)
{
System.out.println("id:"+id);
return "success";
}}
…
具體是通過@RequestMapping(value="/requestWithPathVariable/{id}")
中的占位符“{id}
”接收到參數值“9527”,再把參數值傳給@PathVariable("id")
中的“id
”,最后再把值賦值給方法的參數id
。
27.2.3 REST風格
REST(Representational State Transfer)是一種編程風格,可以顯著降低開發的復雜性,是當前非常流行的一種互聯網軟件架構。
在學習REST之前,我們首先要知道,在HTTP協議里面有多種請求方式,並且其中的POST
、DELETE
、PUT
、GET
等四個方式,分別對應增刪改查四種操作,具體是:POST
對應“增”,DELETE
對應“刪”、PUT
對應“改”,GET
對應“查”。但是普通瀏覽器中的form表單,只支持GET
和POST
兩種請求方式。為了使普通瀏覽器支持PUT
和DELETE
方式,可以使用Spring提供的過濾器HiddenHttpMethodFilter,此過濾器可以通過一定的規則,將部分POST
請求轉換為PUT
或DELETE
請求。如果讀者想了解HiddenHttpMethodFilter的底層代碼,可以閱讀spring-web-x.x.xRELEASE.jar包中HiddenHttpMethodFilter類里的doFilterInternal()
方法。
實現PUT
或DELETE
請求方式的步驟如下:
1.在web.xml中配置HiddenHttpMethodFilter
過濾器,如下,
web.xml
…
<web-app …>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>
org.springframework.web.filter.HiddenHttpMethodFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2.在form
表單中指定請求方式為method="post"
;並在表單中增加一個hidden
隱藏域,且設置隱藏域的name
及value
屬性:name="_method"
、value="PUT"
或value="DELETE"
。
3.在處理方法的@RequestMapping
注解中,用method
屬性指定請求方式(如method=RequestMethod.DELETE、method=RequestMethod.PUT
等)。
例如,在web.xml中配置了HiddenHttpMethodFilter
以后,就可以使用下面的方式發送並處理增、刪、改、查的請求:
①發送請求
index.jsp
…
<body>
…
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="hidden" name="_method" value="DELETE" />
<input type="submit" value="刪除" />
</form>
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="hidden" name="_method" value="PUT" />
<input type="submit" value="修改" />
</form>
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="submit" value="增加" />
</form>
<a href="FirstSpringDemo/requestWithREST/9527">查看</a>
</body>
…
②處理請求
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
…
//使用REST風格,處理“刪除”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.DELETE)
public String requestWithRestDelete(@PathVariable("id")
Integer id)
{
System.out.println("刪除時需要的id:"+id);
return "success";
}
//使用REST風格,處理“修改”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.PUT)
public String requestWithRestPut(@PathVariable("id")
Integer id)
{
System.out.println("修改時需要的id:"+id);
return "success";
}
//使用REST風格,處理“增加”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.POST)
public String requestWithRestAdd(@PathVariable("id")
Integer id)
{
System.out.println("增加時需要的id:"+id);
return "success";
}
//使用REST風格,處理“查看”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.GET)
public String requestWithRestGet(@PathVariable("id") Integer id)
{
System.out.println("查詢時需要的id:"+id);
return "success";
}
}
…
運行index.jsp頁面,如圖,
圖27-03
依次點擊刪除、修改、增加、查看按鈕,可在控制台得到以下結果:
圖27-04
27.2.4 使用@RequestParam
獲取請求參數
Spring MVC可以通過@RequestParam
來接收請求中的參數值,該注解有三個常用的屬性:
屬性名 | 簡介 |
value | 請求攜帶參數的參數名 |
required | 標識請求參數中是否必須存在某個具體的參數。 true(默認):必須存在;若不存在,將拋出異常。 false:不必須。 |
defaultValue | 給參數賦一個默認值。如果請求中不存在某個參數,則該參數就取defaultValue所設置的值。 |
index.jsp
…
<a href="FirstSpringDemo/requestParam?name=zhangsan&age=23">
TestRequestParam
</a>
…
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
//使用@RequestParam注解接收請求參數
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value="name") String name, @RequestParam(value="age") Integer age)
{
System.out.println("name: " + name + " age: " + age);
return "success";
}
}…
@RequestParam
通過value
值與傳入的參數名匹配,並將參數值賦值給@RequestParam
后面的變量。例如,通過@RequestParam(value="name")
接收index.jsp傳來的name
參數值(即zhangsan),並將參數值(zhangsan)賦值給@RequestParam
后面的String name,類似於String name="zhangsan"
。
若將請求中的age
參數刪除,如下
index.jsp
<a href="FirstSpringDemo/requestParam?name=zhangsan">
TestRequestParam
</a>
再次執行以上超鏈接,則會發生異常,如下:
圖27-05
為了解決此異常,就可以給age
參數的@RequestParam
加入required=false
,如下:
FirstSpringDemo.java
…
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value = "name") String name, @RequestParam(value = "age",required=false) Integer age)
{
…
}
…
此外,還可以通過@RequestParam
的defaultValue
屬性給請求參數設置默認值,如下,
FirstSpringDemo.java
…
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value = "name")
String name, @RequestParam(value = "age",
required=false,defaultValue="23") Integer age)
{
System.out.println("name: " + name + " age: " + age);
return "success";
}…
通過defaultValue="23"
將age
的默認值設置為”23”,即如果前端發送的請求中沒有攜帶age參數,則age
的值就是23。
27.2.5 @RequestHeader
注解
在HTTP協議中,每一次請求都會攜帶相關的“頭信息”,例如可以在fireBug中觀察到以下頭信息:
圖27-06
SpringMVC也提供了@RequestHeader
注解來幫助我們獲取請求中的“頭信息”,如下:
index.jsp
<a href="FirstSpringDemo/requestHeader"> requestHeader</a><br/>
FirstSpringDemo
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/requestHeader")
public String
requestHeader(@RequestHeader(value="Accept-Language")
String al){
System.out.println("Accept-Language:" + al);
return "success";
}
}
…
通過@RequestHeader
獲取“頭信息”,並通過value
屬性指定獲取頭信息中的Accept-Language
值,並把值賦值給al
參數。
執行index.jsp中的requestHeader
超鏈接,可在控制台得到以下結果:
圖27-07
27.2.6 @CookieValue
注解
@CookieValue
可以給處理方法入參綁定某個Cookie
值。例如,客戶端有一個名為JSESSIONID的Cookie
對象,服務端可以通過@CookieValue
來獲取此JSESSIONID的值:
index.jsp
<a href="FirstSpringDemo/cookieValue">cookieValue</a><br/>
FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/cookieValue")
public String cookieValue(@CookieValue(value="JSESSIONID")
String sessionid){
System.out.println("sessionid:" + sessionid);
return "success";
}
}
…
運行結果:
圖27-08
27.2.7 使用實體類對象接收請求參數值
如果處理方法的參數是一個實體類對象,那么SpringMVC會將請求的參數名與實體類對象的屬性進行匹配,為實體類對象的屬性賦值,並且支持級聯屬性的賦值。以下是具體的示例:
實體類:Student.java
public class Student
{
private String stuName;
private int stuAge;
private Address address ;
//setter、getter
@Override
public String toString()
{
return "姓名:"+this.stuName+"\t年齡:"+this.stuAge
+"\t家庭地址:"+this.address.getHomeAddress()
+"\t學校地址:"+this.address.getSchoolAddress();
}
}
實體類:Address.java
public class Address
{
private String schoolAddress;
private String homeAddress;
//setter、getter
}
在form
表單中使用實體類的屬性名作為<input>
標簽的name
值(可使用級聯屬性):
請求頁:index.jsp
<form action="FirstSpringDemo/entityProperties">
姓名:<input type="text" name="stuName"/><br>
年齡:<input type="text" name="stuAge"/><br>
<!-- 使用級聯屬性 -->
家庭地址:<input type="text" name="address.homeAddress"/><br>
學校地址:<input type="text" name="address.schoolAddress"/><br>
<input type="submit" value="提交"/>
</form>
請求處理類:FirstSpringDemo.java
// import…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
//使用實體類對象接收請求參數值(form表單中提交的數據)
@RequestMapping("/entityProperties")
public String entityProperties(Student student){
System.out.println(student);
return "success";
}
…
}
執行index.jsp,如下
圖27-09
點擊提交后,控制台的輸出結果:
圖27-10
27.2.8 使用Servlet API作為參數
如果想使用原生的Servlet API進行開發,只需要將Servlet API放入方法的參數中,如下:
…
//使用Servlet API開發
@RequestMapping("/developWithServletAPI")
public String developWithServletAPI(HttpServletRequest requst,
HttpServletResponse response, HttpSession session)
{
//使用request和response參數處理請求或響應...
return "success";
}
…
27.3 處理模型數據
假設需要從數據庫查詢數據:在MVC設計模式中,用戶從視圖頁面(V)發起一個請求到控制器(C),控制器調用Service/Dao等處理數據,並從數據庫中返回數據(M)。之后,控制器(C)拿到數據(M)后加以處理,並返回到視圖頁面(V)。
SpringMVC提供了四種途徑來處理帶數據的視圖(M和V):ModelAndView
,ModelMap
、Map
及Model
,@SessionAttributes
,@ModelAttribute
27.3.1 ModelAndView
ModelAndView
包含了Model(M)和View(V)兩部分。用法如下:
index.jsp
…
<a href="FirstSpringDemo/testModelAndView">testModelAndView</a>
…
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
String view = "success";
ModelAndView mav= new ModelAndView(view);
Student student = new Student("張三",23);
//添加student對象數據放入ModelAndView中
mav.addObject("student ",student);
return mav;
}
…
}
通過ModelAndView的構造方法將視圖頁面的名稱”success”放入mav
對象,再通過addObject()
方法將數據放入mav
對象,最后返回mav
。之后,程序就會跳轉到mav
指定的視圖頁面views/success.jsp(仍然會被視圖解析器加上了前綴和后綴),並將mav
中的數據student
放入request
作用於之中。
返回的視圖頁面:success.jsp
<body>
${requestScope.student.stuName }
</body>
執行index.jsp中的超鏈接,運行結果:
圖27-11
27.3.2 使用Map
、ModelMap
、Model
作為方法的參數處理數據
可以給SpringMVC的請求處理方法增加一個Map
類型的參數。如果向此Map
中增加數據,那么該數據也會被放入request
作用域中。
index.jsp
<a href="FirstSpringDemo/testMap">testMap</a>
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
} …
}
返回的視圖頁面success.jsp及運行結果同上例。
除了Map以外,還可以給請求處理方法增加一個ModelMap
或Model
類型的參數,效果完全一樣,如下:
使用ModelMap類型的參數
@RequestMapping("/testModelMap")
public String testMap(ModelMap map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
使用Model類型的參數
@RequestMapping("/testModel")
public String testModel(Model map)
{
Student student = new Student("張三", 23);
map.addAttribute("student", student);
return "success";
}
27.3.3 使用@SessionAttributes
處理數據
我們已經知道,向ModelAndView
以及Map
、ModelMap
、Model
參數中增加數據時,數據會同時被放到request
作用域中。如果還要把數據放到session
作用域中,就需要使用@SessionAttributes
注解,如下:
index.jsp
<a href="FirstSpringDemo/testSessionAttribute">
testSessionAttribute
</a>
請求處理類:FirstSpringDemo.java
@SessionAttributes(value="student")
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String ,Object> map){
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
…
}
在類的上方加入@SessionAttributes(value="student")
,表示將request
作用域中的"student"
對象同時加入到session
作用域之中。
返回的視圖頁面:success.jsp
…
<body>
request作用域中:${requestScope.student.stuName } <br/>
session作用域中:${sessionScope.student.stuName } <br/>
</body>
…
執行index.jsp中的超鏈接,運行結果:
圖27-12
@SessionAttributes
除了可以使用value
將指定的對象名加入到session
范圍,還可以使用types
將某一個類型的對象都加入到session
范圍,如下:
請求處理類:FirstSpringDemo.java
…
@SessionAttributes(types=Student.class)
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String ,Object> map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
…
}
通過@SessionAttributes(types=Student.class)
將request
作用域中Student
類型的對象同時加入到session
作用域之中。
27.3.4 使用@ModelAttribute
注解處理數據
假設數據庫中存在一條學生信息,如下:
圖27-13
現在我們需要修改學生的年齡(姓名等其他信息不變),先嘗試用以下方式完成:
index.jsp(修改學號為31號的學生年齡)
…
<form action="FirstSpringDemo/testModelAttribute" method="post">
<input type="hidden" value="31" name="stuNo"/>
年齡:<input type="text" name="stuAge"/><br>
<input type="submit" value="修改"/>
</form>
…
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testModelAttribute")
public String testModelAttribute(Student student){
//省略數據庫的更新操作:將數據表中stuNo=31的學生信息,更新為參數student中的各屬性值
System.out.println("更新后的學生信息:姓名: "
+ student.getStuName()+",年齡:"+student.getStuAge());
return "success";
}
}
…
執行index.jsp,並將年齡修改為66歲,如圖:
圖27-14
點擊修改,控制台的輸出結果如下:
圖27-15
可以發現,年齡確實成功修改了,但是姓名卻變成了null
。這是因為在index.jsp的form
表單中,只提交了stuAge
字段的屬性,而不存在stuName等其他字段的屬性,因此stuAge
屬性會從輸入框中獲取,而其他屬性值會使用相應類型的默認值(如String
類型的stuName
默認值就是null
)。
這與我們的本意不符,我們的本意是:被修改的屬性,使用修改后的值(如stuAge
);而沒被修改的屬性,要使用數據庫中原有的值(如果stuName
應該保留“張三”)。要想實現我們的“本意”,可以在請求控制類中增加一個用@ModelAttribute
的方法,如下:
請求處理類:FirstSpringDemo.java
…
//@SessionAttributes(types=Student.class)
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@ModelAttribute
public void queryStudentBeforeUpdate (int stuNo,Map<String,
Object> map)
{
//使用帶數據的實體類對象,模擬從數據庫中獲取學號為stuNo的學生對象
Student student = new Student();
student.setStuNo(stuNo);
student.setStuName("張三");
student.setStuAge(23);
//即用以上語句模擬Student student
= stuService.queryStudentByNo(stuNo);
//將從數據庫中查詢的student對象放入map中
map.put("student", student);
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(Student student){
//省略數據庫的更新操作
System.out.println("更新后的學生信息:姓名: " + student.getStuName()+",年齡:"+student.getStuAge());
return "success";
}
}
重新提交之前的form
表單,控制台輸出結果如下:
圖27-16
可以發現,不但stuAge
得到了修改,並且stuName
也保留了原來的值。
@ModelAttribute的應用邏輯是:
@ModelAttribute
修飾的方法(如queryStudentBeforeUpdate()
)會在請求處理方法(如testModelAttribute()
)調用之前被執行:具體是,如果請求處理方法有輸入參數(如student
),則程序會在@ModelAttribute
修飾的方法中的map
對象里,尋找map
中的key
值是否與請求處理方法的參數名①一致,如果一致(如map中有名為”student”的key,testModelAttribute()
方法也有名為”student”的參數)就會用參數student
中不為null的屬性值(如stuNo=31,stuAge=66)去覆蓋map中的student對象值,最后使用的是覆蓋后的student
對象。例如,map中的student對象值是:“學號:31,姓名:張三,年齡23”,參數中student的對象值是:“學號31,姓名null,年齡66”,因此用參數student不為null的屬性值(stuNo=31,stuAge=66)去覆蓋map
中student屬性值的結果就是:“學號31,姓名:張三,年齡66”。即form表單傳來的stuNo
和stuAge
屬性得到了修改,而form表單中不存在的stuName屬性則保持不變。如果map中的key值與請求處理方法的參數名不一致,則需要在參數前使用@ModelAttribute
標識出map
中對應的key
值,如下:
請求處理類:FirstSpringDemo.java
…
@ModelAttribute
public void queryStudentBeforeUpdate(int stuNo,Map<String,
Object> map)
{
...
map.put("stu", student);
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(
@ModelAttribute("stu")Student student){
...
return "success";
}
…
map
中的key
是”stu”
,與方法的參數名”student”
不一致,就需要在參數名前使用@ModelAttribute("stu")
來進行標識。
①參數名:實際是判斷是否與“首字母小寫的參數類型”一致。如參數的類型是Student
,則會判斷是否與首字母小寫的參數類型(即”student”
)一致。此段落中,用“參數名”來代替“首字母小寫的參數類型”僅僅是為了便於讀者閱讀。
說明:
標有@ModelAttribute
注解的方法,會在請求處理類中的每一個方法執行前,都執行一次,因此需要謹慎使用。
27.1.1 @RequestMapping
使用
之前,我們是把@RequestMapping
注解放在方法之上,用來給方法綁定一個請求映射。除此以外,@RequestMapping
注解還可以放在類的上面。例如,我們給之前的請求處理類(FirstSpringDemo.java
)的類名上也加一個@RequestMapping
注解,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping("/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/firstSpringMVC")
public String welcomeToSpringMVC()
{
System.out.println("welcome to springMVC");
return "success";
}
}
類前面加了@RequestMapping
注解以后,前端發來的請求就不能再直接去匹配方法上面的@RequestMapping
了。而是應該先匹配類前的@RequestMapping
值,再匹配方法前的@RequestMapping
。
因此,方法前面@RequestMapping
的值,是相對於類前的@RequestMapping
值。如果類的前面不存在@RequestMapping
,則方法前的@RequestMapping
值就是相對於項目根目錄。 例如,在類前加了@ RequestMapping("/FirstSpringDemo")
以后,前台就必須通過以下路徑來訪問:
index.jsp
…
<body>
<a href="FirstSpringDemo/firstSpringMVC">
My First SpringMVC Demo
</a>
</body>
…
即先通過“FirstSpringDemo
”匹配類前的@RequestMapping("/FirstSpringDemo")
,再通過“ firstSpringMVC
”匹配方法前的@RequestMapping("/firstSpringMVC")
。
27.1.2 @RequestMapping
屬性
@RequestMapping
注解的常用屬性如下,
屬性 | 簡介 |
value | 指定請求的實際URL地址,屬性名value可省。例如@RequestMapping("/firstSpringMVC")等價於@RequestMapping(value="/firstSpringMVC") |
method | 指定請求方式,包含 GET(默認)、POST、PUT、DELETE等;可以通過枚舉類RequestMethod設置,如method=RequestMethod.POST。 |
params | 規定請求中的些參數值,必須滿足一定的條件。params本身是一個字符串數組。 |
headers | 規定請求中的請求頭(header)必須滿足一定的條件。 |
(1)method
屬性
例如,因為超鏈接本身是采用GET
方式提交請求。因此,若前台仍然是通過<ahref="FirstSpringDemo/firstSpringMVC">...</a>
發送請求,則處理類必須使用GET
方式才能接受到此請求。如果使用“POST
”等其他方式,是無法獲取到該請求的,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/firstSpringMVC"
,method=RequestMethod.POST)
public String welcomeToSpringMVC()
{
return "success";
}
}
…
如果再點擊上面超鏈接,運行結果如下,
圖27-01
提示我們“請求方法不支持GET
方式”。
如果把超鏈接,替換成以下POST
方式的表單提交,
index.jsp
…
<body>
<form action="FirstSpringDemo/firstSpringMVC" method="POST">
<input type="submit" value="POST方式提交"/>
</form>
</body>
…
點擊“POST
方式提交”后,就又會正常運行。
(2)params
屬性
例如,我們通過超鏈接加入兩個請求參數,如下,
index.jsp
…
<body>
<a href=
"FirstSpringDemo/requestWithParams?name=zhangsan&age=20">
requestWithParams...
</a>
</body>
…
再通過params
來檢查請求中的參數是否符合要求,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithParams"
,params={"name","age!=23"})
public String requestWithParams()
{
return "success";
}
}
…
以上請求通過params
規定請求參數必須包含“name
”參數,並且“age!=23
”,我們之前發來的請求“…?name=zhangsan&age=20
”符合要求,因此可以被該方法接收並處理。如果發送的請求參數是“…?name=zhangsan&age=23
”或“…? age=23
”等不符合params
規定,就會引發我們所熟悉 “404”異常。
params
支持以下表達式:
表達式 | 簡介 |
paramName | 必須包含參數名為“paramName”的參數。 |
!paramName | 不能包含參數名為“paramName”的參數。 |
paramName!=paramValue | 必須包含參數名為“paramName”的參數,但參數值不能是“paramValue”。 |
(3)headers
屬性
SpringMVC用headers
來約束“參數”,用headers
來約束“請求頭”。我們可以在火狐瀏覽器里打開“firebug”查看每一次請求的“請求頭”,如下,
圖27-02
“請求頭”指明了請求所攜帶的MIME
類型、字符集等信息。
例如,可以通過“headers
”指定請求頭中的“Accept-Language”必須是“zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
”,以及“Accept-Encoding
”必須是“gzip, deflate
”,如下
FirstSpringDemo.java
…
@RequestMapping(value="/requestWithHeaders",
headers={"Accept-Language=zh-CN,zh;
q=0.8,en-US;q=0.5,en;q=0.3",
"Accept-Encoding=gzip, deflate"})
public String requestWithHeaders()
{
return "success";
}
…
關於“請求頭”的知識,讀者可以查閱相關資料,本書不作為重點講解。
27.2 基於@RequestMapping
的配置
27.2.1 Ant風格的請求路徑
SpringMVC除了支持傳統方式的請求外,還支持Ant風格的請求路徑。
Ant風格的請求路徑支持以下3種通配符:
通配符 | 簡介 |
? | 匹配任何單字符 |
* | 匹配0或者任意數量的字符 |
** | 匹配0或者更多的目錄 |
例如,在處理方法前配置@RequestMapping(value="/requestWithAntPath/*/test")
,表示請求路徑的“requestWithAntPath
”和“test
”之間可以填任意字符,如下,
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithAntPath/*/test")
public String requestWithAntPath()
{
return "success";
}
…
}
如果前端發送以下請求,是可以匹配到requestWithAntPath()
方法的。
index.jsp
…
<body>
<a href="FirstSpringDemo/requestWithAntPath/lanqiao/test">
requestWithAntPath...
</a>
…
</body>
…
其他Ant風格的示例如下表,
請求路徑 | 匹配的示例 |
/requestWithAntPath/**/test | /requestWithAntPath/a/b/test、/requestWithAntPath/test等 |
/requestWithAntPath/test?? | /requestWithAntPath/testxy、/requestWithAntPath/testqq等 |
27.2.2 使用@PathVariable
獲取動態參數
在SpringMVC中,可以使用@PathVariable
來獲得請求路徑中的動態參數。
如下,通過前端傳入一個參數“9527”,
index.jsp
…
<body>
<a href="FirstSpringDemo/requestWithPathVariable/9527">
requestWithPathVariable...
</a>
</body>
…
處理方法就可以通過@PathVariable
來獲取此參數值,如下
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping(value="/requestWithPathVariable/{id}")
public String
requestWithPathVariable(@PathVariable("id") Integer id)
{
System.out.println("id:"+id);
return "success";
}}
…
具體是通過@RequestMapping(value="/requestWithPathVariable/{id}")
中的占位符“{id}
”接收到參數值“9527”,再把參數值傳給@PathVariable("id")
中的“id
”,最后再把值賦值給方法的參數id
。
27.2.3 REST風格
REST(Representational State Transfer)是一種編程風格,可以顯著降低開發的復雜性,是當前非常流行的一種互聯網軟件架構。
在學習REST之前,我們首先要知道,在HTTP協議里面有多種請求方式,並且其中的POST
、DELETE
、PUT
、GET
等四個方式,分別對應增刪改查四種操作,具體是:POST
對應“增”,DELETE
對應“刪”、PUT
對應“改”,GET
對應“查”。但是普通瀏覽器中的form表單,只支持GET
和POST
兩種請求方式。為了使普通瀏覽器支持PUT
和DELETE
方式,可以使用Spring提供的過濾器HiddenHttpMethodFilter,此過濾器可以通過一定的規則,將部分POST
請求轉換為PUT
或DELETE
請求。如果讀者想了解HiddenHttpMethodFilter的底層代碼,可以閱讀spring-web-x.x.xRELEASE.jar包中HiddenHttpMethodFilter類里的doFilterInternal()
方法。
實現PUT
或DELETE
請求方式的步驟如下:
1.在web.xml中配置HiddenHttpMethodFilter
過濾器,如下,
web.xml
…
<web-app …>
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>
org.springframework.web.filter.HiddenHttpMethodFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2.在form
表單中指定請求方式為method="post"
;並在表單中增加一個hidden
隱藏域,且設置隱藏域的name
及value
屬性:name="_method"
、value="PUT"
或value="DELETE"
。
3.在處理方法的@RequestMapping
注解中,用method
屬性指定請求方式(如method=RequestMethod.DELETE、method=RequestMethod.PUT
等)。
例如,在web.xml中配置了HiddenHttpMethodFilter
以后,就可以使用下面的方式發送並處理增、刪、改、查的請求:
①發送請求
index.jsp
…
<body>
…
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="hidden" name="_method" value="DELETE" />
<input type="submit" value="刪除" />
</form>
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="hidden" name="_method" value="PUT" />
<input type="submit" value="修改" />
</form>
<form action="FirstSpringDemo/requestWithREST/9527"
method="post">
<input type="submit" value="增加" />
</form>
<a href="FirstSpringDemo/requestWithREST/9527">查看</a>
</body>
…
②處理請求
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
…
//使用REST風格,處理“刪除”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.DELETE)
public String requestWithRestDelete(@PathVariable("id")
Integer id)
{
System.out.println("刪除時需要的id:"+id);
return "success";
}
//使用REST風格,處理“修改”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.PUT)
public String requestWithRestPut(@PathVariable("id")
Integer id)
{
System.out.println("修改時需要的id:"+id);
return "success";
}
//使用REST風格,處理“增加”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.POST)
public String requestWithRestAdd(@PathVariable("id")
Integer id)
{
System.out.println("增加時需要的id:"+id);
return "success";
}
//使用REST風格,處理“查看”的請求
@RequestMapping(value="/requestWithREST/{id}",
method=RequestMethod.GET)
public String requestWithRestGet(@PathVariable("id") Integer id)
{
System.out.println("查詢時需要的id:"+id);
return "success";
}
}
…
運行index.jsp頁面,如圖,
圖27-03
依次點擊刪除、修改、增加、查看按鈕,可在控制台得到以下結果:
圖27-04
27.2.4 使用@RequestParam
獲取請求參數
Spring MVC可以通過@RequestParam
來接收請求中的參數值,該注解有三個常用的屬性:
屬性名 | 簡介 |
value | 請求攜帶參數的參數名 |
required | 標識請求參數中是否必須存在某個具體的參數。 true(默認):必須存在;若不存在,將拋出異常。 false:不必須。 |
defaultValue | 給參數賦一個默認值。如果請求中不存在某個參數,則該參數就取defaultValue所設置的值。 |
index.jsp
…
<a href="FirstSpringDemo/requestParam?name=zhangsan&age=23">
TestRequestParam
</a>
…
FirstSpringDemo.java
…
@Controller
@RequestMapping(value="/FirstSpringDemo")
public class FirstSpringDemo
{
//使用@RequestParam注解接收請求參數
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value="name") String name, @RequestParam(value="age") Integer age)
{
System.out.println("name: " + name + " age: " + age);
return "success";
}
}…
@RequestParam
通過value
值與傳入的參數名匹配,並將參數值賦值給@RequestParam
后面的變量。例如,通過@RequestParam(value="name")
接收index.jsp傳來的name
參數值(即zhangsan),並將參數值(zhangsan)賦值給@RequestParam
后面的String name,類似於String name="zhangsan"
。
若將請求中的age
參數刪除,如下
index.jsp
<a href="FirstSpringDemo/requestParam?name=zhangsan">
TestRequestParam
</a>
再次執行以上超鏈接,則會發生異常,如下:
圖27-05
為了解決此異常,就可以給age
參數的@RequestParam
加入required=false
,如下:
FirstSpringDemo.java
…
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value = "name") String name, @RequestParam(value = "age",required=false) Integer age)
{
…
}
…
此外,還可以通過@RequestParam
的defaultValue
屬性給請求參數設置默認值,如下,
FirstSpringDemo.java
…
@RequestMapping("/requestParam")
public String requestParam(@RequestParam(value = "name")
String name, @RequestParam(value = "age",
required=false,defaultValue="23") Integer age)
{
System.out.println("name: " + name + " age: " + age);
return "success";
}…
通過defaultValue="23"
將age
的默認值設置為”23”,即如果前端發送的請求中沒有攜帶age參數,則age
的值就是23。
27.2.5 @RequestHeader
注解
在HTTP協議中,每一次請求都會攜帶相關的“頭信息”,例如可以在fireBug中觀察到以下頭信息:
圖27-06
SpringMVC也提供了@RequestHeader
注解來幫助我們獲取請求中的“頭信息”,如下:
index.jsp
<a href="FirstSpringDemo/requestHeader"> requestHeader</a><br/>
FirstSpringDemo
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/requestHeader")
public String
requestHeader(@RequestHeader(value="Accept-Language")
String al){
System.out.println("Accept-Language:" + al);
return "success";
}
}
…
通過@RequestHeader
獲取“頭信息”,並通過value
屬性指定獲取頭信息中的Accept-Language
值,並把值賦值給al
參數。
執行index.jsp中的requestHeader
超鏈接,可在控制台得到以下結果:
圖27-07
27.2.6 @CookieValue
注解
@CookieValue
可以給處理方法入參綁定某個Cookie
值。例如,客戶端有一個名為JSESSIONID的Cookie
對象,服務端可以通過@CookieValue
來獲取此JSESSIONID的值:
index.jsp
<a href="FirstSpringDemo/cookieValue">cookieValue</a><br/>
FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/cookieValue")
public String cookieValue(@CookieValue(value="JSESSIONID")
String sessionid){
System.out.println("sessionid:" + sessionid);
return "success";
}
}
…
運行結果:
圖27-08
27.2.7 使用實體類對象接收請求參數值
如果處理方法的參數是一個實體類對象,那么SpringMVC會將請求的參數名與實體類對象的屬性進行匹配,為實體類對象的屬性賦值,並且支持級聯屬性的賦值。以下是具體的示例:
實體類:Student.java
public class Student
{
private String stuName;
private int stuAge;
private Address address ;
//setter、getter
@Override
public String toString()
{
return "姓名:"+this.stuName+"\t年齡:"+this.stuAge
+"\t家庭地址:"+this.address.getHomeAddress()
+"\t學校地址:"+this.address.getSchoolAddress();
}
}
實體類:Address.java
public class Address
{
private String schoolAddress;
private String homeAddress;
//setter、getter
}
在form
表單中使用實體類的屬性名作為<input>
標簽的name
值(可使用級聯屬性):
請求頁:index.jsp
<form action="FirstSpringDemo/entityProperties">
姓名:<input type="text" name="stuName"/><br>
年齡:<input type="text" name="stuAge"/><br>
<!-- 使用級聯屬性 -->
家庭地址:<input type="text" name="address.homeAddress"/><br>
學校地址:<input type="text" name="address.schoolAddress"/><br>
<input type="submit" value="提交"/>
</form>
請求處理類:FirstSpringDemo.java
// import…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
//使用實體類對象接收請求參數值(form表單中提交的數據)
@RequestMapping("/entityProperties")
public String entityProperties(Student student){
System.out.println(student);
return "success";
}
…
}
執行index.jsp,如下
圖27-09
點擊提交后,控制台的輸出結果:
圖27-10
27.2.8 使用Servlet API作為參數
如果想使用原生的Servlet API進行開發,只需要將Servlet API放入方法的參數中,如下:
…
//使用Servlet API開發
@RequestMapping("/developWithServletAPI")
public String developWithServletAPI(HttpServletRequest requst,
HttpServletResponse response, HttpSession session)
{
//使用request和response參數處理請求或響應...
return "success";
}
…
27.3 處理模型數據
假設需要從數據庫查詢數據:在MVC設計模式中,用戶從視圖頁面(V)發起一個請求到控制器(C),控制器調用Service/Dao等處理數據,並從數據庫中返回數據(M)。之后,控制器(C)拿到數據(M)后加以處理,並返回到視圖頁面(V)。
SpringMVC提供了四種途徑來處理帶數據的視圖(M和V):ModelAndView
,ModelMap
、Map
及Model
,@SessionAttributes
,@ModelAttribute
27.3.1 ModelAndView
ModelAndView
包含了Model(M)和View(V)兩部分。用法如下:
index.jsp
…
<a href="FirstSpringDemo/testModelAndView">testModelAndView</a>
…
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
String view = "success";
ModelAndView mav= new ModelAndView(view);
Student student = new Student("張三",23);
//添加student對象數據放入ModelAndView中
mav.addObject("student ",student);
return mav;
}
…
}
通過ModelAndView的構造方法將視圖頁面的名稱”success”放入mav
對象,再通過addObject()
方法將數據放入mav
對象,最后返回mav
。之后,程序就會跳轉到mav
指定的視圖頁面views/success.jsp(仍然會被視圖解析器加上了前綴和后綴),並將mav
中的數據student
放入request
作用於之中。
返回的視圖頁面:success.jsp
<body>
${requestScope.student.stuName }
</body>
執行index.jsp中的超鏈接,運行結果:
圖27-11
27.3.2 使用Map
、ModelMap
、Model
作為方法的參數處理數據
可以給SpringMVC的請求處理方法增加一個Map
類型的參數。如果向此Map
中增加數據,那么該數據也會被放入request
作用域中。
index.jsp
<a href="FirstSpringDemo/testMap">testMap</a>
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
} …
}
返回的視圖頁面success.jsp及運行結果同上例。
除了Map以外,還可以給請求處理方法增加一個ModelMap
或Model
類型的參數,效果完全一樣,如下:
使用ModelMap類型的參數
@RequestMapping("/testModelMap")
public String testMap(ModelMap map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
使用Model類型的參數
@RequestMapping("/testModel")
public String testModel(Model map)
{
Student student = new Student("張三", 23);
map.addAttribute("student", student);
return "success";
}
27.3.3 使用@SessionAttributes
處理數據
我們已經知道,向ModelAndView
以及Map
、ModelMap
、Model
參數中增加數據時,數據會同時被放到request
作用域中。如果還要把數據放到session
作用域中,就需要使用@SessionAttributes
注解,如下:
index.jsp
<a href="FirstSpringDemo/testSessionAttribute">
testSessionAttribute
</a>
請求處理類:FirstSpringDemo.java
@SessionAttributes(value="student")
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String ,Object> map){
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
…
}
在類的上方加入@SessionAttributes(value="student")
,表示將request
作用域中的"student"
對象同時加入到session
作用域之中。
返回的視圖頁面:success.jsp
…
<body>
request作用域中:${requestScope.student.stuName } <br/>
session作用域中:${sessionScope.student.stuName } <br/>
</body>
…
執行index.jsp中的超鏈接,運行結果:
圖27-12
@SessionAttributes
除了可以使用value
將指定的對象名加入到session
范圍,還可以使用types
將某一個類型的對象都加入到session
范圍,如下:
請求處理類:FirstSpringDemo.java
…
@SessionAttributes(types=Student.class)
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testSessionAttribute")
public String testSessionAttribute(Map<String ,Object> map)
{
Student student = new Student("張三", 23);
map.put("student", student);
return "success";
}
…
}
通過@SessionAttributes(types=Student.class)
將request
作用域中Student
類型的對象同時加入到session
作用域之中。
27.3.4 使用@ModelAttribute
注解處理數據
假設數據庫中存在一條學生信息,如下:
圖27-13
現在我們需要修改學生的年齡(姓名等其他信息不變),先嘗試用以下方式完成:
index.jsp(修改學號為31號的學生年齡)
…
<form action="FirstSpringDemo/testModelAttribute" method="post">
<input type="hidden" value="31" name="stuNo"/>
年齡:<input type="text" name="stuAge"/><br>
<input type="submit" value="修改"/>
</form>
…
請求處理類:FirstSpringDemo.java
…
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@RequestMapping("/testModelAttribute")
public String testModelAttribute(Student student){
//省略數據庫的更新操作:將數據表中stuNo=31的學生信息,更新為參數student中的各屬性值
System.out.println("更新后的學生信息:姓名: "
+ student.getStuName()+",年齡:"+student.getStuAge());
return "success";
}
}
…
執行index.jsp,並將年齡修改為66歲,如圖:
圖27-14
點擊修改,控制台的輸出結果如下:
圖27-15
可以發現,年齡確實成功修改了,但是姓名卻變成了null
。這是因為在index.jsp的form
表單中,只提交了stuAge
字段的屬性,而不存在stuName等其他字段的屬性,因此stuAge
屬性會從輸入框中獲取,而其他屬性值會使用相應類型的默認值(如String
類型的stuName
默認值就是null
)。
這與我們的本意不符,我們的本意是:被修改的屬性,使用修改后的值(如stuAge
);而沒被修改的屬性,要使用數據庫中原有的值(如果stuName
應該保留“張三”)。要想實現我們的“本意”,可以在請求控制類中增加一個用@ModelAttribute
的方法,如下:
請求處理類:FirstSpringDemo.java
…
//@SessionAttributes(types=Student.class)
@Controller
@RequestMapping(value = "/FirstSpringDemo")
public class FirstSpringDemo
{
@ModelAttribute
public void queryStudentBeforeUpdate (int stuNo,Map<String,
Object> map)
{
//使用帶數據的實體類對象,模擬從數據庫中獲取學號為stuNo的學生對象
Student student = new Student();
student.setStuNo(stuNo);
student.setStuName("張三");
student.setStuAge(23);
//即用以上語句模擬Student student
= stuService.queryStudentByNo(stuNo);
//將從數據庫中查詢的student對象放入map中
map.put("student", student);
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(Student student){
//省略數據庫的更新操作
System.out.println("更新后的學生信息:姓名: " + student.getStuName()+",年齡:"+student.getStuAge());
return "success";
}
}
重新提交之前的form
表單,控制台輸出結果如下:
圖27-16
可以發現,不但stuAge
得到了修改,並且stuName
也保留了原來的值。
@ModelAttribute的應用邏輯是:
@ModelAttribute
修飾的方法(如queryStudentBeforeUpdate()
)會在請求處理方法(如testModelAttribute()
)調用之前被執行:具體是,如果請求處理方法有輸入參數(如student
),則程序會在@ModelAttribute
修飾的方法中的map
對象里,尋找map
中的key
值是否與請求處理方法的參數名①一致,如果一致(如map中有名為”student”的key,testModelAttribute()
方法也有名為”student”的參數)就會用參數student
中不為null的屬性值(如stuNo=31,stuAge=66)去覆蓋map中的student對象值,最后使用的是覆蓋后的student
對象。例如,map中的student對象值是:“學號:31,姓名:張三,年齡23”,參數中student的對象值是:“學號31,姓名null,年齡66”,因此用參數student不為null的屬性值(stuNo=31,stuAge=66)去覆蓋map
中student屬性值的結果就是:“學號31,姓名:張三,年齡66”。即form表單傳來的stuNo
和stuAge
屬性得到了修改,而form表單中不存在的stuName屬性則保持不變。如果map中的key值與請求處理方法的參數名不一致,則需要在參數前使用@ModelAttribute
標識出map
中對應的key
值,如下:
請求處理類:FirstSpringDemo.java
…
@ModelAttribute
public void queryStudentBeforeUpdate(int stuNo,Map<String,
Object> map)
{
...
map.put("stu", student);
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(
@ModelAttribute("stu")Student student){
...
return "success";
}
…
map
中的key
是”stu”
,與方法的參數名”student”
不一致,就需要在參數名前使用@ModelAttribute("stu")
來進行標識。
①參數名:實際是判斷是否與“首字母小寫的參數類型”一致。如參數的類型是Student
,則會判斷是否與首字母小寫的參數類型(即”student”
)一致。此段落中,用“參數名”來代替“首字母小寫的參數類型”僅僅是為了便於讀者閱讀。
說明:
標有@ModelAttribute
注解的方法,會在請求處理類中的每一個方法執行前,都執行一次,因此需要謹慎使用。