消息转换器

还定义了几种用于发送和接收委托给 . 为每个方向提供一种方法:一种用于转换为 a,另一种用于 . 请注意,在转换为 时,除了对象之外,还可以提供属性。 该参数通常对应于 Message body。 下面的清单显示了接口定义:AmqpTemplateMessageConverterMessageConverterMessageMessageMessageobjectMessageConverterspring-doc.cn

public interface MessageConverter {

    Message toMessage(Object object, MessageProperties messageProperties)
            throws MessageConversionException;

    Object fromMessage(Message message) throws MessageConversionException;

}

上的相关 -sending 方法比我们之前讨论的方法更简单,因为它们不需要实例。 相反,它负责通过将提供的对象转换为正文的字节数组,然后添加任何提供的任何 . 下面的清单显示了各种方法的定义:MessageAmqpTemplateMessageMessageConverterMessageMessageMessagePropertiesspring-doc.cn

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” 属性。 下面的清单显示了这两种方法的定义:spring-doc.cn

Object receiveAndConvert() throws AmqpException;

Object receiveAndConvert(String queueName) throws AmqpException;
异步 Consumer 中提到的也使用了 .MessageListenerAdapterMessageConverter

SimpleMessageConverter

该策略的默认实现称为 。 这是 的实例使用的转换器(如果您未显式配置替代项)。 它处理基于文本的内容、序列化的 Java 对象和字节数组。MessageConverterSimpleMessageConverterRabbitTemplatespring-doc.cn

Message

如果输入的内容类型以 “text” 开头(例如, “text/plain”),它还会检查 content-encoding 属性,以确定在将 body 字节数组转换为 Java 时要使用的字符集。 如果未在 input 上设置 content-encoding 属性,则默认情况下,它使用 UTF-8 字符集。 如果需要覆盖该默认设置,则可以配置 的实例 ,设置其属性,并将其注入到实例中。MessageMessageStringMessageSimpleMessageConverterdefaultCharsetRabbitTemplatespring-doc.cn

如果输入的 content-type 属性值设置为“application/x-java-serialized-object”,则尝试将字节数组反序列化(解除冻结)为 Java 对象。 虽然这可能对简单的原型设计很有用,但我们不建议依赖 Java 序列化,因为它会导致生产者和消费者之间紧密耦合。 当然,它也排除了任何一方使用非 Java 系统的可能性。 由于 AMQP 是一种有线级协议,因此在此类限制下失去大部分优势将是很遗憾的。 在接下来的两节中,我们将探讨一些不依赖 Java 序列化的情况下传递丰富领域对象内容的替代方案。MessageSimpleMessageConverterspring-doc.cn

对于所有其他内容类型,the 将 body 内容直接作为字节数组返回。SimpleMessageConverterMessagespring-doc.cn

有关重要信息,请参阅 Java 反序列化spring-doc.cn

转换为Message

从任意 Java 对象转换为 a 时,同样会处理字节数组、字符串和可序列化实例。 它将每个 Cookie 转换为 bytes (在 byte 数组的情况下,没有要转换的内容),并相应地设置 content-type 属性。 如果要转换的 the 与这些类型之一不匹配,则 body 为 null。MessageSimpleMessageConverterObjectMessagespring-doc.cn

SerializerMessageConverter

这个转换器与 Obvious Similar to the 不同之处在于它可以配置其他 Spring Framework 和实现进行转换。SimpleMessageConverterSerializerDeserializerapplication/x-java-serialized-objectspring-doc.cn

有关重要信息,请参阅 Java 反序列化spring-doc.cn

Jackson2JsonMessage转换器

本节介绍如何使用 与 转换为 . 它包含以下部分:Jackson2JsonMessageConverterMessagespring-doc.cn

转换为Message

如上一节所述,通常不建议依赖 Java 序列化。 JSON 是一种相当常见的替代方案,它在不同语言和平台之间更加灵活和可移植 (JavaScript 对象表示法)。 可以在任何实例上配置转换器以覆盖其对默认值的使用。 使用 2.x 库。 以下示例配置了一个 :RabbitTemplateSimpleMessageConverterJackson2JsonMessageConvertercom.fasterxml.jacksonJackson2JsonMessageConverterspring-doc.cn

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

如上所示,默认情况下使用 a。 类型信息将添加到 (和检索) 。 如果入站消息在 中不包含类型信息,但您知道预期的类型,则 可以使用 property 配置 static 类型,如下例所示:Jackson2JsonMessageConverterDefaultClassMapperMessagePropertiesMessagePropertiesdefaultTypespring-doc.cn

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

此外,您还可以根据标头中的值提供自定义映射。 以下示例显示了如何执行此操作:TypeIdspring-doc.cn

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

