RabbitMQ竟然无法反序列化List

前言

image

  最近在接到了一个需求,大概是通过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<User> 为啥发个Map过来?

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

image

问题重现

  • 项目依赖
<?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);    }}

错误日志

image

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

分析问题原因

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

image

消息内容如下图所示:


image

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

image

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

image

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

image

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

解决方法是:

converter.setAlwaysConvertToInferredType(true);

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

image

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

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

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

解决办法

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

  1. 在消费端开启如下配置即可:
// 始终转换推断类型
converter.setAlwaysConvertToInferredType(true);
  1. 升级版本:由于GH-1729: Fix JSON Regression合并到了2.2.13.RELEASE。所以只需要将 spring-amqp 升级到 2.2.13.RELEASE 或以上。或者升级SpringBoot版本到2.3.7.RELEASE。

结尾

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

  我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容