SpringMVC中處理請求參數有好幾種不一樣的方式,如咱們常見的下面幾種html
HttpServletRequest
對象獲取@PathVariable
註解獲取url參數@RequestParam
註解獲取請求參數@ModelAttribute
註解獲取請求參數對上面幾種方式有興趣的能夠看一下這篇博文: SpringMVC之請求參數的獲取方式前端
除了上面的幾種方式以外,還有一種 @RequestBody
的使用方式,本文則主要介紹這種傳參的使用姿式和相關注意事項java
藉助Spring框架,使用@RequestBody
並無什麼難度,很簡單的就能夠寫一個使用case出來,以下git
@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
還少github
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestBody { // 默認參數必須存在,不然會拋一個異常 boolean required() default true; }
看到上面的實現,估計也能夠猜出,這個註解對於後端而言,寫沒啥問題,關鍵是如何用(具體來說是如何給前端用)json
上面寫完了,接下來的重點就是如何使用了,在使用以前,有必要了解下 RequestBody
這個註解出現的原有以及應用場景(換句話說它和RequestParam有什麼區別,爲何要單獨的搞一個這個東西出來)後端
RequestBody瀏覽器
@requestBody註解經常使用來處理content-type不是默認的application/x-www-form-urlcoded編碼的內容,好比說:application/json或者是application/xml等。通常狀況下來講經常使用其來處理application/json類型。服務器
在進入下一步以前,有必要說一下Content-Type
這個http請求頭的做用了,下面一段來自其餘博文,原文連接見最後app
MediaType,便是Internet Media Type,互聯網媒體類型;也叫作MIME類型,在Http協議消息頭中,使用Content-Type來表示具體請求中的媒體類型信息。
常見媒體格式以下:
以application開頭的媒體格式類型:
上面算是基本定義和取值,下面結合實例對典型的幾種方式進行說明
對於前端使用而言,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串,和前面差異明顯
根據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
返回結果以下
根據前面的說明,能夠知道 @RequestBody
這個註解的使用,使得REST接口接收的再也不content-type爲application/x-www-form-urlencoded
的請求, 反而須要顯示指定爲application/json
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參數的問題了,經過測試,發如今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這個東西,是怎麼運轉的,後面有機會再聊,此處不展開...
content-type:application/json
javax.servlet.ServletRequest#getParameter
獲取一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
小灰灰Blog&公衆號
知識星球