1. 程式人生 > 其它 >SpringMVC實現資料繫結與傳參​

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-TypeMIME 以及 Content-Type幾種主要型別
簡而言之: Content-type 就是 請求和響應頭中表明 資訊主體以哪種格式編碼的,主要的有:

  1. application/x-www-form-urlencoded:早些年原始Html表單提交 Post請求時預設使用的 Content-type,提交的資料按照 key1=val1&key2=val2 的方式進行編碼,key和val都進行了URL轉碼;
  2. multipart/form-data:表單中存在上傳的檔案時 form 指定的 enctype;
  3. application/json:ajax以及後續的vue.js(axios) ,越來越多使用 application/json來編碼訊息;
  4. 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型別。單個與全域性的設定,應該是互相搭配和補充。