比较两种常用的MessageConverter:Jackson2JsonMessageConverter
和SimpleMessageConverter
。
Jackson2JsonMessageConverter
和 SimpleMessageConverter
都是 Spring AMQP 框架用来转换消息的方法,但是他们的使用情况和处理方式有一些区别。
-
SimpleMessageConverter: 这个类是 Spring AMQP 的默认消息转换类。它可以处理四种类型的数据:
String
、byte[]
、java可序列化的对象
和Spring RemoteInvocation
。它是一种基础的转换方式,适应于简单的消息转换需求。 -
Jackson2JsonMessageConverter:
Jackson2JsonMessageConverter
是一个使用 Jackson JSON 库来将消息转换为 JSON 格式。这个类能够处理所有能被 Jackson 库支持的对象类型,并且可以在消息发送和接收时,把消息的 content_type 自动设置为 application/json。它在处理复杂的对象类型或者需要转换为 JSON 格式的消息时非常有用。
发送消息
Jackson2JsonMessageConverter
Jackson2JsonMessageConverter
使用 Jackson 库来将任意 Java 对象转化为 JSON 格式,再转化为 byte[]
。所以它不需要像 SimpleMessageConverter
那样检查对象的具体类型。
// 方法声明。接受一个要转换的对象、消息属性和可选的泛型类型。
protected Message createMessage(Object objectToConvert, MessageProperties messageProperties,
@Nullable Type genericType) throws MessageConversionException {
byte[] bytes;
try {
// 如果 standardCharset 为 true
if (this.standardCharset) {
// 使用Jackson的ObjectMapper将对象转换为字节数组
bytes = this.objectMapper.writeValueAsBytes(objectToConvert);
}
else {
// 转换为 JSON 文本,并将其编码为字节
String jsonString = this.objectMapper.writeValueAsString(objectToConvert);
bytes = jsonString.getBytes(getDefaultCharset());
}
}
// 如果遇到了任何问题,抛出MessageConversionException
catch (IOException e) {
throw new MessageConversionException("Failed to convert Message content", e);
}
// 设置消息的内容类型和编码以及内容的长度
messageProperties.setContentType(this.supportedContentType.toString());
messageProperties.setContentEncoding(getDefaultCharset());
messageProperties.setContentLength(bytes.length);
// 如果 getClassMapper() 返回 null
if (getClassMapper() == null) {
// 使用 Jackson 来构建 JavaType
JavaType type = this.objectMapper.constructType(
genericType == null ? objectToConvert.getClass() : genericType);
// 如果泛型类型不为 null,类型不是容器类型并且类型是抽象的
if (genericType != null && !type.isContainerType()
&& Modifier.isAbstract(type.getRawClass().getModifiers())) {
// 则使用对象的实际类来构建 JavaType
type = this.objectMapper.constructType(objectToConvert.getClass());
}
// 将 JavaType 映射为 messageProperties
getJavaTypeMapper().fromJavaType(type, messageProperties);
}
else {
// 如果 getClassMapper() 不为 null,则使用 getClassMapper() 来转换
getClassMapper().fromClass(objectToConvert.getClass(), messageProperties);
}
// 使用字节和 messageProperties 来创建一个新的消息对象并返回
return new Message(bytes, messageProperties);
}
SimpleMessageConverter
这个转换器会检查传入对象的类型是否为 byte[]
、String
或者 Serializable
。
如果对象是 byte[]
,那么直接设置消息的内容类型为 bytes
,并将对象内容装换为 byte[]
数组。
如果对象是 String
,那么设置消息的内容类型为 text/plain,并将 String
对象转化为 byte[]
数组。
如果对象是 Serializable
(可以序列化的),那么设置消息的内容类型为 serialized object,并将 Serializable 对象
转化为 byte[]
。
如果对象不满足上面三种要求,就不能被处理,存在一定的局限性。
@Override
protected Message createMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
byte[] bytes = null;
// 依次判断消息类型是否是byte[]?是否是String?是否可以实现Serializable接口?
if (object instanceof byte[]) {
bytes = (byte[]) object;
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_BYTES);
}
else if (object instanceof String) {
try {
bytes = ((String) object).getBytes(this.defaultCharset);
}
catch (UnsupportedEncodingException e) {
throw new MessageConversionException(
"failed to convert to Message content", e);
}
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
messageProperties.setContentEncoding(this.defaultCharset);
}
else if (object instanceof Serializable) {
try {
bytes = SerializationUtils.serialize(object);
}
catch (IllegalArgumentException e) {
throw new MessageConversionException(
"failed to convert to serialized Message content", e);
}
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT);
}
if (bytes != null) {
messageProperties.setContentLength(bytes.length);
return new Message(bytes, messageProperties);
}
throw new IllegalArgumentException(getClass().getSimpleName()
+ " only supports String, byte[] and Serializable payloads, received: " + object.getClass().getName());
}
消费消息
Jackson2JsonMessageConverter
消费消息的时候,Jackson2JsonMessageConverter
会先获取推断出来的Java类型,然后再将message转为推断出来的Java类型。推断出来的Java类型是根据我们注册的监听器方法得来的(也就是说,生产消息指定的数据类型和消费消息使用的数据类型是可以不一致的)。下面代码是Jackson2JsonMessageConverter
将message转换为Object的具体过程(详细介绍可以看文章:Jackson2JsonMessageConverter是如何推断出来需要反序列化为哪种Java类型)。
// doFromMessage方法,它的功能是将消息(message)转换为对象(Object)
private Object doFromMessage(Message message, Object conversionHint, MessageProperties properties, String encoding) {
Object content;
try {
// 获取从消息属性中推断出的Java类型
JavaType inferredType = this.javaTypeMapper.getInferredType(properties);
// 如果获取到的类型是接口并且不是Java原生类型(如List等),则使用投影转换器进行转换
if (inferredType != null && this.useProjectionForInterfaces && inferredType.isInterface()
&& !inferredType.getRawClass().getPackage().getName().startsWith("java.util")) {
content = this.projectingConverter.convert(message, inferredType.getRawClass());
}
// 如果转换提示是参数化类型引用(ParameterizedTypeReference),则通过此引用来进行类型转换
else if (conversionHint instanceof ParameterizedTypeReference) {
content = convertBytesToObject(message.getBody(), encoding,
this.objectMapper.getTypeFactory().constructType(
((ParameterizedTypeReference<?>) conversionHint).getType()));
}
// 当类映射器为null时
else if (getClassMapper() == null) {
// 使用Java类型映射器将消息属性转换为Java类型
JavaType targetJavaType = getJavaTypeMapper()
.toJavaType(message.getMessageProperties());
// 将字节转换为对象,编码方式和目标Java类型都是有参数给出
content = convertBytesToObject(message.getBody(),
encoding, targetJavaType);
}
else {
// 当类映射器不为空时,正在使用类映射器将消息属性映射为类
Class<?> targetClass = getClassMapper().toClass(
message.getMessageProperties());
// 将字节转化为对象,编码方式和目标Class都是由参数给出
content = convertBytesToObject(message.getBody(),
encoding, targetClass);
}
}
catch (IOException e) {
// 当转化过程出现IOException,将该异常包装为MessageConversionException后抛出
throw new MessageConversionException(
"Failed to convert Message content", e);
}
// 返回转化后的对象
return content;
}
@RabbitListener(queues = {"${enroll.common.rabbit.recommendRuleQueue}"})
@RabbitHandler
public void processRedisRecommendRule(MessageDTO<RecommendRuleEntity> tableSync, Channel channel, Message message) {
System.out.println(tableSync);
}
SimpleMessageConverter
消费消息的时候,SimpleMessageConverter
的操作比较简单,只支持字符串和Java序列化的对象。如果ContentType是text,则将字节流转为字符串;如果ContentType是javaSerializedObject,则尝试将字节流反序列化为Java对象。
public Object fromMessage(Message message) throws MessageConversionException {
// 初始化将要被返回对象的引用
Object content = null;
// 获取消息属性
MessageProperties properties = message.getMessageProperties();
// 如果消息属性不为空
if (properties != null) {
// 获取消息内容的类型
String contentType = properties.getContentType();
// 如果内容类型不为空并且内容类型是以"text"开头的
if (contentType != null && contentType.startsWith("text")) {
// 获取内容的编码方式.如果编码方式为空,则使用默认的编码方式
String encoding = properties.getContentEncoding();
if (encoding == null) {
encoding = this.defaultCharset;
}
try {
// 使用获取到的编码方式将消息内容转换为String
content = new String(message.getBody(), encoding);
}
catch (UnsupportedEncodingException e) {
throw new MessageConversionException(
"failed to convert text-based Message content", e);
}
}
// 如果内容类型是"java serialized object"
else if (contentType != null &&
contentType.equals(MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT)) {
try {
// 通过反序列化的方式将消息内容转换为Object
content = SerializationUtils.deserialize(
createObjectInputStream(new ByteArrayInputStream(message.getBody()), this.codebaseUrl));
}
catch (IOException | IllegalArgumentException | IllegalStateException e) {
throw new MessageConversionException(
"failed to convert serialized Message content", e);
}
}
}
// 如果经过前面的操作后content仍然为null,那就直接返回消息体
if (content == null) {
content = message.getBody();
}
// 返回转换后的内容
return content;
}
序列化工具的选择
SimpleMessageConverter
序列化和反序列化主要用的是 Java 内建的序列化机制。类似使用 ObjectInputStream
(一个可以读取 Java 对象的输入流)和 ObjectOutputStream
(一个可以写入 Java 对象的输出流)进行反序列化和序列化。
Jackson2JsonMessageConverter
序列化和反序列化主要用的是 Jackson JSON 库。它使用 ObjectMapper让 Java 对象和 JSON 字符串自由转换。这个库特点是灵活性高,功能强大,并且性能也非常优秀,因此广泛应用于 Java 项目中处理 JSON 数据。