详细聊聊RabbitMQ竟无法反序列化List问题

目录
  • 前言
  • 问题重现
    • 项目依赖
    • 发送方
    • 接收方
  • 错误日志
  • 分析问题原因
  • 解决办法
  • 总结

前言

最近在接到了一个需求,大概是通过RabbitMq给xx子系统同步用户数据,要提供单个同步和批量同步。内心暗喜这不简单的很嘛。三下五除二就把代码给写完了,大概长这样:

public void syncUserSingle(User user) {
    // 省略一大堆业务代码
    rabbitTemplate.convertAndSend("q_sync_user_single", user);
}

public void syncUserBatch(List<User> userList) {
    // 省略一大堆业务代码
    rabbitTemplate.convertAndSend("q_sync_user_batch", userList);
}

但是在联调的过程中,遇到了一个比较奇葩的问题。单个用户进行同步时,子系统可以正常消费。然后进行批量同步的时候,子系统报错了。并抛出java.lang.ClassCastException提示 LinkedHashMap cannot xxxx class 。于是负责子系统的哥们笑嘻嘻的(表面笑嘻嘻)走过来对我说,不是约定List 为啥发个Map过来?

看到这个错误,着实让我摸不到头脑。顿时一堆疑问涌上心头, 为啥单个对象可以,List就不行呢?我发的是List 数据,为啥变成Map了?虽然一大堆疑问,但是只能笑嘻嘻的说,我检查一下哈。

问题重现

项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/>
    </parent>
    <!-- 省略部分信息 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>
</project>

发送方

初始化队列

@Configuration
public class QueueConfig {
    @Bean
    public Queue test() {
        return new Queue("test");
    }
}

配置RabbitTemplete

@Configuration
public class RabbitTemplateConfig {
    @Autowired
    public RabbitTemplateConfig(RabbitTemplate rabbitTemplate) {
        // 设置Json消息转换器
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
    }
}

发送接口

@Controller
@RequestMapping("/test")
public class TestController {

    @Resource
    private RabbitTemplate template;

    @GetMapping("/send")
    public void send() {
        template.convertAndSend("test", Collections.singletonList(new User(20, "不一样的科技宅")));
    }
}

User类

@Data
@AllArgsConstructor
public class User {
    /**
     * 年龄
     */
    private Integer age;

    /**
     * 姓名
     */
    private String name;
}

接收方

监听配置

@Configuration
public class RabbitListenerConfig {

    @Bean
    public SimpleRabbitListenerContainerFactory customFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
                                                              ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        // 设置消息转换器
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        configurer.configure(factory, connectionFactory);
        return factory;
    }

}

接收方

@Service
public class UserService {

    public void save(List<User> userList) {
        userList.forEach(System.out::println);
    }

}
@Componentpublic class Receiver {    @Resource    private UserService userService;    @RabbitListener(queues = "test", containerFactory = "customFactory")    public void receive(@Payload List<User> msg) {        userService.save(msg);    }}

错误日志

❝好家伙果然失败了,这百分百必现的bug呀。❞

分析问题原因

首先错误信息是在消费端抛出来的,按理应该是消费端出问题概率较大。但是如果和他说的一样,我生产端发送的消息就是错误的,从而导致消费端出问题呢?这对这个疑问,我先断开消费端,然后发送一条消息,并通过Rabbitmq的管控台来查看消息的内容是否正确。

消息内容如下图所示:

通过上图可以发现,消息体(payload)是一个标准的json串,并且TypeId也是List,并不是错误信息中的LinkedHashMap。哈哈哈,到此可以石锤是消费端反序列化的问题了。赶紧把锅甩出去,抽他呀的(自嗨而已),我写的代码怎么可能有bug。

对我爱学习的我,肯定不愿意就这样算了。必须刨根问底,给他上一课。于是我在google一圈发现这竟然是这个bug。有个老哥也发现了,并提交了一个issues: spring-ampq/issues/1279。

