起因:本人刚入职一家新的云解决方案公司,该云平台分为前置机和后端业务机,前置机负责接入设备,业务机负责业务逻辑处理,同时前置机和后端业务机通过rabbitmq进行消息通讯。


   但原来系统都维护在一个大的库中,你们懂的,代码存在耦合,非常不利于后期的维护,并且从技术栈、版本等因素考虑。


   所以本人加入后打算对结构改造一下,将前置机和业务机程序分为两个独立的库维护。


    


   经过:

     在拆分过程中,一切都还算顺利,因为本来两个模块都比较独立,将公用抽象出来,基本上事情就成了。

     其他不详细讲了,讲我们的主题,拆分后rabbitmq的消息转化器从原来的  SerializerMessageConverter

 转化为Jackson2JsonMessageConverter.

  原因是库独立了,类也变化了,所以要选取平台无关的序列化方式来处理,这点很自然的都能想到。

    在测试阶段也没发现什么问题,一切貌似很正常。

    但当接近上线的时候,在测试byte[]类型的数据的时候,发现问题了,反序列化后 居然成为了一个字符串。预期的byte[]数组呢?


  结果:

    处理结果很简单,我们将byte[]进行了base64转码后传输,问题解决。


    其后:

    经过查找Jackson2JsonMessageConverter的原代码,发现还有另外一个大坑:


  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);

}



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


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


1.依赖系统1的jar包,直接使用类A来接收

2.不依赖系统1的jar包,自己建一个和A一模一样的类,连名称,包路径都一样

3.负责监听 queue 的类实现 MessageListener 接口,直接接收 Message 类,再自己转换

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


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


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

   

 参考代码实现:

@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;

}

  

 

     



本文版权归作者,欢迎转载,但未经作者同意必须在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。