为Rabbitmq中的Jackson2JsonMessageConverter自定义ClassMapper

新年第一篇~~ 🐣🐥🐤🐔

消息队列算是各个系统间通信比较常见的方式了。我们公司用的是是基于 AMQP 协议的 RabbitMq。在 Spring-AMQP 中比较重要的类就是 Message,因为要发送的消息必须要构造成一个 Message 对象来进行传输。Message 对象包括两部分 Body 和 Properties,Body 就是真正要发送的消息内容,Properties 就是和消息相关的一些属性(消息头,要发送的交换机,routingKey等等),主要结构如下:

1
2
3
4
5
6
7
public class Message {
private final MessageProperties messageProperties;
private final byte[] body;
}

消息生产者构造好 Message 之后,就会将 Message 发送到指定的 Exchange (交换机),再根据 Exchange 的类型及 routing-key 将消息路由到相应的 queue 中,最后被监听该 queue 的消费者消费,大致如下图:

流程1

不过每次发消息都要自己构造 Message 对象比较麻烦。Spring-AMQP 允许我们直接使用自定义的类,然后会利用指定好的 MessageConverter 将自定义的类转换为 Message 进行发送,在接收时也会利用 MessageConverter 将接收到的 Message 对象转成需要的对象。Spring-AMQP 提供了多种 MessageConverter,比如 SimpleMessageConverter,SerializerMessageConverter,Jackson2JsonMessageConverter,MarshallingMessageConverter等等,如果发送的消息对象不是 Message 实例,并且没有指定 MessageConverter 的话,默认用 SimpleMessageConverter。以上各种 MessageConverter 归根结底都是实现了 MessageConverter 接口,该接口只有两个方法:

1
2
3
4
public interface MessageConverter {
Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException;
Object fromMessage(Message message) throws MessageConversionException;
}

这两个方法一个是在发送消息时将我们的消息对象转换成标准的 Message 对象,另一个是在接受消息时将 Message 对象转换为相应的对象。
比较常用的 Converter 就是 Jackson2JsonMessageConverter(以下简称 JsonMessageConverter),在发送消息时,它会先将自定义的消息类序列化成json格式,再转成byte构造 Message,在接收消息时,会将接收到的 Message 再反序列化成自定义的类。大致流程如下图:
流程2

不过使用 JsonMessageConverter 时有一个小问题,在不对它进行任何改造的前提下,发送消息的类和接受消息的类必须是一样的,不仅是要里面的字段一样,类名一样,连类的包路径都要一样。

所以当系统1使用 JsonMessageConverter 发送消息类A给系统2时,系统2可以有如下几种方式来接收:

  • 1.依赖系统1的jar包,直接使用类A来接收
  • 2.不依赖系统1的jar包,自己建一个和A一模一样的类,连名称,包路径都一样
  • 3.负责监听 queue 的类实现 MessageListener 接口,直接接收 Message 类,再自己转换

上面三个方法都不是很好,按照正常的想法,我们肯定是期望系统2直接使用自己的类来接收就可以了,只要与A类的字段名一样即可。那有没有方法可以让系统2既不依赖无用的jar包,也不用建立个与自己命名规范不相符的类, 也无需自己转换呢?

要解决这个问题,就要先看看 JsonMessageConverter 是如何将 Message 进行反序列化的。
在 JsonMessageConverter 的 fromMessage 方法中有这么一段:

1
2
3
4
5
6
7
8
9
if (getClassMapper() == null) {
JavaType targetJavaType = getJavaTypeMapper()
.toJavaType(message.getMessageProperties());
content = convertBytesToObject(message.getBody(), encoding, targetJavaType);
} else {
Class<?> targetClass = getClassMapper().toClass(
message.getMessageProperties());
content = convertBytesToObject(message.getBody(), encoding, targetClass);
}

就是说默认情况下,JsonMessageConverter 使用的 ClassMapper 是 DefaultJackson2JavaTypeMapper,在转换时通过 Message 的 Properties 来获取要转换的目标类的类型。通过 Debug 可以发现,目标类的类型是存储在 Message 的 Proterties 的 一个 headers 的 Map 中,Key 叫“__TypeId__”。所以只要想办法在传输消息时更改__TypeId__的值即可。

下面是解决办法,在消息的生产者端为 JsonMessageConverter, 设置一个自定义的 ClassMapper,重写 fromClass 方法,将 __TypeId__ 的值设为消费端用来接收的类的路径+名称。当然了,也可以在消费者端重写toClass方法,直接返回想要转换的目标类的类类型。两种选一种就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Bean
public Jackson2JsonMessageConverter customConverter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setClassMapper(new ClassMapper() {
@Override
public Class<?> toClass(MessageProperties properties) {
throw new UnsupportedOperationException("this mapper is only for outbound, do not use for receive message");
}
@Override
public void fromClass(Class<?> clazz, MessageProperties properties) {
properties.setHeader("__TypeId__", "com.xxx.B");
}
});
return converter;
}

感觉自己语言组织能力退化了。。。。。

分享到 评论