Spring MVC Controller 嵌套參數在三種 Content-Type 下的綁定方式

如下將介紹 application/jsonmultipart/form-data, application/x-www-form-urlencoded 三種 Content-Type 傳參狀況下,Spring MVC 控制器中參數綁定的方式。

1. 前置內容

1.1 關於三種 Content-Type

這裏主要介紹三種 Content-Type:前端

  1. multipart/form-data 請求中既能夠攜帶文件,又能夠攜帶參數。其中參數以鍵值對的方式傳遞,參數之間、參數與文件之間以 content-disposition 分隔;
  2. application/x-www-form-urlencoded 只能上傳參數,不能攜帶文件,參數經過 ?xxx=xxx&xxx=xxx 的方式被組織在一塊兒;
  3. application/json 只能上傳參數,不能攜帶文件,參數不被特殊組織,保持原 JSON 字符串的形式。

1.2 擴展:瀏覽器調試工具中請求參數的形式

在前端發送請求時,咱們能夠經過瀏覽器看到請求的參數。在瀏覽器調試工具中,參數欄會有多種標題:jquery

  1. Query String Parameters


當使用 GET 方式提交請求時,採用這一標題spring

  1. Request Payload


當使用 application/json 方式提交時,採用這一標題chrome

  1. Form Data


當使用 multipart/form-dataapplication/x-www-form-urlencoded 方式提交時,採用這一標題,注意這兩種 form-data 的區別。json

1.3 測試數據

這裏採用嵌套數據以下:數組

{
    "username": "dailybird",
    "password": "dailybirdo",
  "ids": [1,2,3],
    "detail": {
        "gender": "male",
        "location": "Beijing",
    "ids": [4,5,6]
    }
}

注:與文件上傳相關的參數後面會單獨提到,這裏先進行非文件參數提交的實驗。瀏覽器

1.4 預期綁定的對象

這裏,仿照請求參數的格式建立 User 對象,咱們試圖將請求參數綁定到該對象上。這裏使用 Lombok 來減小 setter 的建立:app

@ToString
@Data
public class User {
    private String username;
    private String password;
    private List<Integer> ids;

    private Detail detail;

    @Data
    public static class Detail {
        private String gender;
        private String location;
        private List<Integer> ids;
    }
}

2. 綁定方式

2.1 利用 @RequestBody 解析 application/json 的 POST請求

控制器代碼以下:框架

@RequestMapping(value = "/application/json")
  public String applicationJson(@RequestBody User user) {
      log.info("{}", user.toString());
      return user.toString();
  }

當使用 POST,並攜帶 Content-Type: application/json 頭髮送請求時,控制器可以徹底解析嵌套的參數。函數

注:因爲 @RequestBody 自己是調用 HttpMessageConverter 解析請求體中的數據,而 GET 方式的參數不會存在於請求體中,因此 @RequestBody 不能處理 GET 方式的請求。

2.2 利用 @RequestParam 接收 multipart/form-data 及 application/x-www-form-urlencoded 中的請求

控制器代碼以下:

public String xWwwFormUrlencoded(@RequestParam("username") String username,
                                     @RequestParam("password") String password,
                                     @RequestParam("ids")List<Integer> ids,
                                     @RequestParam("detail") Detail detail) {
        log.info("{}, {}, {}, {}", username, password, ids, detail);
        return "";
    }

其中 Detail 類爲與以前 User 內部類等同的類。

2.2.1 application/x-www-form-urlencoded

這裏咱們藉助 jquery 的相關函數進行測試:

$.post("http://localhost:8083/application/x-www-form-urlencoded", {
    "username": "aaa",
    "password": "bbb",
    "ids": [1,2,3],
    "detail": {
        "gender": "ccc",
        "location": "ddd",
        "ids": [4,5,6]
    }
})

而後咱們收到了以下提示:

Required List parameter 'ids' is not present

但咱們確實已經發送了 ids 參數,爲何沒有獲取到呢?這一點咱們放到以後再談,先試一下 multipart/form-data 的方式。

2.2.2 multipart/form-data

固然,採用這一方式,咱們會收到一樣的提示:

Required List parameter 'ids' is not present

2.2.3 參數接收到的問題

讓咱們在瀏覽器的開發者工具中看一看請求參數實際的樣子:

不一樣於 PHP 框架 Laravel,@RequestParam 並不會將 ids[] 之類的數組類參數和 detail[xxx] 之類的嵌套參數進行重組。於是,控制器會認爲收到了 ids[] 參數,而不是 ids 參數,同理也適用於嵌套參數。

那咱們該怎麼作的?我在 Stack Overflow 上獲得瞭解答,咱們能夠採用如下方法之一:

  1. ids[] 改成 ids 傳參,即 ids=1&ids=2&... 的方式( 注意對比上圖 ),將嵌套類參數 detail[gender] 等改成 detail.gender
  2. 將嵌套參數採用 Map 類型接收。

注:在 Spring MVC 中,咱們能夠不書寫 @RequestParam,直接使用相與請求參數同名的變量進行接收( 或直接使用一個 POJO 對象 ),但該方式也存在着與以上相同的問題。

2.3 文件上傳問題

最開始已經說過,若要上傳文件,在上述三種 Content-Type 中,只能使用 multipart/form-data,在注意到 2.2 中所提到的問題後,咱們即可以經過 MultipartFile 類型的屬性來獲取到文件參數了。

3. 總結

從 Laravel 過渡到 Spring Boot,確實感到了在控制器層面兩者的差別( 固然在 DAO 層更是如此 ),如下給出一個列表,用以記念本身踩的坑:

參考連接

  1. postman中 form-data、x-www-form-urlencoded、raw、binary的區別 - CSDN博客
  2. http - What's the difference between "Request Payload" vs "Form Data" as seen in Chrome dev tools Network tab - Stack Overflow
  3. spring - What is difference between @RequestBody and @RequestParam? - Stack Overflow
  4. 淺談@RequestMapping @ResponseBody 和 @RequestBody 註解的用法與區別 - CSDN博客
相關文章
相關標籤/搜索