Spring之RequestBody的使用姿勢小結
SpringMVC中處理請求參數有好幾種不同的方式,如我們常見的下面幾種
- 根據
HttpServletRequest
對象獲取 - 根據
@PathVariable
注解獲取url參數 - 根據
@RequestParam
注解獲取請求參數 - 根據Bean的方式獲取請求參數
- 根據
@ModelAttribute
注解獲取請求參數
對上面幾種方式有興趣的可以看一下這篇博文: SpringMVC之請求參數的獲取方式
除了上面的幾種方式之外,還有一種 @RequestBody
的使用方式,本文則主要介紹這種傳參的使用姿勢和相關注意事項
I. 使用姿勢
1. 服務接口
借助Spring框架,使用@RequestBody
並沒有什么難度,很簡單的就可以寫一個使用case出來,如下
@Slf4j
@RestController
public class ReqBodyController {
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Req {
private String key;
private Integer size;
}
@RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
public BaseRsp body(@RequestBody Req req) {
log.info("req: {}", req);
return new BaseRsp<>(req);
}
}
看上面的實現,和我們通常的寫法並無差別,無非是將以前的 @RequsetParam
注解換成 @RequsetBody
注解,而且這個注解內部只有一個filed,比RequsetParam
還少
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
// 默認參數必須存在,否則會拋一個異常
boolean required() default true;
}
看到上面的實現,估計也可以猜出,這個注解對於后端而言,寫沒啥問題,關鍵是如何用(具體來講是如何給前端用)
2. 接口調用
上面寫完了,接下來的重點就是如何使用了,在使用之前,有必要了解下 RequestBody
這個注解出現的原有以及應用場景(換句話說它和RequestParam有什么區別,為什么要單獨的搞一個這個東西出來)
RequestBody
@requestBody注解常用來處理content-type不是默認的application/x-www-form-urlcoded編碼的內容,比如說:application/json或者是application/xml等。一般情況下來說常用其來處理application/json類型。
a. content-type定義
在進入下一步之前,有必要說一下Content-Type
這個http請求頭的作用了,下面一段來自其他博文,原文鏈接見最后
MediaType,即是Internet Media Type,互聯網媒體類型;也叫做MIME類型,在Http協議消息頭中,使用Content-Type來表示具體請求中的媒體類型信息。
常見媒體格式如下:
- text/html : HTML格式
- text/plain :純文本格式
- text/xml : XML格式
- image/gif :gif圖片格式
- image/jpeg :jpg圖片格式
- image/png:png圖片格式
以application開頭的媒體格式類型:
- application/xhtml+xml :XHTML格式
- application/xml : XML數據格式
- application/atom+xml :Atom XML聚合格式
- application/json : JSON數據格式
- application/pdf :pdf格式
- application/msword : Word文檔格式
- application/octet-stream : 二進制流數據(如常見的文件下載)
- application/x-www-form-urlencoded :
b. content-type 實例說明
上面算是基本定義和取值,下面結合實例對典型的幾種方式進行說明
- application/x-www-form-urlencoded:數據被編碼為名稱/值對。這是標准的編碼格式。
- multipart/form-data: 數據被編碼為一條消息,頁上的每個控件對應消息中的一個部分。
- text/plain: 數據以純文本形式(text/json/xml/html)進行編碼,其中不含任何控件或格式字符
對於前端使用而言,form表單的enctype屬性為編碼方式,常用有兩種:application/x-www-form-urlencoded
和multipart/form-data
,默認為application/x-www-form-urlencoded
。
Get請求
發起Get請求時,瀏覽器用application/x-www-form-urlencoded
方式,將表單數據轉換成一個字符串(key1=value1&key2=value2...)拼接到url上,這就是我們常見的url帶請求參數的情況
Post表單
發起post請求時,如果沒有傳文件,瀏覽器也是將form表單的數據封裝成k=v的結果丟到http body中,拿開源中國的博客提交的表單為例,一個典型的post表單,上傳的數據拼裝在form data中,為kv結構
如果有傳文件的場景,Content-Type類型會升級為multipart/form-data
,這一塊不詳細展開,后面有機會再說
Post json串
post表單除了前面一種方式之外,還有一種也是我們常見的,就是講所有的表單數據放在一個大的json串中,然后丟給后端,這里也有一個在線的實例,某電商平台的商品發表,截圖如下
注意看上面的Request Payload,是一個大的json串,和前面差別明顯
c. RequestBody請求
根據RequestBody的定義,要想訪問前面定義的那個接口,使用傳統的表單傳遞方式是不行的,curl命令測試如下
curl -X POST -d 'key=haha&size=123' http://127.0.0.1:19533/body
后端對應的輸出如下(拋了一個異常,表示@RequestBody注解修飾rest接口,不支持 Content type 'application/x-www-form-urlencoded;charset=UTF-8'
因此使用姿勢需要顯示添加請求頭,傳參也改變一下
curl -l -H "Content-type: application/json" -X GET -d '{"key": "!23", "size": 10}' http://127.0.0.1:19533/body
返回結果如下
3. 注意事項
a. content-type顯示指定
根據前面的說明,可以知道 @RequestBody
這個注解的使用,使得REST接口接收的不再content-type為application/x-www-form-urlencoded
的請求, 反而需要顯示指定為application/json
b. 請求方法
RequestBody支持GET方法么?前面都是采用post提交參數,如果改成GET會怎樣?
curl測試方式
curl -l -H "Content-type: application/json" -X GET -d '{"key": "!23", "size": 10}' http://127.0.0.1:19533/body\?key\=app
對應的后端debug截圖如下,發現使用GET方式,並沒有問題,依然可以獲取到參數
換成大名鼎鼎的POSTMAN來測試
使用post方法請求時,截圖如下,主要就是修改header的content-type,然后在body中添加json串格式的請求
然而改成get之后,body都直接灰掉了,也就是它不支持在get請求時,提交Body數據
url請求方式
接下來直接換成url的請求方式,看是否直接支持get請求
http://127.0.0.1:19533/body?{"key": "!23", "size": 10}
瀏覽器中輸入時,服務器400, 換成curl方式請求,拋的是缺少RequestBody的異常,也就是說,將json串拼接到url中貌似不行(也有可能是我的使用姿勢不對。。。)
小結
- 到這里小結一下,使用RequestBody獲取參數時,還是老老實實的選擇POST方法比較合適,至於原因,跟大眾,隨主流,跟着大家的習慣走比較好
c. 參數獲取
這個主要就是后端編寫接口時,獲取RequestBody參數的問題了,通過測試,發現在HttpServletRequest
參數中,居然拿不到提交的RequestBody參數,演示如下
請求url為
curl -l -H "Content-type: application/json" -X POST -d '{"key": "!23", "size": 10}' http://127.0.0.1:19533/body\?url\=ddd
對應的debug截圖如下,url參數可以拿到,RequestBody參數沒有
首先聲明,下面的這段分析,沒有看源碼,純屬於個人推斷,如有問題,對被誤導的朋友表示歉意,也希望對此有了解的朋友,多多批評指正
從傳文件的思路出發,前端傳文件給后端時,后端是基於流的方式,將上傳的二進制流,寫入到`MultipartFile`;而二進制流讀完之后,沒法再重復的讀
RequestBody可能也是這么個邏輯,首先是從HttpServletRequest的Reader流中讀取body參數並封裝到上面的req對象,而不會像url參數一樣,寫回到`javax.servlet.ServletRequest#getParameterMap`
對上面的猜測做一個小小的驗證,改成直接從HttpServletRequest的Reader流中獲取請求body參數
@RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
public BaseRsp body(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder builder = new StringBuilder();
String line = reader.readLine();
while (line != null) {
builder.append(line);
line = reader.readLine();
}
reader.close();
String reqBody = builder.toString();
Req req = JSON.parseObject(reqBody, Req.class);
log.info("req: {}, request: {}", req, request.getParameterMap());
return new BaseRsp<>(req);
}
驗證如下
其實到這里,有個有意思的地方已經引起了我的好奇,那就是在Spring容器中HttpServletRequest這個東西,是怎么運轉的,后面有機會再聊,此處不展開...
4. 小結
- ReuqestBody 主要是處理json串格式的請求參數,要求使用方指定header
content-type:application/json
- RequestBody 通常要求調用方使用post請求
- RequsetBody參數,不會放在HttpServletRequest的Map中,因此沒法通過
javax.servlet.ServletRequest#getParameter
獲取
II. 其他
0. 參考
1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 聲明
盡信書則不如,已上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
- 微博地址: 小灰灰Blog
- QQ: 一灰灰/3302797840
3. 掃描關注
小灰灰Blog&公眾號
知識星球