最近在開發時候碰到一個問題,springmvc頁面向後臺傳數據的時候,一般我是這樣處理的,在前臺把數據打成一個json,在後臺接口中使用@requestbody定義一個對象來接收,可是此次數據傳不過去,報400的錯誤,緣由也很容易想到,該對象有一個屬性也是一個對象,屬性對象是用抽象類定義的,他有幾個具體實現,具體實現中的字段都是不同的,springmvc是不會自動識別並注入你使用的是哪個實現類的.因此沒法傳過來.html
傳遞對象以下:前端
@Data public class ActivityRule { ...private RuleDetail ruleDetail;//注意這裏的RuleDetail是一個抽象類 ... }
使用自定義消息轉換器,首先讓咱們來了解一下spring的消息轉換器ajax
咱們都使用過@RequestBody和@ResponseBody這兩個註解,他們的做用就是在前臺向後臺傳遞數據時,把請求報文中的數據經過springmvc的處理成一個咱們本身定義的對象,在這個過程當中首先springmvc會去請求頭中找到一個contentType的屬性,而後去匹配可以處理這種類型的消息轉換器,而在返回數據時,再把對象轉換成響應報文.spring
介紹一下contentType屬性:json
contentType是requestHeader中的一個屬性,這個頭部主要用於說明body中的字符串是什麼格式的,好比:text,json,xml,html等。springmvc解析請求時,首先經過此頭部,才能肯定使用什麼格式來解析請求體中的字符串,對於響應報文,瀏覽器也是要經過這個屬性,來肯定在如何處理響應報文的返回數據。介紹一下@RequestBody/@ResponseBody註解當用該註解標註一個對象時,在請求過程當中進行數據映射時,spring會根據Request對象header部分的content-Type類型,逐一匹配合適的HttpMessageConverter來讀取數據,而在響應時,spring會根據Request對象header部分的Accept屬性(逗號分隔),逐一按accept中的類型,去遍歷找到能處理的HttpMessageConverter.
到這裏咱們就有了一種思路,能不能讓咱們來接管請求報文到對象映射這個過程,只要咱們獲得了json字段,根據內容咱們就知道須要去映射哪一個類,至此,咱們有了思路就能夠去實現了,咱們能夠經過spring的消息轉換器來實現咱們的想法,瀏覽器
默認狀況下,spring使用HttpMessageConverter來負責將請求信息轉換爲一個對象(類型爲 T),而且將對象(類型爲 T)輸出爲響應信息,若是自定義咱們本身的消息轉換器,則須要新建一個類,繼承mvc
AbstractHttpMessageConverter<T>,下面看我針對上面的ActivityRule對象定義的一個消息轉換器.app
/** * 自定義消息轉換器 ActivityRule * @author yogo.wang * @date 2017/10/25-下午5:43. */ public class RuleMessageConverter extends AbstractHttpMessageConverter<ActivityRule> { public RuleMessageConverter(){ super(new MediaType("application","x-rule", Charset.forName("UTF-8"))); } @Override protected boolean supports(Class<?> clazz) { return ActivityRule.class.isAssignableFrom(clazz); } @Override protected ActivityRule readInternal(Class<? extends ActivityRule> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { String temp= StreamUtils.copyToString(inputMessage.getBody(),Charset.forName("UTF-8")); Map<String,Object> map = (Map<String,Object>)JSON.parse(temp); RuleType ruleType = RuleType.valueOf((String)map.get("ruleType")); String ruleDetail = StringUtils.substringAfter(temp, "ruleDetail\":"); ruleDetail=ruleDetail.substring(0,ruleDetail.length()-1); ActivityRule rule=new ActivityRule(); rule.setName((String)map.get("name")); rule.setRuleType(ruleType); switch (ruleType){ case LOGIN: rule.setRuleDetail(JSON.parseObject(ruleDetail, LoginRuleDetail.class)); break; case ROLE_UPGRADE: rule.setRuleDetail(JSON.parseObject(ruleDetail, UpgradeRuleDetail.class)); break; case PAY_AMOUNT: rule.setRuleDetail(JSON.parseObject(ruleDetail, RechargeRuleDetail.class)); break; default: rule.setRuleDetail(null); } return rule; } @Override protected void writeInternal(ActivityRule activityRule, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { } }
在上面的類中,在繼承抽象類AbstractHttpMessageConverter時,咱們將泛型指定爲@RequestBody標註的了類,即ActivityRule類,而後在該類中的構造器中,咱們建立了一個新的媒體類型"x-rule",名稱能夠自定義,而且指定相應的編碼方式,通常都是utf-8。在重寫的support()方法中,咱們來判斷所支持的Class是否與ActivityRule的Class相同,只有相同,纔會走下面的方法readInternal,在這個方法裏,咱們就須要從請求頭裏拿到json字符串,而後本身手動將json映射成對象.ide
寫完這個類還沒完,還有兩步操做是必須的,第一,在spring的配置文件將消息轉換器配置上,以下:測試
<mvc:annotation-driven> <mvc:message-converters> <bean class="com.ximalaya.cms.games.operation.activity.service.converter.RuleMessageConverter"> <property name="supportedMediaTypes"> <list> <value>application/json</value> <value>application/x-rule</value> </list> </property> </bean> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven>
第二,在controller接口中,須要手動指定哪一個接口能夠接收咱們自定義的媒體類型.以下:
/** * 保存規則對象 * @param rule * @return */ @RequestMapping(method = RequestMethod.POST,produces = { "application/x-rule"}) @ResponseBody public ResponseMessage save(@RequestBody ActivityRule rule) { LOG.info("begin to save ActivityRule:{}",rule); try{ ruleService.saveAvtivityRule(rule); return ResponseMessage.ok(); }catch (Exception e){ return ResponseMessage.fail(e.getMessage()); } }
以上操做完成後,在我測試的時候,踩了兩個坑,須要特別說明一下.在我運行時,數據仍是過不來,報415的錯誤,說不支持的媒體類型,後來發如今前端的Ajax調用中,發現contentType沒改,改後以下:
$.ajax({ method: $form.attr('method'), traditional: true, url: $form.attr('action'), data: JSON.stringify(rule), contentType: "application/x-rule", dataType:"json", success: function (ret) { //,...... }, error: function (message) { alert('ERROR:' + JSON.stringify(message)); } });
再次運行,沒問題,json字段如願映射成了咱們想要的對象,但在前端返回的時候,仍然有錯誤,報406,根據網上的解決方案,說是缺乏fastjson相關包,因而引入了相關jar報,仍是沒解決,卡了大半天,終,修改了一下@RequestMapping()的內容,神奇的解決了問題,以下:
/** * 保存規則對象 * @param rule * @return */ @RequestMapping(method = RequestMethod.POST,produces = { "application/x-rule","application/json"}) @ResponseBody public ResponseMessage save(@RequestBody ActivityRule rule) { LOG.info("begin to save ActivityRule:{}",rule); try{ ruleService.saveAvtivityRule(rule); return ResponseMessage.ok(); }catch (Exception e){ return ResponseMessage.fail(e.getMessage()); } }
我猜想,緣由多是這樣的,因爲設置了@ResponseBody,要把對象轉換成json格式,可是注意看個人代碼,@ResponseBody標註的類是ResponseMessage,不是ActivityRule!!!!,而coontentType被我設成了application/x-rule,因此在返回的時候,仍然走了我自定義的那個消息轉換器,而兩個類確定是不一樣的,support返回了false,而我添加了application/json之後,ResponseMessage對象就沒有走我自定義的消息轉換器,而是以json的contentType進入了spring的默認消息轉換器,而且成功映射到響應體中.