1. 程式人生 > >@RequestBody與Content-type

@RequestBody與Content-type

1、簡介

Spring Web MVC 是一種基於Java的實現了Web MVC設計模式的請求驅動型別的輕量級Web框架,自Spring MVC出現以來,Java服務端開發逐漸採用Spring MVC編寫Http介面。今天主要跟大家分享一個 @RequestBodyapplication/x-www-form-urlencoded 同時使用時遇到的坑。

2、問題描述

要傳送的HTTP請求:

curl --header 'content-type:application/x-www-form-urlencoded;charset=UTF-8' -d 'msgObject=string&msgTitle=string&msgContent=string&msgLevel=string&msgSource=string&msgSenders=string&msgReceivers=string' 'http://127.0.0.1:8080/service/send'

服務端Controller:

@RequestMapping(value = "/alarm/send", method = RequestMethod.POST)
public ResponseEntity<String> sendAlarm1(@RequestBody MessageAlarmReq req) {
    boolean success = false;
    System.out.println(req);
    return new ResponseEntity<String>(HttpStatus.OK);
}

其中,MessageAlarmReq定義:

@Data
public class MessageAlarmReq {
    private String msgObject;
    private String msgTitle;
    private String msgContent;
    private String msgLevel;
    private String msgSource;
    private String msgSenders;
    private String msgReceivers;
}

本希望Spring MVC將body對映為MessageAlarmReq物件,但實際上得到了:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded' not supported

3、問題分析

Spring 3.X系列增加了新註解 @ResponseBody@RequestBody@RequestBody 將HTTP請求正文轉換為適合的HttpMessageConverter物件。 Spring MVC 使用HttpMessageConverter將請求物件轉化為我們希望的格式,當我們在方法中加入@RequestBody註解,Spring MVC固定使用RequestMappingHandlerAdapter來解析,其中RequestMappingHandlerAdapter支援的HttpMessageConverter有四種:

ByteArrayHttpMessageConverter:轉化 byte arrays. StringHttpMessageConverter:轉化 Strings. FormHttpMessageConverter:轉化 form data to/from a MultiValueMap. SourceHttpMessageConverter:轉化 to/from a javax.xml.transform.Source.

通過分析這四個Converter可知,FormHttpMessageConverter支援解析application/x-www-form-urlencoded這種格式:

private List<MediaType> supportedMediaTypes = new ArrayList();
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);

其中:
MediaType APPLICATION_FORM_URLENCODED = valueOf("application/x-www-form-urlencoded");
MediaType MULTIPART_FORM_DATA = valueOf("multipart/form-data");

瞭解HttpMessageConverter原理可知,HttpMessageConverter使用:

T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;

將引數轉化為我們希望的物件,FormHttpMessageConverter的實現方式為:

public MultiValueMap<String, String> read(@Nullable Class<? extends MultiValueMap<String, ?>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException{ ...... };

由此,我們可以知道,FormHttpMessageConverter只能將requestBody轉化為MultiValueMap,而不能是自定義物件。

4、解決方案

方案一、

將 @RequestBody 引數設定為 MultiValueMap,即 Controller定義修改為:

@RequestMapping(value = "/alarm/send", method = RequestMethod.POST)
public ResponseEntity<String> sendAlarm1(@RequestBody MultiValueMap<String, String> req) {
    boolean success = false;
    System.out.println(req);
    return new ResponseEntity<String>(HttpStatus.OK);
}

這樣就能符合FormHttpMessageConverter的要求。

方案二、

使用ModelAttribute:

@RequestMapping(value = "/alarm/send", method = RequestMethod.POST)
public ResponseEntity<String> sendAlarm1(@ModelAttribute("alarmReq") MessageAlarmReq req)
 { ...... }

@ModelAttribute("alarmReq")
public MessageAlarmReq getMessageAlarmReq() {
    return new MessageAlarmReq();
}

利用ModelAttribute進行物件對映,參考文獻

5、相關問題

經過測試我發現假如程式碼中直接刪除@RequestBody,也可以實現 RequestBody 直接對映為自定義物件:

@RequestMapping(value = "/alarm/send", method = RequestMethod.POST)
public ResponseEntity<String> sendAlarm1(MessageAlarmReq req) {
    boolean success = false;
    System.out.println(req);
    return new ResponseEntity<String>(HttpStatus.OK);
}

此處暫時還沒找到具體原因,因為專案用的是Spring Boot 2.0,推測有可能是Spring Boot做的一些優化。

6、總結

通過上述分析,我們看到在我們使用@RequestBody,享受其便利,增大方法可讀性時,無形中也受到RequestMappingHandlerAdapter中HttpMessageConverter的限制,對此個人建議:

  1. 針對新編寫的介面,優先使用@RequestBody,前後端傳參時儘量使用json格式,增加程式碼可讀性;
  2. 為相容老版本介面,不要再使用@RequestBody,推薦使用@ModelAttribute,充分利用Spring MVC自帶Converter的引數對映,減少程式碼邏輯;
  3. 如果專案中有很多Content-Type:application/x-www-form-urlencoded的情況,推薦擴充套件HttpMessageConverter,自己來編碼處理物件對映;
  4. 不推薦 5 中描述的直接刪除@RequestBody的方式,因為目前具體原因不明,不排除未來有其他坑的可能。

作者:沈淵 連結:https://www.jianshu.com/p/6a83b73060bb 來源:簡書 簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。