现在,如果发送系统将标头设置为 ,则转换器将创建一个对象,依此类推。 有关从非 Spring 应用程序转换消息的完整讨论,请参阅从非 Spring 应用程序接收 JSON 示例应用程序。thing1Thing1spring-doc.cn

从版本 2.4.3 开始,如果 具有参数,则转换器将不会添加 message 属性;这也用于编码。 添加了一个新方法:contentEncodingsupportedMediaTypecharsetsetSupportedMediaTypespring-doc.cn

String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));

Message

入站消息根据发送系统添加到 Headers 的类型信息转换为对象。spring-doc.cn

从版本 2.4.3 开始,如果没有 message 属性,转换器将尝试检测 message 属性中的参数并使用该参数。 如果两者都不存在,如果 具有参数,则它将用于解码,并最终回退到该属性。 添加了一个新方法:contentEncodingcharsetcontentTypesupportedMediaTypecharsetdefaultCharsetsetSupportedMediaTypespring-doc.cn

String utf16 = "application/json; charset=utf-16";
converter.setSupportedContentType(MimeTypeUtils.parseMimeType(utf16));

在 1.6 之前的版本中,如果类型信息不存在,则转换将失败。 从版本 1.6 开始,如果缺少类型信息,转换器将使用 Jackson 默认值(通常是 map)转换 JSON。spring-doc.cn

此外,从版本 1.6 开始,当您使用注释(在方法上)时,推断的类型信息将添加到 . 这允许转换器转换为目标方法的参数类型。 仅当存在一个没有注释的参数或单个参数带有注释时,这才适用。 在分析过程中,将忽略 type 参数。@RabbitListenerMessageProperties@PayloadMessagespring-doc.cn

默认情况下,推断的类型信息将覆盖创建的入站和相关标头 通过发送系统。 这允许接收系统自动转换为不同的域对象。 仅当参数类型是具体的(不是抽象的或接口的)或来自包时,这才适用。 在所有其他情况下,使用 和 related 标头。 在某些情况下,您可能希望覆盖默认行为并始终使用该信息。 例如,假设您有一个 a 接受一个参数,但消息包含一个 that 是 (which is concrete) 的子类。 推断的类型将不正确。 要处理这种情况,请将 上的 属性设置为 的默认值 . (该属性实际上位于 converter 的 上,但在 converter 上提供了一个 setter 为方便起见。 如果你注入了一个自定义类型 mapper,你应该在 mapper 上设置 property 。TypeIdjava.utilTypeIdTypeId@RabbitListenerThing1Thing2Thing1TypePrecedenceJackson2JsonMessageConverterTYPE_IDINFERREDDefaultJackson2JavaTypeMapper
从 转换时,传入的内容必须符合 JSON 标准( 用于检查)。 从版本 2.2 开始,如果没有 property 或它具有默认值,则假定为 。 要恢复到之前的行为(返回 unconverted),请将转换器的属性设置为 。 如果内容类型不受支持,则会发出一条日志消息 ,并按原样返回 — 作为 . 因此,为了满足使用者端的要求,生成者必须添加 message 属性 — 例如,as 或 or 通过使用 ,它会自动设置标头。 下面的清单显示了许多 converter 调用:MessageMessageProperties.getContentType()contentType.contains("json")application/jsoncontentTypeapplication/octet-streambyte[]assumeSupportedContentTypefalseWARNCould not convert incoming message with content-type […​]message.getBody()byte[]Jackson2JsonMessageConvertercontentTypeapplication/jsontext/x-jsonJackson2JsonMessageConverter
@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) {...}

在前面清单中的前四种情况下,转换器尝试转换为 type。 第五个示例无效,因为我们无法确定哪个参数应该接收消息有效负载。 在第六个示例中,由于泛型类型是 .Thing1WildcardTypespring-doc.cn

但是,您可以创建自定义转换器并使用 message 属性来决定要转换的类型 JSON 到targetMethodspring-doc.cn

只有在方法级别声明 Comments 时,才能实现这种类型推断。 使用 class-level ,转换后的类型用于选择要调用的方法。 因此,基础架构提供了 message 属性,您可以在自定义 converter 来确定类型。@RabbitListener@RabbitListener@RabbitHandlertargetObject
从版本 1.6.11 开始,因此 () 提供了克服序列化小工具漏洞的选项。 默认情况下,为了向后兼容,s 信任所有软件包 — 也就是说,它用于 option。Jackson2JsonMessageConverterDefaultJackson2JavaTypeMapperDefaultClassMappertrustedPackagesJackson2JsonMessageConverter*