大致是说:尝试从 Spring Boot 2.3.1 升级到 2.3.3,然后再升级到 2.3.6。错误信息依然是:List<Foo> foos是LikedHashMap,而不是Foo对象。并通过远程调试确认了这种情况。出于某种原因,他认为没有正确使用泛型类型。恢复到 Spring-AMQP 2.2.7 使它再次工作,并且对象确实是Foo。

然后garyrussell这个人说:他们添加了对抽象类反序列化的支持,如果配置不正确,这会对消息转换器产生一些副作用。然后调查了一下,确认这是一个错误。是由于List是抽象的,新代码认为它不能反序列化。

解决方法是:

converter.setAlwaysConvertToInferredType(true);

后面还提到在 GH-1729: Fix JSON Regression修复这个问题,修复的代码如下:

通过阅读代码发现,修改前的逻辑是: 如果推断类型是抽象的,则返回false也就代表不能转换成推断类型。然后被转换成LinkedHashMap。这也就是出现 LinkedHashMap cannot cast xxxx class的主要原因。

修改后变成了:如果推断类型是抽象的并且不是容器类型,返回false。也就意味着,虽然推断类型是抽象的,但是如果是容器类型,并且容器内的对象不是抽象的,则可以被转换。这样一来避免了上述问题的产生了。

前面还提到了通过增加配置来解决。解决起来就相对简单粗暴了,始终转换推断类型。

解决办法

到此问题分析完毕,简单总结一下解决方法。主要有两种:

1、在消费端开启如下配置即可:

// 始终转换推断类型
converter.setAlwaysConvertToInferredType(true);

2、升级版本:由于GH-1729: Fix JSON Regression合并到了2.2.13.RELEASE。所以只需要将 spring-amqp 升级到 2.2.13.RELEASE 或以上。或者升级SpringBoot版本到2.3.7.RELEASE。

总结

