前言最近接到一个请求,大概是通过RabbitMq为xx子系统同步用户数据,提供单同步和批量同步。我暗自庆幸,这不简单。代码由三遍、五遍、二除完成。看起来像这样:publicvoidsyncUserSingle(Useruser){//省略很多业务代码rabbitTemplate.convertAndSend("q_sync_user_single",user);}publicvoidsyncUserBatch(ListuserList){//省略很多业务代码rabbitTemplate.convertAndSend("q_sync_user_batch",userList);}但是在联调过程中,遇到了一个比较奇怪的问题。当单个用户同步时,子系统可以正常消费。然后在进行批量同步时,子系统报错。并抛出java.lang.ClassCastException提示LinkedHashMapcannotxxxxclass。于是负责子系统的哥们就笑着(表面笑)走过来跟我说,为什么发个Map而不约定List呢?看到这个错误真的让我很困惑。脑子里立刻冒出一堆问题,为什么单个对象可以,而List却不行?我发了List数据,为什么变成了Map?虽然问题很多,但我也只能笑着说,我查一下。问题重现项目依赖4.0.0org.springframework.bootspring-boot-starter-parent2.3.2.RELEASEorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-amqp发送方初始化队列@ConfigurationpublicclassQueueConfig{@BeanpublicQueuetest(){returnnewQueue("test");}}configureRabbitTemplete@ConfigurationpublicclassRabbitTemplateConfig{@AutowiredpublicRabbitTemplateConfig(RabbitTemplaterabbitTemplate){//设置Json消息转换器rabbitTemplate.setMessageConverter(newJackson2JsonMessageConverter());}}发送接口@Controller@RequestMapping("/test")publicclassTestController{@ResourceprivateRabbitTemplatetemplate;@GetMapping("/send")publicvoidsend(){template.convertAndSend("test",Collections.singletonList(newUser(20,"不同技术之家")));}}Userclass@Data@AllArgsConstructorpublicclassUser{/***age*/privateIntegerage;/***name*/privateStringname;}接收监听器配置@ConfigurationpublicclassRabbitListenerConfig{@BeanpublicSimpleRabbitListenerContainerFactorycustomFactory(SimpleRabbitListenerContainerFactoryConfigurer配置器,ConnectionFactoryconnectionFactory){SimpleRabbitListenerContainerFactoryfactory=newSimpleRabbitListenerContainerFactory();//设置消息转换器factory.setMessageConverter(newJackson2JsonMessageConverter());配置器.configure(工厂,connectionFactory);返厂;}}接收方@ServicepublicclassUserService{publicvoidsave(ListuserList){userList.forEach(System.out::println);}}@ComponentpublicclassReceiver{@ResourceprivateUserServiceuserService;@RabbitListener(queues="test",containerFactory="customFactory")publicvoidreceive(@PayloadListmsg){userService.save(msg);}}错误日志好家伙真的失败了,这个bug是100%肯定会出现的分析问题原因第一个错误信息是在消费端丢出去的,按理说出问题的概率应该更高消费者端。但是如果像他说的那样,我的生产端发送的消息是错误的,导致消费端出现问题怎么办?对于这个问题,我先断开消费者,然后发送消息,通过Rabbitmq控制台查看消息内容是否正确。消息内容如下图所示:由上图我们可以发现,消息体(payload)是一个标准的json字符串,TypeId也是一个List,而不是错误消息中的LinkedHashMap。哈哈哈,至此,石锤可以说是消费端反序列化的问题了。赶紧甩锅,揍他一顿(开玩笑的),我写的代码怎么可能有bug。对于爱学习的我来说,绝对不想放过。我们必须追根究底,给他一个教训。于是google了一下,发现原来是这个bug。也有兄弟发现并提交了一个issue:spring-ampq/issues/1279。粗略地说:尝试从SpringBoot2.3.1升级到2.3.3,然后再升级到2.3.6。报错信息依然是:ListfoosisaLikedHashMap,notaFooobject。并确认远程调试也是如此。出于某种原因,他认为泛型没有被正确使用。恢复到Spring-AMQP2.2.7使其再次工作,对象确实是Foo。然后garyrussell这家伙说:他们添加了对抽象类反序列化的支持,如果配置不正确,这会对消息转换器产生一些副作用。然后查看它并确认这是一个错误。因为List是抽象的,新代码认为它不能被反序列化。解决方法是:converter.setAlwaysConvertToInferredType(true);后面也提到这个问题在GH-1729:FixJSONRegression中被修复。修复后的代码如下:通过阅读代码,发现修改前的逻辑是:如果推断类型为抽象类型,则返回false表示无法转换为推断类型。然后它被转换成一个LinkedHashMap。这是LinkedHashMap无法转换xxxx类的主要原因。修改为:如果推断类型是抽象类型而不是容器类型,则返回false。也就是说,虽然推断出的类型是抽象的,但是如果是容器类型,并且容器中的对象不是抽象的,就可以进行转换。这样就避免了上述问题。前面也提到了可以通过添加配置来解决。解决方法比较简单粗暴,一直转换推断类型。解法分析完这个问题后,简单总结一下解法。有两种主要类型:只需在消费者端启用以下配置://始终转换推断类型converter.setAlwaysConvertToInferredType(true);升级版本:由于GH-1729:修复JSON回归并入2.2.13.RELEASE。所以只需要将spring-amqp升级到2.2.13.RELEASE或以上即可。或者升级SpringBoot版本到2.3.7.RELEASE。最后如果觉得对您有帮助,可以点个赞,多多指教。你也可以去我的主页看看。或许有喜欢的文章,可以随便关注一下,谢谢。我是不一样的科技极客,每天进步一点点,体验不一样的生活。下次见!