詳細聊聊RabbitMQ竟無法反序列化List問題
前言
最近在接到了一個需求,大概是通過RabbitMq給xx子系統(tǒng)同步用戶數(shù)據(jù),要提供單個同步和批量同步。內(nèi)心暗喜這不簡單的很嘛。三下五除二就把代碼給寫完了,大概長這樣:
public void syncUserSingle(User user) {
// 省略一大堆業(yè)務代碼
rabbitTemplate.convertAndSend("q_sync_user_single", user);
}
public void syncUserBatch(List<User> userList) {
// 省略一大堆業(yè)務代碼
rabbitTemplate.convertAndSend("q_sync_user_batch", userList);
}
但是在聯(lián)調(diào)的過程中,遇到了一個比較奇葩的問題。單個用戶進行同步時,子系統(tǒng)可以正常消費。然后進行批量同步的時候,子系統(tǒng)報錯了。并拋出java.lang.ClassCastException提示 LinkedHashMap cannot xxxx class 。于是負責子系統(tǒng)的哥們笑嘻嘻的(表面笑嘻嘻)走過來對我說,不是約定List 為啥發(fā)個Map過來?
看到這個錯誤,著實讓我摸不到頭腦。頓時一堆疑問涌上心頭, 為啥單個對象可以,List就不行呢?我發(fā)的是List 數(shù)據(jù),為啥變成Map了?雖然一大堆疑問,但是只能笑嘻嘻的說,我檢查一下哈。
問題重現(xiàn)
項目依賴
<?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>
發(fā)送方
初始化隊列
@Configuration
public class QueueConfig {
@Bean
public Queue test() {
return new Queue("test");
}
}
配置RabbitTemplete
@Configuration
public class RabbitTemplateConfig {
@Autowired
public RabbitTemplateConfig(RabbitTemplate rabbitTemplate) {
// 設置Json消息轉(zhuǎn)換器
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
}
}
發(fā)送接口
@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;
}
接收方
監(jiān)聽配置
@Configuration
public class RabbitListenerConfig {
@Bean
public SimpleRabbitListenerContainerFactory customFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// 設置消息轉(zhuǎn)換器
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); }}
錯誤日志
❝好家伙果然失敗了,這百分百必現(xiàn)的bug呀。❞
分析問題原因
首先錯誤信息是在消費端拋出來的,按理應該是消費端出問題概率較大。但是如果和他說的一樣,我生產(chǎn)端發(fā)送的消息就是錯誤的,從而導致消費端出問題呢?這對這個疑問,我先斷開消費端,然后發(fā)送一條消息,并通過Rabbitmq的管控臺來查看消息的內(nèi)容是否正確。

消息內(nèi)容如下圖所示:

通過上圖可以發(fā)現(xiàn),消息體(payload)是一個標準的json串,并且TypeId也是List,并不是錯誤信息中的LinkedHashMap。哈哈哈,到此可以石錘是消費端反序列化的問題了。趕緊把鍋甩出去,抽他呀的(自嗨而已),我寫的代碼怎么可能有bug。
對我愛學習的我,肯定不愿意就這樣算了。必須刨根問底,給他上一課。于是我在google一圈發(fā)現(xiàn)這竟然是這個bug。有個老哥也發(fā)現(xiàn)了,并提交了一個issues: spring-ampq/issues/1279。

大致是說:嘗試從 Spring Boot 2.3.1 升級到 2.3.3,然后再升級到 2.3.6。錯誤信息依然是:List<Foo> foos是LikedHashMap,而不是Foo對象。并通過遠程調(diào)試確認了這種情況。出于某種原因,他認為沒有正確使用泛型類型。恢復到 Spring-AMQP 2.2.7 使它再次工作,并且對象確實是Foo。

然后garyrussell這個人說:他們添加了對抽象類反序列化的支持,如果配置不正確,這會對消息轉(zhuǎn)換器產(chǎn)生一些副作用。然后調(diào)查了一下,確認這是一個錯誤。是由于List是抽象的,新代碼認為它不能反序列化。
解決方法是:
converter.setAlwaysConvertToInferredType(true);
后面還提到在 GH-1729: Fix JSON Regression修復這個問題,修復的代碼如下:

通過閱讀代碼發(fā)現(xiàn),修改前的邏輯是: 如果推斷類型是抽象的,則返回false也就代表不能轉(zhuǎn)換成推斷類型。然后被轉(zhuǎn)換成LinkedHashMap。這也就是出現(xiàn) LinkedHashMap cannot cast xxxx class的主要原因。
修改后變成了:如果推斷類型是抽象的并且不是容器類型,返回false。也就意味著,雖然推斷類型是抽象的,但是如果是容器類型,并且容器內(nèi)的對象不是抽象的,則可以被轉(zhuǎn)換。這樣一來避免了上述問題的產(chǎn)生了。
前面還提到了通過增加配置來解決。解決起來就相對簡單粗暴了,始終轉(zhuǎn)換推斷類型。
解決辦法
到此問題分析完畢,簡單總結(jié)一下解決方法。主要有兩種:
1、在消費端開啟如下配置即可:
// 始終轉(zhuǎn)換推斷類型 converter.setAlwaysConvertToInferredType(true);
2、升級版本:由于GH-1729: Fix JSON Regression合并到了2.2.13.RELEASE。所以只需要將 spring-amqp 升級到 2.2.13.RELEASE 或以上?;蛘呱塖pringBoot版本到2.3.7.RELEASE。
總結(jié)
到此這篇關于RabbitMQ竟無法反序列化List的文章就介紹到這了,更多相關RabbitMQ無法反序列化List內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用Mybatis Plus整合多數(shù)據(jù)源和讀寫分離的詳細過程
這篇文章主要介紹了Mybatis Plus整合多數(shù)據(jù)源和讀寫分離的詳細過程,mybatisplus可以整合阿里的分布式事務組件seata,本文通過示例代碼給大家介紹的非常詳細,需要的朋友參考下吧2021-09-09
Mybatis的collection三層嵌套查詢方式(驗證通過)
這篇文章主要介紹了Mybatis的collection三層嵌套查詢方式(驗證通過),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03