到此这篇关于RabbitMQ竟无法反序列化List的文章就介绍到这了,更多相关RabbitMQ无法反序列化List内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详细聊聊RabbitMQ竟无法反序列化List问题

    目录 前言 问题重现 项目依赖 发送方 接收方 错误日志 分析问题原因 解决办法 总结 前言 最近在接到了一个需求,大概是通过RabbitMq给xx子系统同步用户数据,要提供单个同步和批量同步.内心暗喜这不简单的很嘛.三下五除二就把代码给写完了,大概长这样: public void syncUserSingle(User user) { // 省略一大堆业务代码 rabbitTemplate.convertAndSend("q_sync_user_single", user); } p

  • 详细聊聊关于sql注入的一些零散知识点

    目录 零.本文涉及知识点 一.sqlmap写一句马的过程(-- os-shell) 1.1 简述过程 1.2 一个小问题 二.堆叠注入: 2.1 什么是堆叠注入 2.2 如何判断存在堆叠注入? 2.3 局限性 三.union injection(联合注入) 3.1 原理 3.2 与堆叠注入的区别 四.常见的sql注入绕过姿势 4.1 Waf特性: 4.2 绕waf的核心思路: 4.3 常见的思路 五.Sql注入预编译与常见绕过姿势 5.1 概述 5.2 具体方式 5.2.1 ASC/DESC 5

  • 详细聊聊SpringBoot中动态切换数据源的方法

    其实这个表示有点不太对,应该是 Druid 动态切换数据源的方法,只是应用在了 springboot 框架中,准备代码准备了半天,之前在一次数据库迁移中使用了,发现 Druid 还是很强大的,用来做动态数据源切换很方便. 首先这里的场景跟我原来用的有点点区别,在项目中使用的是通过配置中心控制数据源切换,统一切换,而这里的例子多加了个可以根据接口注解配置 第一部分是最核心的,如何基于 Spring JDBC 和 Druid 来实现数据源切换,是继承了org.springframework.jdbc

  • 详细聊聊浏览器是如何看闭包的

    目录 前言 闭包简介 如何判别内存垃圾 闭包的内存表示 结语 前言 闭包,是javascript的一大理解难点,网上关于闭包的文章也很多,但是很少有能让人看了就彻底明白的文章.究其原因,我想是因为闭包涉及了一连串的知识点.只有把这一连串的知识点都理解透彻,实现一个概念的闭环,才可以真正理解它.今天打算换个角度来理解闭包,从内存分配与回收的角度阐述,希望能帮助你真正消化掉所看到的闭包知识,同时也希望本文是你看的最后一篇关于闭包的文章. 大家看本文中的配图时,请牢记箭头的指向.因为它是根对象wind

  • 详细聊聊Vue生命周期的那些事

    目录 前言 一.Vue2中的生命周期 实例的生命周期 其它生命周期钩子 二.Vue3中的生命周期 Options API生命周期 Composition API生命周期 两个新的Vue3生命周期函数 最后 前言 如今学习Vue的人越来越多了,Vue框架或React框架的学习也成为开发了前端开发人员的必备技能,今天我们就来聊聊Vue中的生命周期函数,Vue中生命周期函数的参考价值很高,让我们来一起认识它吧~ 一.Vue2中的生命周期 实例的生命周期 在介绍生命周期之前,我们需要知道在Vue中要渲染

  • 聊聊RabbitMQ发布确认高级问题

    目录 1.发布确认高级 1.1.发布确认SpringBoot版本 1.1.1.确认机制方案 1.1.2.代码架构图 1.1.3.配置文件 1.1.4.配置类 1.1.5.回调接口 1.1.6.生产者 1.1.7.消费者 1.1.8.测试结果 1.2.回退消息 1.2.1.Mandatory参数 1.2.2.配置文件 1.2.3.生产者代码 1.2.4.回调接口代码 1.2.5.测试结果 1.3.备份交换机 1.3.1.代码架构图 1.3.2.配置类代码 1.3.3.消费者代码 1.3.4.测试结

  • 详细聊聊sql中exists和not exists用法

    目录 exists: exists 和in 的区别 not exists详细介绍: 附案例分析 总结 之所以要说这个问题,是因为项目中用到了not exists,但两者写的语句只有一点差别,结果一个有问题了,一个没问题.具体问题下面详细说明,先来看看exists如何应用. exists: 强调的是是否有返回集,不需知道具体返回的是什么,比如: SELECT * FROM customer WHERE not EXISTS ( SELECT 0 FROM customer_goods WHERE

  • 详细聊聊vue组件是如何实现组件通讯的

    目录 前言 如何解决组件之间通讯呢? 解决方案: 父传子 实现过程: 原理图示 父组件 Footer.vue 子组件 MyCon.vue 小案例 采用了父传子 父组件 App.vue 子组件 MyProduct.vue 效果展示 子传父 实现过程 原理图示 父组件 App.vue 子组件 MyCon.vue 商品案例 运用了子传父 父组件 App.vue 子组件 MyProduct.vue 效果展示 总结 前言 每一个组件中的变量和数据都是独立的,如果别的组件想要访问另一个组件里的数据该怎么做?

  • 详细聊聊JavaScript是如何影响DOM树构建的

    目录 文档对象模型 (DOM) DOM 和 JavaScript DOM 树如何生成 解析 HTML 的三个阶段 详解 HTML 解析流程 JavaScript 是如何影响 DOM 生成的 解析过程中的优化 总结 文档对象模型 (DOM) 文档对象模型 (DOM) 会将 web 页面与到脚本或编程语言连接起来.DOM模型表示具有逻辑树的文档.树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects).DOM的方法(methods)允许以编程方式进行访问树,从而改变文档的结

  • 详细聊聊MySQL中慢SQL优化的方向

    目录 前言 SQL语句优化 记录慢查询SQL 如何修改配置 查看慢查询日志 查看SQL执行计划 如何使用 SQL编写优化 为何要对慢SQL进行治理 总结 前言 影响一个系统的运行速度的原因有很多,是多方面的,甚至可能是偶然性的,或前端,或后端,或数据库,或中间件,或服务器,或网络等等等等,真正的去定位一个问题需要对系统有一定的认知,可以根据自身的判断去缩小问题范围. 今天不说其他的优化,单独把数据库的优化拿出来说几个优化方向. 跟系统的优化方向一样,数据库的优化,同样也是多方面的,其中涵盖着SQ

随机推荐