1. 报错的背景
a)使用 RabbitMQ 发送消息时,发送消息的类型为 Map<String, Object>,map 里面我 put 了一个 <String, Long> 类型,如下图:
b)这里有一个前提:我清楚使用 org.springframework.amqp.rabbit.core.RabbitTemplate 来发送消息到 RabbitMQ 队列时,消息的序列化是由 MessageConverter 完成的。默认情况下,RabbitTemplate 使用 SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。
但是这样的数据就有可能引发以下两种问题:
- 转化后数据过长,影响传输效率。
- 容易引发 sql 注入问题。
因此这里我使用 Jackson 库将对象序列化为 JSON 格式,如下消息转化器配置
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
@Configuration
public class MqConfig {
/**
* 消息转化器
* @return
*/
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
c)拿到发来的消息 map 后,将消息按照传入的类型进行强制类型转化, 但是在强制类型转化 Long 的时候,出现了以下错误:
2. 问题分析
a)在 Map<String, Object> 中传入 <String, Long> 类型作为消息并转发,接着接收到消息后,再从 Map 中取出 <String, Long> (由于是 Object 类型,所以需要强制类型转化),然后强制类型转化 Long 引发异常,原因是 “Integer 类型不能被转化为 Long”,可是我明明传入的 Long 啊???
b)经过调试,也发现,RabbitMQ 监听到消息之后,Map 中原本是 Long 的数据,确实变成了 Integer 类型......
c)接着,又尝试了以下两种办法:
- 传入<String, Long>,接收到消息后,强制类型转化的时候就转为 <String,Integer>,解决了报错问题,但是引入了新的问题:“这不就丢失了 Long 的精度吗???”.
- 直接传入一个实体类对象(继承了 Serializable 接口),塞入 Long 数据,然后发送消息,解决了报错问题,可是总不能每次遇到 Long 类型都传一个实体类对象吧......
- 自己通过 ObjectMapper 的 writeValueAsString 转化为 JSON,然后接收消息之后再通过 readValue 将 JSON 反序列化成 Map,结果报错......
d)最后,我自己又重新写了一个 demo,模拟这个场景。但不同的是,为了验证是 rabbitmq 自身的消息转换问题,还是我引入的 Jackson 库有问题,因此这里我没有引入 Jackson 库又试了一次,发现成功了,最后发现,原来是 Jackson 库配置消息转化器的问题(b 站上的黑马程序员教程,讲错了).
为什么呢?因为 Jackson 库中的消息转化器只是基于 ObjectMapper 的 readValue 进行了一层转化,例如转化出 HashMap,但是对于你 HashMap 中存在复杂类型,例如 Long,就不能直接通过 get 获取(Jackson 库中的消息转化器没有对这一步做处理),而是需要通过 ObjectMapper 中的 convertValue 进行进一步的转化才行~
3. 解决办法
3.1. 方法一(可破头都要用 Jackson 的消息转化器)
那干脆这样做:在每次 Map<String, Object> 中有 Long 类型的时候,都先将 Long 类型通过 toString() 转化为字符串,然后接收到消息的时候,先强转为 String,然后再通过 Long.valueOf() 转化为 Long 类型,既解决了异常,又保证了不丢失精度.
发送消息如下:
接收消息如下:
成功解决异常!
3.2、方法二(推荐)
不要使用 Jackson 库中的消息转化器,而是使用 Jackson 库中的 ObjectMapper (不用添加任何依赖)手动完成序列化和反序列化的过程.
- 发送消息前,不用将 Long 类型转化成 String,只需要通过 ObjectMapper 中的 writeValueAsString 转化成 JSON 格式后再发送消息.
- 拿到消息后先通过 readValue 拿到 map ,对于 Long 这种复杂类型,直接通过 ObjectMapper.convertValue 进一步解析,就可以得到了.
Long visits = objectMapper.convertValue(hashMap.get("visits"), new TypeReference<Long>() {});