1、簡介
Spring Web MVC 是一種基于Java的實現(xiàn)了Web MVC設(shè)計模式的請求驅(qū)動類型的輕量級Web框架,自Spring MVC出現(xiàn)以來,Java服務(wù)端開發(fā)逐漸采用Spring MVC編寫Http接口。今天主要跟大家分享一個 @RequestBody 與 application/x-www-form-urlencoded 同時使用時遇到的坑。
2、問題描述
要發(fā)送的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'
服務(wù)端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請求正文轉(zhuǎn)換為適合的HttpMessageConverter對象。
Spring MVC 使用HttpMessageConverter將請求對象轉(zhuǎn)化為我們希望的格式,當(dāng)我們在方法中加入@RequestBody注解,Spring MVC固定使用RequestMappingHandlerAdapter來解析,其中RequestMappingHandlerAdapter支持的HttpMessageConverter有四種:
ByteArrayHttpMessageConverter:轉(zhuǎn)化 byte arrays.
StringHttpMessageConverter:轉(zhuǎn)化 Strings.
FormHttpMessageConverter:轉(zhuǎn)化 form data to/from a MultiValueMap.
SourceHttpMessageConverter:轉(zhuǎn)化 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;
將參數(shù)轉(zhuǎn)化為我們希望的對象,FormHttpMessageConverter的實現(xiàn)方式為:
public MultiValueMap<String, String> read(@Nullable Class<? extends MultiValueMap<String, ?>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException{ ...... };
由此,我們可以知道,F(xiàn)ormHttpMessageConverter只能將requestBody轉(zhuǎn)化為MultiValueMap,而不能是自定義對象。
4、解決方案
方案一、
將 @RequestBody 參數(shù)設(shè)置為 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進(jìn)行對象映射,參考文獻(xiàn)
5、相關(guān)問題
經(jīng)過測試我發(fā)現(xiàn)假如代碼中直接刪除@RequestBody,也可以實現(xiàn) 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做的一些優(yōu)化。
6、總結(jié)
通過上述分析,我們看到在我們使用@RequestBody,享受其便利,增大方法可讀性時,無形中也受到RequestMappingHandlerAdapter中HttpMessageConverter的限制,對此個人建議:
- 針對新編寫的接口,優(yōu)先使用@RequestBody,前后端傳參時盡量使用json格式,增加代碼可讀性;
- 為兼容老版本接口,不要再使用@RequestBody,推薦使用@ModelAttribute,充分利用Spring MVC自帶Converter的參數(shù)映射,減少代碼邏輯;
- 如果項目中有很多Content-Type:application/x-www-form-urlencoded的情況,推薦擴(kuò)展HttpMessageConverter,自己來編碼處理對象映射;
- 不推薦 5 中描述的直接刪除@RequestBody的方式,因為目前具體原因不明,不排除未來有其他坑的可能。