最近在开发时候碰到一个问题,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的默认消息转换器,而且成功映射到响应体中.