从版本 2.4.7 开始,可以将转换器配置为在 Jackson 在反序列化消息正文后返回时返回。 这有助于 s 以两种方式接收 null 有效负载:Optional.empty()null@RabbitListenerspring-doc.cn

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

要启用此功能,请设置为 ;when (default) 时,转换器将回退到原始消息正文 ()。setNullAsOptionalEmptytruefalsebyte[]spring-doc.cn

@Bean
Jackson2JsonMessageConverter converter() {
    Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
    converter.setNullAsOptionalEmpty(true);
    return converter;
}

反序列化抽象类

在版本 2.2.8 之前,如果 a 的推断类型是抽象类(包括接口),则转换器将回退到在 Headers 中查找类型信息,如果存在,则使用该信息;如果不存在,它将尝试创建 Abstract 类。 当使用配置了自定义反序列化器来处理抽象类的自定义,但传入消息具有无效的类型标头时,这会导致问题。@RabbitListenerObjectMapperspring-doc.cn

从版本 2.2.8 开始,默认情况下保留之前的行为。如果您有此类自定义,并且希望忽略类型标头,并始终使用推断的类型进行转换,请将 设置为 。 这是向后兼容所必需的,并避免在转换失败时尝试转换的开销(使用 standard )。ObjectMapperalwaysConvertToInferredTypetrueObjectMapperspring-doc.cn

使用 Spring Data Projection 接口

从版本 2.2 开始,你可以将 JSON 转换为 Spring Data Projection 接口,而不是具体类型。 这允许对数据进行非常有选择性的低耦合绑定,包括从 JSON 文档中的多个位置查找值。 例如,可以将以下接口定义为消息有效负载类型:spring-doc.cn

interface SomeSample {

  @JsonPath({ "$.username", "$.user.name" })
  String getUsername();

}
@RabbitListener(queues = "projection")
public void projection(SomeSample in) {
    String username = in.getUsername();
    ...
}

默认情况下,将使用访问器方法在接收到的 JSON 文档中查找属性名称作为字段。 该表达式允许自定义值查找,甚至定义多个 JSON 路径表达式,以便从多个位置查找值,直到表达式返回实际值。@JsonPathspring-doc.cn

要启用此功能,请在消息转换器上设置 to。 还必须将 and 添加到类路径中。useProjectionForInterfacestruespring-data:spring-data-commonscom.jayway.jsonpath:json-pathspring-doc.cn

当用作方法的参数时,接口类型会像往常一样自动传递给转换器。@RabbitListenerspring-doc.cn

从 a 转换 withMessageRabbitTemplate

如前所述,类型信息在消息标头中传达,以帮助转换器从消息转换。 这在大多数情况下效果很好。 但是,当使用泛型类型时,它只能转换简单对象和已知的 “容器” 对象(列表、数组和映射)。 从版本 2.0 开始, implements ,这允许它与采用参数的新方法一起使用。 这允许转换复杂的泛型类型,如以下示例所示:Jackson2JsonMessageConverterSmartMessageConverterRabbitTemplateParameterizedTypeReferencespring-doc.cn

Thing1<Thing2<Cat, Hat>> thing1 =
    rabbitTemplate.receiveAndConvert(new ParameterizedTypeReference<Thing1<Thing2<Cat, Hat>>>() { });
从版本 2.1 开始,该类已被删除。 它不再是 的基类。 它已被 取代。AbstractJsonMessageConverterJackson2JsonMessageConverterAbstractJackson2MessageConverter

MarshallingMessageConverter

另一个选项是 . 它委托给 Spring OXM 库的 和 strategy 接口的实现。 您可以在此处阅读有关该库的更多信息。 在配置方面,最常见的是只提供 constructor 参数,因为大多数 的实现也实现 。 以下示例显示如何配置 :MarshallingMessageConverterMarshallerUnmarshallerMarshallerUnmarshallerMarshallingMessageConverterspring-doc.cn

<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 相互转换。spring-doc.cn

两者具有相同的基类:.Jackson2XmlMessageConverterJackson2JsonMessageConverterAbstractJackson2MessageConverterspring-doc.cn

引入该类是为了替换已删除的类 .AbstractJackson2MessageConverterAbstractJsonMessageConverter

使用 2.x 库。Jackson2XmlMessageConvertercom.fasterxml.jacksonspring-doc.cn

您可以像 一样使用它,只是它支持 XML 而不是 JSON。 以下示例配置了一个 :Jackson2JsonMessageConverterJackson2JsonMessageConverterspring-doc.cn

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

有关更多信息,请参见Jackson2JsonMessageConverterspring-doc.cn

从版本 2.2 开始,如果没有 property 或它具有默认值,则假定为 。 要恢复到之前的行为(返回 unconverted),请将转换器的属性设置为 。application/xmlcontentTypeapplication/octet-streambyte[]assumeSupportedContentTypefalse

