詳細(xì)聊聊RabbitMQ竟無法反序列化List問題
前言
最近在接到了一個(gè)需求,大概是通過RabbitMq給xx子系統(tǒng)同步用戶數(shù)據(jù),要提供單個(gè)同步和批量同步。內(nèi)心暗喜這不簡單的很嘛。三下五除二就把代碼給寫完了,大概長這樣:
public void syncUserSingle(User user) { // 省略一大堆業(yè)務(wù)代碼 rabbitTemplate.convertAndSend("q_sync_user_single", user); } public void syncUserBatch(List<User> userList) { // 省略一大堆業(yè)務(wù)代碼 rabbitTemplate.convertAndSend("q_sync_user_batch", userList); }
但是在聯(lián)調(diào)的過程中,遇到了一個(gè)比較奇葩的問題。單個(gè)用戶進(jìn)行同步時(shí),子系統(tǒng)可以正常消費(fèi)。然后進(jìn)行批量同步的時(shí)候,子系統(tǒng)報(bào)錯(cuò)了。并拋出java.lang.ClassCastException提示 LinkedHashMap cannot xxxx class 。于是負(fù)責(zé)子系統(tǒng)的哥們笑嘻嘻的(表面笑嘻嘻)走過來對(duì)我說,不是約定List 為啥發(fā)個(gè)Map過來?
看到這個(gè)錯(cuò)誤,著實(shí)讓我摸不到頭腦。頓時(shí)一堆疑問涌上心頭, 為啥單個(gè)對(duì)象可以,List就不行呢?我發(fā)的是List 數(shù)據(jù),為啥變成Map了?雖然一大堆疑問,但是只能笑嘻嘻的說,我檢查一下哈。
問題重現(xiàn)
項(xiàng)目依賴
<?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ā)送方
初始化隊(duì)列
@Configuration public class QueueConfig { @Bean public Queue test() { return new Queue("test"); } }
配置RabbitTemplete
@Configuration public class RabbitTemplateConfig { @Autowired public RabbitTemplateConfig(RabbitTemplate rabbitTemplate) { // 設(shè)置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(); // 設(shè)置消息轉(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); }}
錯(cuò)誤日志
❝好家伙果然失敗了,這百分百必現(xiàn)的bug呀。❞
分析問題原因
首先錯(cuò)誤信息是在消費(fèi)端拋出來的,按理應(yīng)該是消費(fèi)端出問題概率較大。但是如果和他說的一樣,我生產(chǎn)端發(fā)送的消息就是錯(cuò)誤的,從而導(dǎo)致消費(fèi)端出問題呢?這對(duì)這個(gè)疑問,我先斷開消費(fèi)端,然后發(fā)送一條消息,并通過Rabbitmq的管控臺(tái)來查看消息的內(nèi)容是否正確。
消息內(nèi)容如下圖所示:
通過上圖可以發(fā)現(xiàn),消息體(payload)是一個(gè)標(biāo)準(zhǔn)的json串,并且TypeId也是List,并不是錯(cuò)誤信息中的LinkedHashMap。哈哈哈,到此可以石錘是消費(fèi)端反序列化的問題了。趕緊把鍋甩出去,抽他呀的(自嗨而已),我寫的代碼怎么可能有bug。
對(duì)我愛學(xué)習(xí)的我,肯定不愿意就這樣算了。必須刨根問底,給他上一課。于是我在google一圈發(fā)現(xiàn)這竟然是這個(gè)bug。有個(gè)老哥也發(fā)現(xiàn)了,并提交了一個(gè)issues: spring-ampq/issues/1279。
大致是說:嘗試從 Spring Boot 2.3.1 升級(jí)到 2.3.3,然后再升級(jí)到 2.3.6。錯(cuò)誤信息依然是:List<Foo> foos是LikedHashMap,而不是Foo對(duì)象。并通過遠(yuǎn)程調(diào)試確認(rèn)了這種情況。出于某種原因,他認(rèn)為沒有正確使用泛型類型?;謴?fù)到 Spring-AMQP 2.2.7 使它再次工作,并且對(duì)象確實(shí)是Foo。
然后garyrussell這個(gè)人說:他們添加了對(duì)抽象類反序列化的支持,如果配置不正確,這會(huì)對(duì)消息轉(zhuǎn)換器產(chǎn)生一些副作用。然后調(diào)查了一下,確認(rèn)這是一個(gè)錯(cuò)誤。是由于List是抽象的,新代碼認(rèn)為它不能反序列化。
解決方法是:
converter.setAlwaysConvertToInferredType(true);
后面還提到在 GH-1729: Fix JSON Regression修復(fù)這個(gè)問題,修復(fù)的代碼如下:
通過閱讀代碼發(fā)現(xiàn),修改前的邏輯是: 如果推斷類型是抽象的,則返回false也就代表不能轉(zhuǎn)換成推斷類型。然后被轉(zhuǎn)換成LinkedHashMap。這也就是出現(xiàn) LinkedHashMap cannot cast xxxx class的主要原因。
修改后變成了:如果推斷類型是抽象的并且不是容器類型,返回false。也就意味著,雖然推斷類型是抽象的,但是如果是容器類型,并且容器內(nèi)的對(duì)象不是抽象的,則可以被轉(zhuǎn)換。這樣一來避免了上述問題的產(chǎn)生了。
前面還提到了通過增加配置來解決。解決起來就相對(duì)簡單粗暴了,始終轉(zhuǎn)換推斷類型。
解決辦法
到此問題分析完畢,簡單總結(jié)一下解決方法。主要有兩種:
1、在消費(fèi)端開啟如下配置即可:
// 始終轉(zhuǎn)換推斷類型 converter.setAlwaysConvertToInferredType(true);
2、升級(jí)版本:由于GH-1729: Fix JSON Regression合并到了2.2.13.RELEASE。所以只需要將 spring-amqp 升級(jí)到 2.2.13.RELEASE 或以上?;蛘呱?jí)SpringBoot版本到2.3.7.RELEASE。
總結(jié)
到此這篇關(guān)于RabbitMQ竟無法反序列化List的文章就介紹到這了,更多相關(guān)RabbitMQ無法反序列化List內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot中@Validated注解不生效問題匯總大全
這篇文章主要給大家介紹了關(guān)于Spring?Boot中@Validated注解不生效問題匯總的相關(guān)資料,@Validated注解是Spring框架中的一個(gè)注解,用于在方法參數(shù)上添加參數(shù)校驗(yàn)規(guī)則,需要的朋友可以參考下2023-07-07AQS(AbstractQueuedSynchronizer)抽象隊(duì)列同步器及工作原理解析
AQS是用來構(gòu)建鎖或者其他同步器組件的重量級(jí)基礎(chǔ)框架及整個(gè)JUC體系的基石,通過內(nèi)置的FIFO對(duì)列來完成資源獲取線程的排隊(duì)工作,并通過一個(gè)int類型變量表示持有鎖的狀態(tài),本文給大家詳細(xì)介紹下AQS抽象隊(duì)列同步器的相關(guān)知識(shí),感興趣的朋友一起看看吧2022-03-03Java程序生成exe可執(zhí)行文件詳細(xì)教程(圖文說明)
這篇文章主要介紹了Java程序生成exe可執(zhí)行文件詳細(xì)教程,有需要的朋友可以參考一下2013-12-12實(shí)例分析Java中public static void main(String args[])是什么意思
這篇文章主要介紹了實(shí)例分析Java中public static void main(String args[])的意義,詳細(xì)分析了Java主函數(shù)main關(guān)鍵字聲明的具體含義和用法,需要的朋友可以參考下2015-12-12如何使用spring gateway微服務(wù)網(wǎng)關(guān)(基本用法)
本文介紹spring gateway的使用,包括配置文件的使用和調(diào)試跟蹤,讓大家了解spring gateway的基本用法,感興趣的朋友跟隨小編一起看看吧2024-08-08Android 資源 id詳解及的動(dòng)態(tài)獲取
這篇文章主要介紹了Android 資源 id詳解及的動(dòng)態(tài)獲取的相關(guān)資料,需要的朋友可以參考下2016-12-12面試官:詳細(xì)談?wù)凧ava對(duì)象的4種引用方式
這篇文章主要給大家介紹了java面試官常會(huì)問到的,關(guān)于Java對(duì)象的4種引用方式的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05