SpringMVC自定义配置消息转换器踩坑总结

问题描述

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

时间: 2024-10-21 19:37:13

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

SpringMVC源码 —— 消息转换器HttpMessageConverter

SpringMVC使用消息转换器实现请求报文和对象.对象和响应报文之间的自动转换 概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制. Http请求的抽象 还是回到请求-响应,也就是解析请求体,然后返回响应报文这个最基本的Http请求过程中来.我们知道,在servlet标准中,可以用ja

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

安装 Oracle 安装 Oracle11g 32位[Oracle 32位的话,OGG 也必须是 32位,否则会有0xc000007b无法正常启动 错误] 安装目录为 D:\oracle\product\11.1.0\db1 [这个目录要设置为 环境变量 ORACLE_HOME] 设置环境变量 JAVAHOME C:\Program Files\Java\jdk1.8.0121 ORACLEHOME D:\oracle\product\11.1.0\db1 ORACLE_SID ORCL 如何查

springmvc-自定义消息转换器

最近的项目没有用到这个,先把自己自学跑通的例子先帖出来,供自己以后参考吧! 如有不对地方望指出! 一.自定义类实现AbstractHttpMessageConverter 1 package com.dzf.converter; 2 3 import java.io.IOException; 4 import java.nio.charset.Charset; 5 6 import org.springframework.http.HttpInputMessage; 7 import org.s

SpringMVC源码剖析5:消息转换器HttpMessageConverter与@ResponseBody注解

转自 SpringMVC关于json.xml自动转换的原理研究[附带源码分析] 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Spring源码解析 https://blog.csdn.net/column/details/21851.html 部分代码会放在我的的Github:https://github.com/h2pl/ 目录 前言 现象 源码分析 实例讲解 关于配置 总结 参考资料 前言 SpringMVC是目前主流的Web MVC

SpringMVC消息转换器HttpMessageConverter

摘要: SpringMVC使用消息转换器实现请求报文和对象.对象和响应报文之间的自动转换 概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制. Http请求的抽象 还是回到请求-响应,也就是解析请求体,然后返回响应报文这个最基本的Http请求过程中来.我们知道,在servlet标准中,可

SpringMVC——消息转换器HttpMessageConverter(转)

文章转自http://blog.csdn.net/cq1982/article/details/44101293 概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Spring3.x中新引入的HttpMessageConverter即消息转换器机制. Http请求的抽象 还是回到请求-响应,也就是解析请求体,然后返回响应报文这个最基本的Http请求过程中来.我们知道,在

SpringMVC源码剖析(五)-消息转换器HttpMessageConverter

SpringMVC源码剖析(五)-消息转换器HttpMessageConverter 目录[-] 概述 Http请求的抽象 HttpInputMessage HttpOutputMessage HttpMessageConverter RequestResponseBodyMethodProcessor 思考 概述 在SpringMVC中,可以使用@RequestBody和@ResponseBody两个注解,分别完成请求报文到对象和对象到响应报文的转换,底层这种灵活的消息转换机制,就是Sprin

Win10 安装配置 MongoDB 4.0 踩坑记

redis 官方没有 Windows 版的,微软维护的已经好久没更新了,所以就在想着换成 MongoDB. 于是一趟被我复杂化的踩坑之旅就开始了,同时也记录一下,避免有人遇见跟我一样的问题. 首先在 官网 上下载 msi 安装包开始安装.   我不喜欢把软件装在系统盘,所以我选择 Custom,自己选要装在哪里,然后就跳出来下面这张图.针对下面这张图我翻译一下官方文档上的内容作为解释.  从 MongoDB 4.0 开始,默认情况下,你可以在安装期间配置和启动 MongoDB 作为服务,并在成功

记一次 Spring 事务配置踩坑记

记一次 Spring 事务配置踩坑记 问题描述:(SpringBoot + MyBatisPlus) 业务逻辑伪代码如下.理论上,插入数据 t1 后,xxService.getXxx() 方法的查询条件会不满足,会查询不到数据.结果事与愿违,后一次的查询,居然查到了数据. void saveXxx(){  xxService.getXxx(); // 查到一条数据 data1  xxService.insert(); // 插入一条数据 t1  xxService.getXxx(); // 查到