RabbitMQ竟然無法反序列化List

前言

image

??最近在接到了一個(gè)需求,大概是通過RabbitMq給xx子系統(tǒng)同步用戶數(shù)據(jù)句灌,要提供單個(gè)同步和批量同步悟耘。內(nèi)心暗喜這不簡(jiǎn)單的很嘛。三下五除二就把代碼給寫完了先紫,大概長(zhǎng)這樣:

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<User> 為啥發(fā)個(gè)Map過來檬洞?

??看到這個(gè)錯(cuò)誤狸膏,著實(shí)讓我摸不到頭腦榨婆。頓時(shí)一堆疑問用上心頭窘奏, 為啥單個(gè)對(duì)象可以昭卓,List就不行呢砍濒?我發(fā)的是List<User>數(shù)據(jù),為啥變成Map了钦扭?雖然一大堆疑問兔乞,但是只能笑嘻嘻的說戏溺,我檢查一下哈性昭。

image

問題重現(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ò)誤日志

image

好家伙果然失敗了拦止,這百分百必現(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)容是否正確。

image

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


image

??通過上圖可以發(fā)現(xiàn)匀归,消息體(payload)是一個(gè)標(biāo)準(zhǔn)的json串坑资,并且TypeId也是List,并不是錯(cuò)誤信息中的LinkedHashMap穆端。哈哈哈袱贮,到此可以石錘是消費(fèi)端反序列化的問題了。趕緊把鍋甩出去体啰,抽他呀的(自嗨而已)攒巍,我寫的代碼怎么可能有bug。

image

??對(duì)我愛學(xué)習(xí)的我荒勇,肯定不愿意就這樣算了柒莉。必須刨根問底,給他上一課沽翔。于是我在google一圈發(fā)現(xiàn)這竟然是這個(gè)bug兢孝。有個(gè)老哥也發(fā)現(xiàn)了,并提交了一個(gè)issues: spring-ampq/issues/1279仅偎。

image

??大致是說:嘗試從 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腹备。

image

??然后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ù)的代碼如下:

image

??通過閱讀代碼發(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ì)簡(jiǎn)單粗暴了,始終轉(zhuǎn)換推斷類型囱淋。

解決辦法

??到此問題分析完畢猪杭,簡(jiǎn)單總結(jié)一下解決方法。主要有兩種:

  1. 在消費(fèi)端開啟如下配置即可:
// 始終轉(zhuǎn)換推斷類型
converter.setAlwaysConvertToInferredType(true);
  1. 升級(jí)版本:由于GH-1729: Fix JSON Regression合并到了2.2.13.RELEASE妥衣。所以只需要將 spring-amqp 升級(jí)到 2.2.13.RELEASE 或以上皂吮。或者升級(jí)SpringBoot版本到2.3.7.RELEASE税手。

結(jié)尾

??如果覺得對(duì)你有幫助蜂筹,可以多多評(píng)論,多多點(diǎn)贊哦芦倒,也可以到我的主頁看看艺挪,說不定有你喜歡的文章,也可以隨手點(diǎn)個(gè)關(guān)注哦兵扬,謝謝麻裳。

??我是不一樣的科技宅,每天進(jìn)步一點(diǎn)點(diǎn)器钟,體驗(yàn)不一樣的生活津坑。我們下期見!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傲霸,一起剝皮案震驚了整個(gè)濱河市疆瑰,隨后出現(xiàn)的幾起案子眉反,更是在濱河造成了極大的恐慌,老刑警劉巖穆役,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寸五,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡孵睬,警方通過查閱死者的電腦和手機(jī)播歼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門伶跷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掰读,“玉大人,你說我怎么就攤上這事叭莫〉讣” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵雇初,是天一觀的道長(zhǎng)拢肆。 經(jīng)常有香客問我,道長(zhǎng)靖诗,這世上最難降的妖魔是什么郭怪? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮刊橘,結(jié)果婚禮上鄙才,老公的妹妹穿的比我還像新娘。我一直安慰自己促绵,他們只是感情好攒庵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著败晴,像睡著了一般浓冒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尖坤,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天稳懒,我揣著相機(jī)與錄音,去河邊找鬼慢味。 笑死僚祷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贮缕。 我是一名探鬼主播辙谜,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼感昼!你這毒婦竟也來了装哆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜕琴,沒想到半個(gè)月后萍桌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凌简,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年上炎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雏搂。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藕施,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凸郑,到底是詐尸還是另有隱情裳食,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布芙沥,位于F島的核電站诲祸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏而昨。R本人自食惡果不足惜救氯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歌憨。 院中可真熱鬧着憨,春花似錦、人聲如沸躺孝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽植袍。三九已至惧眠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間于个,已是汗流浹背氛魁。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厅篓,地道東北人秀存。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羽氮,于是被迫代替她去往敵國(guó)和親或链。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容