SpringMVC實現資料繫結與傳參
1.1 URL繫結
@ReqeustMapping(value="/test/info", method=GET)
用於繫結Controller中的方法與對應的URL,預設可接受Get /Post請求,也可以用method進行限定。但是有更針對性的 @GetMapping
和 @PostMapping
,因此 @ReqeustMapping
主要用於在類層面添加註解,用於設定這個類下所有訪問方法的URL字首。
1.2 Controller接收請求引數
1.2.1原始的鍵值對直接傳遞,保證屬性名相同即可
前端
<form action="/um/p" method="post"> <input name = "username"></br> <input name = "password"></br> <input type = "submit" value="提交"></br> </form>
後端
@RequestMapping("/um")
public clss URLMappingController {
@PostMapping("/p")
@ResponseBody
public String postMapping(String username, Sring password){
}
}
SpringMVC
還可以進行自動型別轉換,比如前端傳的密碼是數字,後端可以用Long接收,可以支援自動型別轉換,但是如果前端密碼是 "abcd",後端 Long password
就會報 400 錯誤,型別轉換異常。因此出現400轉換異常通常就是 前端表單驗證不嚴導致的型別引數傳遞型別轉換異常。
1.2.2 @RequestParam的使用
如果引數名無法保持一致,比如前端屬性名是 user_name
,那麼後端接收也要用同樣的屬性名,如此會違反駝峰命名變數名的規則,此時可以用 @RequestParam
使用別名來繫結。而且前端傳來的複雜資料比如多選框的資料,後端如果用List和Map集合型別的屬性,也必須用 @RequestParam
來修飾,這樣SpringMVC才能正確識別並接收。此外@RquestParam
還可以設定預設值,即前端沒有限制某個資料強制必填時或者表單匿名提交時,後端如何設定該引數的預設值(如下程式碼①位置所示)。
前端:複雜表單
<div class="container"> <h2>學員調查問卷</h2> <form action="./apply" method="post"> <h3>您的姓名</h3> <input name="name" class="text" style="width: 150px"> <h3>您正在學習的技術方向</h3> <select name="course" style="width: 150px"> <option value="java">Java</option> <option value="h5">HTML5</option> <option value="python">Python</option> <option value="php">PHP</option> </select> <div> <h3>您的學習目的:</h3> <input type="checkbox" name="purpose" value="1">就業找工作 <input type="checkbox" name="purpose" value="2">工作要求 <input type="checkbox" name="purpose" value="3">興趣愛好 <input type="checkbox" name="purpose" value="4">其他 </div> <div style="text-align: center;padding-top:10px" > <input type="submit" value="提交" style="width:100px"> </div> </form> </div>
後端:
@Controller
public class FormController {
// ① @RequestParam可以設定當前端沒有傳參時的後端接收的預設值,如下 name會預設為 "ANON"
// @PostMapping("/apply")
@ResponseBody
public String apply(@RequestParam(value = "n",defaultValue = "ANON") String name, String course, Integer[] purpose){
System.out.println(name);
System.out.println(course);
for (Integer p : purpose) {
System.out.println(p);
}
return "SUCCESS";
}
// @RquestParam 修飾 List
// @PostMapping("/apply")
@ResponseBody
public String apply(String name, String course, @RequestParam List<Integer> purpose){
System.out.println(name);
System.out.println(course);
for (Integer p : purpose) {
System.out.println(p);
}
return "SUCCESS";
}
//用實體類來接收(只有在Post請求才會生效)
// ② @PostMapping("/apply")
@ResponseBody
public String apply(Form form){
return "SUCCESS";
}
// @RquestParam 修飾 Map
// @PostMapping("/apply")
@ResponseBody
public String apply(@RequestParam Map map){
System.out.println(map);
return "SUCCESS";
}
}
如果前端表單引數過多,比如有兩個以上,可以用 Map來接收,但是會存在資料丟失的問題:在上面例子中,前端 name=purpose 多選框的陣列資料 如果用Map接收只會儲存選中的陣列中的第一個資料,導致資料丟失。因此,這種結構化資料封裝成實體類接收更為規範。但是用實體類接收,切記必須在Post請求下(如上程式碼②位置所示)。
1.2.3 @RequestBody
這裡背景知識涉及到 Content-type
的型別,可以參考Conten-Type 和 MIME 以及 Content-Type幾種主要型別
簡而言之: Content-type
就是 請求和響應頭中表明 資訊主體以哪種格式編碼的,主要的有:
- application/x-www-form-urlencoded:早些年原始Html表單提交 Post請求時預設使用的 Content-type,提交的資料按照 key1=val1&key2=val2 的方式進行編碼,key和val都進行了URL轉碼;
- multipart/form-data:表單中存在上傳的檔案時 form 指定的 enctype;
- application/json:ajax以及後續的vue.js(axios) ,越來越多使用 application/json來編碼訊息;
- text/xml:一種使用 HTTP 作為傳輸協議,XML 作為編碼方式的遠端呼叫規範。
首先明確一點,一開始還是 Html簡單頁面的時候 表單傳送Post請求,則請求的 content-type預設是application/www-urlencode。如果 SpringMVC後端使用某個類來接收,則後端傳參時不需要新增 @RequestBody。但是如果 content-type=application/json 或者有檔案上傳的表單時,則必須使用 @RequestBody來修飾接收類。而現在的Vue.js前端框架搭配 axios(ajax變體) 實現表單傳參都預設用 application/json的方式,所以Java後端實體類獲取引數都必須前面加上 @RequestBody。 上述的演化歷程可以參考 vue的axios使用時,Content-Type 引發的引數接收不到的問題回顧。
1.2.4 關聯物件賦值
當前端表單如下:
<!--原始-->
<div>
<input name="username">
<input name="password">
<!---------------------->
<input name="name">
<input name="idno">
<input name="expire">
</div>
<!--與後端類匹配之後,進行鍼對性修改-->
<div>
<input name="username">
<input name="password">
<!---------------------->
<input name="idcard.name">
<input name="idcard.idno">
<input name="idcard.expire">
</div>
按照面向物件的涉及原則,在設計接收的實體類時,應該如下:
## User.java
public class User {
private String userName;
private String password;
private IDcard idcard = new IDcard();
}
## IDcard.java
public class IDcard {
private String name;
private String idno;
private Date expire;
}
由此可以將前端的表單的值準確的傳遞到User類的普通屬性以及關聯物件中。
1.2.5 @PathVariable註解
@PathVariable 用於獲取 url中的路徑變數
@GetMapping("/api/employees/{id}")
@ResponseBody
public String getEmployeesById(@PathVariable String id) {
return "ID: " + id;
}
## 直接指定,類似與 @RequestParam使用別名
@GetMapping("/api/employeeswithvariable/{id}")
@ResponseBody
public String getEmployeesByIdWithVariableName(@PathVariable("id") String employeeId) {
return "ID: " + employeeId;
}
## 多個變數
@GetMapping("/api/employees/{id}/{name}")
@ResponseBody
public String getEmployeesByIdAndName(@PathVariable String id, @PathVariable String name) {
return "ID: " + id + ", name: " + name;
}
1.2.6 時間型別引數的單個和全域性轉換
單個轉換
@DateTimeFormat可以接收前端指定格式的表示時間的字串格式的引數,並在後端解析成 Date型別或者LocalDate型別對應的屬性。
## 解析前端傳來的 "2021-10-01", 為Date型別
@PostMapping("/p1")
@ResponseBody
public String postMapping1(User user , String username ,@DateTimeFormat(pattern = "yyyy-MM-dd") Date createTime){
System.out.println(user.getUsername() + ":" + user.getPassword());
return "<h1>這是Post響應</h1>";
}
## 實體類接收,對應屬性田間 @DateTimeFormat註解
public class User {
private String username;
private Long password;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date createTime;
}
(BTW: 後端往前端轉可以新增 @JsonFormat來實現自動轉)
全域性設定
如果要全域性設定,需要自己寫一個自定義轉換器類:
public class MyDateConverter implements Converter<String, Date> {
public Date convert(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
Date d = sdf.parse(s);
return d;
} catch (ParseException e) {
return null;
}
}
}
在applicationContext.html中新增以下程式碼新增到容器中
<mvc:annotation-driven conversion-service="conversionService">
</mvc:annotation-driven>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.imooc.springmvc.converter.MyDateConverter"/>
</set>
</property>
</bean>
此時,後端獲取到前端的表示時間的格式為 "yyyy-MM-dd" 的字串就會自動轉換為Date型別。單個與全域性的設定,應該是互相搭配和補充。