Message Converters
AmqpTemplate
还为发送和接收消息定义了几个委托给 MessageConverter
的方法。MessageConverter
为每个方向提供一种方法:一种用于转换为 Message
,另一种用于从 Message
转换。请注意,在转换为 Message
时,除了对象外,还可以提供属性。object
参数通常对应于消息主体。以下清单显示了 MessageConverter
接口定义:
public interface MessageConverter {
Message toMessage(Object object, MessageProperties messageProperties)
throws MessageConversionException;
Object fromMessage(Message message) throws MessageConversionException;
}
AmqpTemplate
上相关的 Message
发送方法比我们之前讨论的方法更简单,因为它们不需要 Message
实例。相反,MessageConverter
负责通过将提供对象转换为 Message
主体的字节数组,然后添加任何提供的 MessageProperties
来“创建”每个 Message
。以下清单显示了各种方法的定义:
void convertAndSend(Object message) throws AmqpException;
void convertAndSend(String routingKey, Object message) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message)
throws AmqpException;
void convertAndSend(Object message, MessagePostProcessor messagePostProcessor)
throws AmqpException;
void convertAndSend(String routingKey, Object message,
MessagePostProcessor messagePostProcessor) throws AmqpException;
void convertAndSend(String exchange, String routingKey, Object message,
MessagePostProcessor messagePostProcessor) throws AmqpException;
在接收端,只有两种方法:一种接受队列名称,另一种依赖于模板的“queue
”属性已被设置。以下清单显示了这两个方法的定义:
Object receiveAndConvert() throws AmqpException;
Object receiveAndConvert(String queueName) throws AmqpException;
Asynchronous Consumer 中提到的 |
SimpleMessageConverter
MessageConverter
策略的默认实现称为 SimpleMessageConverter
。这是 RabbitTemplate
实例在你未显式配置替代项时使用的转换器。它可以处理基于文本的内容、序列化的 Java 对象和字节数组。
Converting From a Message
如果输入 Message
的内容类型以“text”开头(例如,“text/plain”),它还会检查内容编码属性,以确定在将 Message
主体字节数组转换为 Java String
时要使用的字符集。如果在输入 Message
上未设置内容编码属性,则它默认使用 UTF-8 字符集。如果你需要覆盖该默认设置,你可以配置 SimpleMessageConverter
实例、设置其 defaultCharset
属性并将其注入 RabbitTemplate
实例。
如果输入 Message
的内容类型属性值设置为“application/x-java-serialized-object”,则 SimpleMessageConverter
尝试将字节数组反序列化(rehydrate)为 Java 对象。虽然这对于简单的原型制作可能很有用,但我们不建议依赖 Java 序列化,因为它会导致生产者和消费者之间紧密耦合。当然,它还排除了在任何一方使用非 Java 系统。由于 AMQP 是一个线缆级协议,因此使用此类限制会失去很多优势,这是非常不幸的。在接下来的两节中,我们将探讨一些在不依赖于 Java 序列化的前提下传递丰富域对象内容的备选方案。
对于所有其他内容类型,SimpleMessageConverter
将 Message
主体内容直接作为字节数组返回。
有关重要信息,请参见 Java Deserialization。
SerializerMessageConverter
此转换器类似于 SimpleMessageConverter
,只不过它可以针对用于 application/x-java-serialized-object
转换的其它 Spring Framework Serializer
和 Deserializer
实现进行配置。
有关重要信息,请参见 Java Deserialization。
Jackson2JsonMessageConverter
本节涵盖使用 Jackson2JsonMessageConverter
转换到 Message
和从 Message
转换。它包含以下部分:
Converting to a Message
如前一节所述,通常不建议依赖 Java 序列化。一种更灵活且更便于在不同语言和平台上移植的常见备选方案是 JSON(JavaScript 对象表示法)。可以在任何 RabbitTemplate
实例上配置转换器,以覆盖其对 SimpleMessageConverter
默认值的用法。Jackson2JsonMessageConverter
使用 com.fasterxml.jackson
2.x 库。以下示例配置了 Jackson2JsonMessageConverter
:
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
<!-- if necessary, override the DefaultClassMapper -->
<property name="classMapper" ref="customClassMapper"/>
</bean>
</property>
</bean>
如上所示,Jackson2JsonMessageConverter
默认使用 DefaultClassMapper
。类型信息被添加到(并从)MessageProperties
中检索。如果入站消息在 MessageProperties
中不包含类型信息,但是你知道预期类型,则可以使用 defaultType
属性配置静态类型,如下例所示:
<bean id="jsonConverterWithDefaultType"
class="o.s.amqp.support.converter.Jackson2JsonMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="thing1.PurchaseOrder"/>
</bean>
</property>
</bean>
此外,你可以从 TypeId
中的值提供自定义映射。以下示例演示如何执行此操作:
@Bean
public Jackson2JsonMessageConverter jsonMessageConverter() {
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
jsonConverter.setClassMapper(classMapper());
return jsonConverter;
}
@Bean
public DefaultClassMapper classMapper() {
DefaultClassMapper classMapper = new DefaultClassMapper();
Map<String, Class<?>> idClassMapping = new HashMap<>();
idClassMapping.put("thing1", Thing1.class);
idClassMapping.put("thing2", Thing2.class);
classMapper.setIdClassMapping(idClassMapping);
return classMapper;
}
现在,如果发送系统将标头设置为 thing1
,转换器将创建一个 Thing1
对象,依此类推。请参阅 Receiving JSON from Non-Spring Applications 的示例应用程序,以获取有关从非 Spring 应用程序转换消息的完整讨论。
从版本 2.4.3 开始,如果 supportedMediaType
有 charset
参数,转换器将不会添加 contentEncoding
消息属性;这也用于编码。已经添加了一个新方法 setSupportedMediaType
:
String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));
Converting from a Message
入站消息根据发送系统添加到标头中的类型信息转换为对象。
从版本 2.4.3 开始,如果没有 contentEncoding
消息属性,转换器将尝试在 contentType
消息属性中检测 charset
参数并使用它。如果都不存在,如果 supportedMediaType
有 charset
参数,它将用于解码,最终回退到 defaultCharset
属性。已经添加了一个新方法 setSupportedMediaType
:
String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));
在 1.6 版本之前,如果不存在类型信息,转换将会失败。从 1.6 版本开始,如果缺少类型信息,转换器将使用 Jackson 默认值(通常是一个映射)将 JSON 转换。
另外,从 1.6 版本开始,当您使用 @RabbitListener
注解(在方法上)时,推断出的类型信息将被添加到 MessageProperties
中。这使转换器能够转换为目标方法的参数类型。这仅适用于没有注解的一个参数,或者用 @Payload
注解的单个参数。在分析过程中,将忽略类型为 Message
的参数。
默认情况下,推断出的类型信息将覆盖传入的 TypeId
和发送系统创建的相关标头。这使得接收系统可以自动转换为不同的域对象。仅当参数类型是具体的(不是抽象的或接口)或来自 java.util
包时,此规则才适用。在所有其他情况下,将使用 TypeId
和相关标头。在某些情况下,您可能希望覆盖默认行为并始终使用 TypeId
信息。例如,假设您有一个 @RabbitListener
,它接受 Thing1
参数,但消息中包含的是 Thing2
(它是 Thing1
的子类)(具体)。推断出的类型将不正确。为了处理这种情况,请将 Jackson2JsonMessageConverter
上的 TypePrecedence
属性设置为 TYPE_ID
,而不是默认的 INFERRED
。(该属性实际上在转换器的 DefaultJackson2JavaTypeMapper
上,但为方便起见在转换器上提供了一个 setter。)如果您注入自定义类型映射器,则应转而设置映射器上的属性。
从 |
@RabbitListener
public void thing1(Thing1 thing1) {...}
@RabbitListener
public void thing1(@Payload Thing1 thing1, @Header("amqp_consumerQueue") String queue) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.amqp.core.Message message) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<Foo> message) {...}
@RabbitListener
public void thing1(Thing1 thing1, String bar) {...}
@RabbitListener
public void thing1(Thing1 thing1, o.s.messaging.Message<?> message) {...}
在前面列表中的前四个案例中,转换器尝试转换为 Thing1
类型。第五个示例无效,因为我们无法确定哪个参数应该接收消息有效载荷。对于第六个示例,由于泛型类型是 WildcardType
,因此应用 Jackson 默认值。
不过,您可以创建自定义转换器并使用 targetMethod
消息属性来决定将 JSON 转换为哪种类型。
只有在方法级别声明 |
从 1.6.11 版本开始,Jackson2JsonMessageConverter
及其 DefaultJackson2JavaTypeMapper
(DefaultClassMapper
) 提供了 trustedPackages
选项,用于克服 Serialization Gadgets 漏洞。对于向后兼容性,Jackson2JsonMessageConverter
默认情况下信任所有包——也就是说,它对该选项使用 *
。
从 2.4.7 版本开始,如果 Jackson在反序列化消息体后返回 null
,则可以配置转换器以返回 Optional.empty()
。这为 @RabbitListener
提供了接收空负载的两种方式:
@RabbitListener(queues = "op.1")
void listen(@Payload(required = false) Thing payload) {
handleOptional(payload); // payload might be null
}
@RabbitListener(queues = "op.2")
void listen(Optional<Thing> optional) {
handleOptional(optional.orElse(this.emptyThing));
}
要启用此功能,请将 setNullAsOptionalEmpty
设置为 true
;当 false
(默认)时,转换器将回退到原始消息正文 (byte[]
)。
@Bean
Jackson2JsonMessageConverter converter() {
Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
converter.setNullAsOptionalEmpty(true);
return converter;
}
Deserializing Abstract Classes
在 2.2.8 版本之前,如果 @RabbitListener
的推断类型是抽象类(包括接口),则转换器将回退到标头中查找类型信息,如果存在,则会使用该信息;如果不存在,它将尝试创建抽象类。在使用了配置有自定义反序列化程序来处理抽象类的自定义 ObjectMapper
时,但传入消息具有无效类型标头时,这会引起问题。
从 2.2.8 版本开始,将默认保留以前的行为。如果您有这样的自定义 ObjectMapper
,并且您想忽略类型标头,并始终使用推断的类型进行转换,则将 alwaysConvertToInferredType
设置为 true
。这对于向后兼容性和避免在失败时尝试转换(使用标准 ObjectMapper
)的开销是必要的。
Using Spring Data Projection Interfaces
从 2.2 版本开始,您可以将 JSON 转换为 Spring Data 投影接口,而不是具体类型。这允许对数据进行非常有选择性和低耦合的绑定,包括从 JSON 文档中的多个位置查找值。例如,可以将以下接口定义为消息有效负载类型:
interface SomeSample {
@JsonPath({ "$.username", "$.user.name" })
String getUsername();
}
@RabbitListener(queues = "projection")
public void projection(SomeSample in) {
String username = in.getUsername();
...
}
默认情况下,访问器方法将用于在接收的 JSON 文档中作为字段查找属性名称。@JsonPath
表达式允许对值查找进行自定义,甚至可以定义多个 JSON 路径表达式,以从多个位置查找值,直到表达式返回实际值。
要启用此功能,请在消息转换器上将 useProjectionForInterfaces
设置为 true
。您还必须将 spring-data:spring-data-commons
和 com.jayway.jsonpath:json-path
添加到类路径。
当用作 @RabbitListener
方法的参数时,接口类型将如常自动传递给转换器。
Converting From a Message
With RabbitTemplate
如前所述,类型信息在消息标头中传递,以在从消息转换时帮助转换器。在大多数情况下,这工作正常。但是,当使用泛型类型时,它只能转换简单对象和已知的 “容器” 对象(列表、数组和映射)。从 2.0 版本开始,Jackson2JsonMessageConverter
实现 SmartMessageConverter
,它允许与采用 ParameterizedTypeReference
参数的新 RabbitTemplate
方法一起使用。这允许转换复杂的泛型类型,如下面的示例所示:
Thing1<Thing2<Cat, Hat>> thing1 =
rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
从 2.1 版本开始,已删除 |
MarshallingMessageConverter
另一个选项是 MarshallingMessageConverter
。它将委托给 Spring OXM
库的 Marshaller
和 Unmarshaller
策略界面的实现。您可以在这里阅读有关该库的更多信息:https://docs.spring.io/spring/docs/current/spring-framework-reference/html/oxm.html。在配置方面,最常见的是只提供构造函数参数,因为大多数 Marshaller
的实现也实现了 Unmarshaller
。以下示例演示如何配置 MarshallingMessageConverter
:
<bean class="org.springframework.amqp.rabbit.core.RabbitTemplate">
<property name="connectionFactory" ref="rabbitConnectionFactory"/>
<property name="messageConverter">
<bean class="org.springframework.amqp.support.converter.MarshallingMessageConverter">
<constructor-arg ref="someImplemenationOfMarshallerAndUnmarshaller"/>
</bean>
</property>
</bean>
Jackson2XmlMessageConverter
此类在 2.1 版本中引入,可用于将消息从 XML 转换到 XML,反之亦然。
Jackson2XmlMessageConverter
和 Jackson2JsonMessageConverter
都具有相同的基类:AbstractJackson2MessageConverter
。
|
Jackson2XmlMessageConverter
使用 com.fasterxml.jackson
2.x 库。
您可以以与 Jackson2JsonMessageConverter
相同的方式使用它,只是它支持 XML,而不是 JSON。以下示例配置 Jackson2JsonMessageConverter
:
<bean id="xmlConverterWithDefaultType"
class="org.springframework.amqp.support.converter.Jackson2XmlMessageConverter">
<property name="classMapper">
<bean class="org.springframework.amqp.support.converter.DefaultClassMapper">
<property name="defaultType" value="foo.PurchaseOrder"/>
</bean>
</property>
</bean>
有关详细信息,请参阅 Jackson2JsonMessageConverter。
从 2.2 版本开始,如果不存在 |
ContentTypeDelegatingMessageConverter
此类在 1.4.2 版本中引入,允许根据 MessageProperties
中的 contentType
属性,委派给特定的 MessageConverter
。默认情况下,如果不存在 contentType
属性或存在与任何已配置转换器都不匹配的值,则它委派给 SimpleMessageConverter
。以下示例配置 ContentTypeDelegatingMessageConverter
:
<bean id="contentTypeConverter" class="ContentTypeDelegatingMessageConverter">
<property name="delegates">
<map>
<entry key="application/json" value-ref="jsonMessageConverter" />
<entry key="application/xml" value-ref="xmlMessageConverter" />
</map>
</property>
</bean>
Java Deserialization
本节介绍如何反序列化 Java 对象。
从不可信任的来源反序列化 Java 对象时,有一个可能的漏洞。
如果接受来自不可信来源且 content-type
为 application/x-java-serialized-object
的消息,则应考虑配置允许反序列化的包和类。它适用于显式或通过配置使用 DefaultDeserializer
配置为使用 SimpleMessageConverter
和 SerializerMessageConverter
的情况。
默认情况下,允许列表为空,这意味着不会对任何类进行反序列化。
您可以设置模式列表,例如 thing1.
, thing1.thing2.Cat
或 .MySafeClass
。
模式按顺序检查,直到找到匹配项。如果没有匹配项,则会抛出 SecurityException
。
您可以使用这些转换器上的 allowedListPatterns
属性设置模式。或者,如果您信任所有消息发起者,则可以将环境变量 SPRING_AMQP_DESERIALIZATION_TRUST_ALL
或系统属性 spring.amqp.deserialization.trust.all
设置为 true
。
Message Properties Converters
MessagePropertiesConverter
strategy 接口用于在 Rabbit 客户端 BasicProperties
和 Spring AMQP MessageProperties
之间进行转换。默认实现 (DefaultMessagePropertiesConverter
) 通常足以满足大多数目的,但您可以在需要时实现自己的实现。默认属性转换器在大小不超过 1024
字节时将 BasicProperties
类型为 LongString
的元素转换为 String
实例。不会转换较大的 LongString
实例(请参见下一段)。此限制可以通过构造函数参数覆盖。
从 1.6 版本开始,长度超过长字符串限制(默认:1024)的标头现在由 DefaultMessagePropertiesConverter
默认保留为 LongString
实例。您可以通过 getBytes[]
、toString()`或 `getStream()
方法访问内容。
以前,DefaultMessagePropertiesConverter
将此类标头“转换”为 DataInputStream
(实际上它只是引用了 LongString
实例的 DataInputStream
)。在输出时,此标头没有被转换(除了转换为 String 之外,例如,toString()
调用流时为 java.io.DataInputStream@1d057a39
)。
现在,较大的传入 LongString
标头也在输出时正确“转换”(默认情况下)。
提供了一个新构造函数,可以让您将转换器配置为以前一样工作。以下清单显示了该方法的 Javadoc 注释和声明:
/**
* Construct an instance where LongStrings will be returned
* unconverted or as a java.io.DataInputStream when longer than this limit.
* Use this constructor with 'true' to restore pre-1.6 behavior.
* @param longStringLimit the limit.
* @param convertLongLongStrings LongString when false,
* DataInputStream when true.
* @since 1.6
*/
public DefaultMessagePropertiesConverter(int longStringLimit, boolean convertLongLongStrings) { ... }
同样,从 1.6 版本开始,MessageProperties
中添加了一个名为 correlationIdString
的新属性。以前,当使用 RabbitMQ 客户端使用的 BasicProperties
进行转换时,会执行不必要的 byte[] <→ String
转换,因为 MessageProperties.correlationId
是 byte[]
,而 BasicProperties
使用 String
。(最终,RabbitMQ 客户端使用 UTF-8 将 String
转换为字节以放入协议消息中。)
为了提供最大的向后兼容性,已向 DefaultMessagePropertiesConverter
添加了一个名为 correlationIdPolicy
的新属性。这需要一个 DefaultMessagePropertiesConverter.CorrelationIdPolicy
枚举参数。默认情况下,它设置为 BYTES
,这复制了以前的行为。
对于入站消息:
-
STRING
:只映射correlationIdString
属性 -
BYTES
:只映射correlationId
属性 -
BOTH
:映射这两个属性
对于出站消息:
-
STRING
:只映射correlationIdString
属性 -
BYTES
:只映射correlationId
属性 -
BOTH
:考虑这两个属性,同时String
属性优先
同样从版本 1.6 开始,入站 deliveryMode
属性不再映射到 MessageProperties.deliveryMode
。它改为映射到 MessageProperties.receivedDeliveryMode
。此外,入站 userId
属性不再映射到 MessageProperties.userId
。它改为映射到 MessageProperties.receivedUserId
。这些更改是为了避免在将相同的 MessageProperties
对象用于出站消息时意外传播这些属性。
从版本 2.2 开始,DefaultMessagePropertiesConverter
将使用 getName()
而非 toString()
转换任何值类型为 Class<?>
的自定义标头;这避免了使用应用程序必须从 toString()
表示解析类名的消费。对于滚动升级,您可能需要更改使用者,以便在升级所有生产者之前理解这两种格式。