问题描述

  最近在开发时候碰到一个问题,springmvc页面向后台传数据的时候,通常我是这样处理的,在前台把数据打成一个json,在后台接口中使用@requestbody定义一个对象来接收,但是这次数据传不过去,报400的错误,原因也很容易想到,该对象有一个属性也是一个对象,属性对象是用抽象类定义的,他有几个具体实现,具体实现中的字段都是不一样的,springmvc是不会自动识别并注入你使用的是哪一个实现类的.所以无法传过来.

传递对象如下:

@Data
public class ActivityRule {
...private RuleDetail ruleDetail;//注意这里的RuleDetail是一个抽象类
...
}

  解决方案:

  使用自定义消息转换器,首先让我们来了解一下spring的消息转换器

springmvc的消息转换器(HTTPMessageConverter)

  我们都使用过@RequestBody和@ResponseBody这两个注解,他们的作用就是在前台向后台传递数据时,把请求报文中的数据通过springmvc的处理成一个我们自己定义的对象,在这个过程中首先springmvc会去请求头中找到一个contentType的属性,然后去匹配能够处理这种类型的消息转换器,而在返回数据时,再把对象转换成响应报文.

介绍一下contentType属性:

  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)输出为响应信息,如果自定义我们自己的消息转换器,则需要新建一个类,继承

AbstractHttpMessageConverter<T>,下面看我针对上面的ActivityRule对象定义的一个消息转换器.

/**
* 自定义消息转换器 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映射成对象.

  写完这个类还没完,还有两步操作是必须的,第一,在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的默认消息转换器,并且成功映射到响应体中.

SpringMVC自定义配置消息转换器踩坑总结的更多相关文章

  1. SpringBoot使用logback自定义配置时遇到的坑 --- 在 /tmp目录下自动生成spring.log文件

    问题描述 SpringBoot项目使用logback自定义配置后,会在/tmp/ 目录下生成 spring.log的文件(如下图所示). 解决方案 通过各种资料的搜索,最终发现问题的所在(logbac ...

  2. SpringMVC 自定义全局日期转换器

    第一步: 编写自定义转换器的类 /* * 自定义日期转换器 */ public class CustomDateConverter implements Converter<String, Da ...

  3. 记一次线上Kafka消息堆积踩坑总结

    2018年05月31日 13:26:59 xiaoguozi0218 阅读数:2018更多 个人分类: 大数据   年后上线的系统,与其他业务系统的通信方式采用了第三代消息系统中间件Kafka.由于是 ...

  4. springmvc restful配置有一个小小的坑坑

    首先web.xml配置 <!-- spring-mvc --> <servlet> <servlet-name>springServlet</servlet- ...

  5. 『OGG 01』Win7 配置 Oracle GoldenGate 踩坑指南

    安装 Oracle 安装 Oracle11g 32位[Oracle 32位的话,OGG 也必须是 32位,否则会有0xc000007b无法正常启动 错误] 安装目录为 D:\oracle\produc ...

  6. 多页应用 Webpack4 配置优化与踩坑记录

    前言 最近新起了一个多页项目,之前都未使用 webpack4 ,于是准备上手实践一下.这篇文章主要就是一些配置介绍,对于正准备使用 webpack4 的同学,可以做一些参考. webpack4 相比之 ...

  7. PX4配置过程与踩坑

    0.前言 由于需要在GitHub下载代码,而国内访问受限,可能会出现一些问题,这里建议使用github国内镜像,参看:GitHub国内镜像网站,当然下面会给出具体解决方案. 1.步骤 1.1下载源码: ...

  8. VirtualBox 下 CentOS7 静态 IP 的配置 → 多次踩坑总结,蚌埠住了!

    开心一刻 一个消化不良的病人向医生抱怨:我近来很不正常,吃什么拉什么,吃黄瓜拉黄瓜,吃西瓜拉西瓜,怎样才能恢复正常呢? 医生沉默片刻:那你只能吃屎了 环境准备 VirtualBox 6.1 网络连接方 ...

  9. 腾讯通消息webSDK踩坑

    1.腾讯通提供一个通过http协议的接口,可用于发送消息,公告等功能,要使用其功能首先要开启RTX_HTTPServer服务. 2.阅读文档http://rtx.tencent.com/sdk/,为了 ...

随机推荐

  1. 云计算之阿里仓库停止openstack mitaka源报错“No package centos-release-openstack-mitaka available.”

    之前学习了一个月的openstack的mitaka版本,写完脚本放置一段时间,最近准备正式部署突然发现 No package centos-release-openstack-mitaka avail ...

  2. 最详细的PHP flush()与ob_flush()的区别详解

    buffer ---- flush()buffer是一个内存地址空间,Linux系统默认大小一般为4096(1kb),即一个内存页.主要用于存储速度不同步的设备或者优先级不同的 设备之间传办理数据的区 ...

  3. 调用惯例Calling Convention (或者说:调用约定)

    调用惯例影响执行效率,参数的传递方式以及栈清除的方式.   调用惯例 参数传递顺序 谁负责清除参数 参数是否使用暂存器 register 从左到右 被调用者 是 pascal 从左到右 被调用者 否 ...

  4. URL编码解决

    与其他系统对接时遇到的问题URL中传递认证码,URL默认只允许传递ASCII码中的数据,所以浏览器默认会进行一次编码将%等特殊符号转义后台web服务器收到URL中的参数,会默认进行一次解码,但遇到的问 ...

  5. .NET十年回顾

    一.   引子 从我还是编程菜鸟时起,.NET就从来没让我失望过.总是惊喜不断. 当年我第一个项目是做个进销存.用的Winform.当时我是机电工程师.编程只是业余心血来潮而已. .NET的低门槛.V ...

  6. Cow Exhibition 变种背包

    Cow Exhibition Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Subm ...

  7. LCM Cardinality 暴力

    LCM Cardinality Time Limit:3000MS     Memory Limit:0KB     64bit IO Format:%lld & %llu Submit St ...

  8. ZOJ3541 The Last Puzzle

    这道题是宁波集训的那道题,讲课时轻描淡写吧(应该是我听课不认真罢了),所以这样就要靠自己的理解了, dp[i][j][0]表示从左端点开始完成整个区间的最小花费dp[i][j][1]表示从右端点开始完 ...

  9. 架构师之路-在Dubbo中开发REST风格的远程调用

    架构师之路:从无到有搭建中小型互联网公司后台服务架构与运维架构 http://www.roncoo.com/course/view/ae1dbb70496349d3a8899b6c68f7d10b 概 ...

  10. day01:study HTTP协议

    总结: 1.对web客户端和web服务器之间的通讯有了基本原理有了简单理解. 2.对http协议有了相关概念的建立 3.B/S C/S 两种形式 4.搭建tomcat服务器的环境,相关配置(虚拟目录 ...