ContentTypeDelegatingMessageConverter

此类是在版本 1.4.2 中引入的,它允许根据 . 默认情况下,如果没有属性或存在与任何已配置的转换器都不匹配的值,则它会委托给 a。 以下示例配置了一个 :MessageConverterMessagePropertiesSimpleMessageConvertercontentTypeContentTypeDelegatingMessageConverterspring-doc.cn

<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 反序列化

本节介绍如何反序列化 Java 对象。spring-doc.cn

从不受信任的来源反序列化 java 对象时可能存在漏洞。spring-doc.cn

如果您接受来自不受信任的来源的邮件,并且 为 ,则应 请考虑配置允许反序列化的包和类。 这适用于 和 当它配置为隐式或通过配置使用 a 时。content-typeapplication/x-java-serialized-objectSimpleMessageConverterSerializerMessageConverterDefaultDeserializerspring-doc.cn

默认情况下,允许的列表为空,这意味着不会反序列化任何类。spring-doc.cn

您可以设置模式列表,例如 thing1.thing2.Cat.thing1..MySafeClassspring-doc.cn

按顺序检查模式,直到找到匹配项。 如果没有匹配项,则抛出 a。SecurityExceptionspring-doc.cn

您可以使用这些转换器上的属性设置模式。 或者,如果您信任所有消息发起方,则可以将环境变量或 system 属性设置为 。allowedListPatternsSPRING_AMQP_DESERIALIZATION_TRUST_ALLspring.amqp.deserialization.trust.alltruespring-doc.cn

消息属性转换器

策略接口用于在 Rabbit Client 和 Spring AMQP 之间进行转换。 默认实现 () 通常足以满足大多数用途,但如果需要,您可以实现自己的实现。 默认 properties 转换器在大小不大于 bytes 时将 type 元素转换为实例。 较大的实例不会被转换(请参阅下一段)。 可以使用 constructor 参数覆盖此限制。MessagePropertiesConverterBasicPropertiesMessagePropertiesDefaultMessagePropertiesConverterBasicPropertiesLongStringString1024LongStringspring-doc.cn

从版本 1.6 开始,长于长字符串限制(默认值:1024)的标头现在默认由 . 您可以通过 、 或 方法访问内容。LongStringDefaultMessagePropertiesConvertergetBytes[]toString()getStream()spring-doc.cn

以前,将此类标头“转换”为 a(实际上它只是引用了实例的 )。 在输出时,此标头未被转换(除了转换为 String — 例如,通过调用流)。DefaultMessagePropertiesConverterDataInputStreamLongStringDataInputStreamjava.io.DataInputStream@1d057a39toString()spring-doc.cn

大型传入标头现在也可以在输出时正确 “转换” (默认情况下)。LongStringspring-doc.cn

提供了一个新的构造函数,允许您将转换器配置为像以前一样工作。 下面的清单显示了该方法的 Javadoc 注释和声明:spring-doc.cn

/**
 * 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 开始,一个名为 的新属性已添加到 . 以前,当 RabbitMQ 客户端使用和 from 进行转换时,会执行不必要的转换,因为 是 ,但使用 . (最终,RabbitMQ 客户端使用 UTF-8 将 to bytes 转换为 bytes 以放入协议消息中)。correlationIdStringMessagePropertiesBasicPropertiesbyte[] <→ StringMessageProperties.correlationIdbyte[]BasicPropertiesStringStringspring-doc.cn

为了提供最大的向后兼容性,已将名为 的新属性添加到 . 这需要 enum 参数。 默认情况下,它设置为 ,这将复制以前的行为。correlationIdPolicyDefaultMessagePropertiesConverterDefaultMessagePropertiesConverter.CorrelationIdPolicyBYTESspring-doc.cn

对于入站消息:spring-doc.cn

对于出站消息:spring-doc.cn

此外,从版本 1.6 开始,入站属性不再映射到 。 而是映射到它。 此外,入站属性不再映射到 。 而是映射到它。 这些更改是为了避免在将同一对象用于出站消息时意外传播这些属性。deliveryModeMessageProperties.deliveryModeMessageProperties.receivedDeliveryModeuserIdMessageProperties.userIdMessageProperties.receivedUserIdMessagePropertiesspring-doc.cn

从版本 2.2 开始,将转换任何值为 using 而不是 ;这避免了 Consumer 应用程序必须从表示中解析类名。 对于滚动升级,您可能需要更改使用者以理解这两种格式,直到所有创建器都升级为止。DefaultMessagePropertiesConverterClass<?>getName()toString()toString()spring-doc.cn