什么是序列化和反序列化
序列化(Serialization)和反序列化(Deserialization)是将对象转换为字节流或将字节流转换为对象的过程。
序列化是指将对象转换为字节流的过程。在序列化时,对象的状态信息(例如属性值、字段值等)被转换为一串字节,以便能够在网络上传输或者保存到磁盘上。序列化可以将对象持久化,并且使其能够在不同的环境中进行传输和恢复。序列化通常用于分布式系统、缓存、消息队列等场景。
反序列化是指将字节流恢复为对象的过程。在反序列化时,字节流被解析并还原为对应的对象,使其可以在内存中重新使用。反序列化是序列化的逆过程,通过反序列化,我们可以重新获得序列化之前的对象实例及其状态信息。
序列化和反序列化的主要目的是实现对象的持久化和跨网络传输。通过序列化和反序列化,我们可以将对象转换为字节流,然后在需要的时候重新恢复成对象,使得对象的状态可以被保存和传输。
rabbitmq中的序列化和反序列化
在 RabbitMQ 中,消息的序列化和反序列化是指将消息体转换为字节流以便发送到 RabbitMQ 服务器,并且在接收消息时将字节流还原为消息体的过程。
通常情况下,当你向 RabbitMQ 发送消息时,消息体需要被序列化成字节流。RabbitMQ 不会直接处理对象,而是处理字节流。因此,在发送消息之前,你需要将消息体序列化为字节流。同样地,当消费者从 RabbitMQ 接收消息时,需要将接收到的字节流反序列化为原始的消息体对象。
在 RabbitMQ 中,最常见的消息序列化方式是将消息体序列化为 JSON 或者其他类似的格式。在生产者端,你可以将对象转换为 JSON 字符串并将其作为消息体发送到 RabbitMQ;在消费者端,你可以从接收到的 JSON 字符串中解析出对象。
实现
RabbitMQ 提供了 Jackson2JsonMessageConverter 来支持消息内容 JSON 序列化与反序列化
消息发送者在发送消息时应设置 MessageConverter 为 Jackson2JsonMessageConverter
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
消费者
package com.example.user.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName RabbitMQConfig
* @Author Larry
* @create 2024/3/8 0008 15:23
*/
@Configuration
@Data
public class RabbitMQConfig {
@Bean
public MessageConverter jsonMessageConverter(ObjectMapper objectMapper) {
return new Jackson2JsonMessageConverter(objectMapper);
}
}
生产者
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
然后理论上来说,就可以实现正常的发送接受消息了。
踩坑
然而我在接受消息时确一直报错,只有String的消息才能被消费,而我发送和接受都是用的一个对象,而且mq管理页面可以接受到消息,而消费者不行。于是我觉得是反序列化出了问题,
开始各种查阅资料配置mq的序列化和反序列化,都不管用。
于是我开始仔细阅读报错信息:
Caused by: org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener threw exception
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.wrapToListenerExecutionFailedExceptionIfNeeded(AbstractMessageListenerContainer.java:1705) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1595) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1510) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1498) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1489) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1433) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
... 6 common frames omitted
Caused by: org.springframework.amqp.support.converter.MessageConversionException: failed to resolve class name. Class not found [com.example.vochers.domain.CouponRecordMessage]
at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:189) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.fromTypeHeader(DefaultJackson2JavaTypeMapper.java:146) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.toJavaType(DefaultJackson2JavaTypeMapper.java:122) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.AbstractJackson2MessageConverter.doFromMessage(AbstractJackson2MessageConverter.java:309) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.AbstractJackson2MessageConverter.fromMessage(AbstractJackson2MessageConverter.java:273) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.AbstractJackson2MessageConverter.fromMessage(AbstractJackson2MessageConverter.java:253) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.AbstractAdaptableMessageListener.extractMessage(AbstractAdaptableMessageListener.java:302) ~[spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter$MessagingMessageConverterAdapter.extractPayload(MessagingMessageListenerAdapter.java:323) ~[spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.support.converter.MessagingMessageConverter.fromMessage(MessagingMessageConverter.java:130) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.toMessagingMessage(MessagingMessageListenerAdapter.java:205) ~[spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:132) ~[spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1591) [spring-rabbit-2.2.10.RELEASE.jar:2.2.10.RELEASE]
... 10 common frames omitted
Caused by: java.lang.ClassNotFoundException: com.example.vochers.domain.CouponRecordMessage
at java.net.URLClassLoader.findClass(URLClassLoader.java:387) ~[na:1.8.0_372]
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_372]
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) ~[na:1.8.0_372]
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_372]
at java.lang.Class.forName0(Native Method) ~[na:1.8.0_372]
at java.lang.Class.forName(Class.java:348) ~[na:1.8.0_372]
at org.springframework.util.ClassUtils.forName(ClassUtils.java:284) ~[spring-core-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper.getClassIdType(DefaultJackson2JavaTypeMapper.java:185) ~[spring-amqp-2.2.10.RELEASE.jar:2.2.10.RELEASE]
这是其中一小部分,也是关键所在。
他一直在报找不到我传入的对象的类,于是我查看了我项目的整体结构,大胆推理,发现了问题所在。因为我这并不是一个完整的项目,因为只是想实现部分demo,就几乎所有东西都在这俩模块,没对公共部分进行抽离
也就是说,我传入的那个类,在两个类都有,属于是两个类,在我接受参数的时候,先对第一个模块的类进行反射,然后对应第二个类,然后发现找不到第一个类,因为它们属于不同模块,路径是不同的。
在 Java 中,当使用反射加载类时,通常是根据类的全限定名(包括包路径)来进行加载的。如果类的全限定名是正确的,Java 的 ClassLoader 应该能够根据类的包路径找到对应的类文件并加载类。所以,当使用反射时,确保提供的类的全限定名是正确的,并且类文件在类路径下可被找到。这样系统的 ClassLoader 才能够成功加载该类。如果类的全限定名不正确,或者类文件不在类路径下,就会导致 ClassNotFoundException 异常。
解决
增加commom,抽离出公共类就完了
再次测试
成功被消费
结语
打好基础,这次bug本质就是对反射和序列化理解不到位,同时要细看报错信息,不要妄自猜测报错原因。