消息转换

消息转换

转换器

消息转换器在实现消息生产者和消息消费者的解耦方面发挥着非常重要的作用。 无需要求每个消息生产组件都知道下一个消费者期望的类型,您可以在这些组件之间添加转换器。 通用转换器(例如将String转换为 XML 文档的转换器)也具有很高的可重用性。spring-doc.cadn.net.cn

对于某些系统,提供规范数据模型可能是最佳选择,但 Spring Integration 的总体理念是不强制要求任何特定格式。 相反,为了获得最大的灵活性,Spring Integration 旨在提供最简单的扩展模型。 与其他端点类型一样,使用 XML 中的声明式配置或 Java 注解,可以轻松地将普通 POJO 适配为消息转换器角色。 本章其余部分将介绍这些配置选项。spring-doc.cadn.net.cn

为了最大化灵活性,Spring 并不强制要求使用基于 XML 的消息负载。 不过,如果确实需要处理基于 XML 的负载,该框架也提供了一些便捷的转换器。 有关这些转换器的更多信息,请参见 XML 支持 - 处理 XML 负载

使用 Java 和其他 DSL 配置转换器

对于简单的 Java 和注解配置,Spring Bean POJO 方法必须标记为 @MessageMapping 注解,框架会在从输入通道消费消息时调用该方法:spring-doc.cadn.net.cn

public class SomeService {

    @Transfomer(inputChannel = "transformChannel", outputChannel = "nextServiceChannel")
    public OutputData exampleTransformer(InputData payload) {
        ...
    }

}

有关更多信息,请参阅 注解支持spring-doc.cadn.net.cn

对于 Java、Groovy 或 Kotlin DSL,.transform() 运算符中的 IntegrationFlow 表示转换端点:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("transformChannel")
             .transform(someService, "exampleTransformer")
             .channel("nextServiceChannel")
             .get();
}
Kotlin DSL
@Bean
fun someFlow() =
    integrationFlow("transformChannel") {
        transform(someService, "exampleTransformer")
        channel("nextServiceChannel")
    }
Groovy DSL
@Bean
someFlow() {
    integrationFlow 'transformChannel',
            {
                transform someService, 'exampleTransformer'
                channel 'nextServiceChannel'
            }
}

查看有关 DSL 的更多信息,请参见相应的章节:spring-doc.cadn.net.cn

使用 XML 配置转换器

<transformer>元素用于创建消息转换端点。 除了input-channeloutput-channel属性外,它还需要一个ref属性。 ref可以指向包含单个方法上@Transformer注解的对象(参见使用注解配置转换器),也可以与method属性中提供的显式方法名值结合使用。spring-doc.cadn.net.cn

<int:transformer id="testTransformer" ref="testTransformerBean" input-channel="inChannel"
             method="transform" output-channel="outChannel"/>
<beans:bean id="testTransformerBean" class="org.foo.TestTransformer" />

如果自定义转换器处理器实现可以在其他 <transformer> 定义中重用,则通常建议使用 ref 属性。 然而,如果自定义转换器处理器实现应限定在单个 <transformer> 定义范围内,则可以定义一个内部 Bean 定义,如下示例所示:spring-doc.cadn.net.cn

<int:transformer id="testTransformer" input-channel="inChannel" method="transform"
                output-channel="outChannel">
  <beans:bean class="org.foo.TestTransformer"/>
</transformer>
在同一个 <transformer> 配置中同时使用 ref 属性和内部处理器定义是不允许的,因为这会创建歧义条件并导致抛出异常。
如果 ref 属性引用了扩展自 AbstractMessageProducingHandler 的 Bean(例如框架自身提供的转换器),则配置将通过直接将输出通道注入处理器来进行优化。 在这种情况下,每个 ref 必须指向单独的 Bean 实例(或 prototype 作用域的 Bean),或者使用内部的 <bean/> 配置类型。 如果您意外地从多个 Bean 引用了同一个消息处理器,将会抛出配置异常。

当使用 POJO 时,用于转换的方法可能期望接收入站消息的 Message 类型或有效负载类型。 它还可以通过分别使用 @Header@Headers 参数注解,单独接受或作为完整映射接受消息头值。 方法的返回值可以是任何类型。 如果返回值本身是一个 Message,则将其传递给转换器的输出通道。spring-doc.cadn.net.cn

自 Spring Integration 2.0 起,消息转换器的转换方法不再允许返回 null。 返回 null 会导致异常,因为消息转换器应始终被期望将每个源消息转换为有效的目标消息。 换句话说,消息转换器不应用作消息过滤器,因为为此有专用的 <filter> 选项。 然而,如果您确实需要这种行为(即组件可能返回 null 且不应被视为错误),您可以使用服务激活器。 其 requires-reply 值默认为 false,但可以设置为 true,以便像转换器一样对 null 返回值抛出异常。spring-doc.cadn.net.cn

转换器与 Spring 表达式语言 (SpEL)

与路由器、聚合器和其他组件一样,从 Spring Integration 2.0 开始,只要转换逻辑相对简单,转换器也可以受益于 SpEL 支持。 以下示例展示了如何使用 SpEL 表达式:spring-doc.cadn.net.cn

<int:transformer input-channel="inChannel"
	output-channel="outChannel"
	expression="payload.toUpperCase() + '- [' + T(System).currentTimeMillis() + ']'"/>

上述示例在不编写自定义转换器的情况下对有效载荷进行了转换。 我们的有效载荷(假定为 String)被转换为大写,与当前时间戳连接,并应用了一些格式化。spring-doc.cadn.net.cn

常用转换器

Spring Integration 提供了一些转换器实现。spring-doc.cadn.net.cn

对象到字符串转换器

由于使用 toString() 表示法来表示 Object 的情况相当常见,Spring Integration 提供了一个 ObjectToStringTransformer(另请参阅 Transformers 工厂),其输出为带有 String payloadMessage。 该 String 是调用入站消息有效负载上的 toString() 操作的结果。 以下示例展示了如何声明一个对象到字符串转换器的实例:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("in")
             .transform(Transformers.objectToString())
             .channel("out")
             .get();
}
Kotlin DSL
@Bean
fun someFlow() =
    integrationFlow("in") {
        transform(Transformers.objectToString())
        channel("out")
    }
Groovy DSL
@Bean
someFlow() {
    integrationFlow 'in',
            {
                transform Transformers.objectToString()
                channel 'out'
            }
}
XML
<int:object-to-string-transformer input-channel="in" output-channel="out"/>

此转换器的一个潜在用途是将某些任意对象发送到 file 命名空间中的 'outbound-channel-adapter'。 虽然该通道适配器默认仅支持 String、字节数组或 java.io.File 类型的负载,但在此适配器之前立即添加此转换器即可处理必要的转换。 只要 toString() 调用的结果正是您希望写入文件的内容,这种方法就能正常工作。 否则,您可以使用前面展示的通用 'transformer' 元素提供基于自定义 POJO 的转换器。spring-doc.cadn.net.cn

在调试时,通常不需要此转换器,因为 logging-channel-adapter 能够记录消息负载。 有关更多详细信息,请参阅 Wire Tap

对象到字符串的转换器非常简单。 它对入站负载调用 toString()。 自 Spring Integration 3.0 起,此规则有两个例外:spring-doc.cadn.net.cn

  • 如果有效载荷为 char[],它将调用 new String(payload)spring-doc.cadn.net.cn

  • 如果负载是 byte[],它将调用 new String(payload, charset),其中 charset 默认为 UTF-8。 可以通过在转换器上提供 charset 属性来修改 charsetspring-doc.cadn.net.cn

如需更高级的功能(例如在运行时动态选择字符集),您可以改用基于 SpEL 表达式的转换器,如下示例所示:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("in")
             .transform("new String(payload, headers['myCharset']")
             .channel("out")
             .get();
}
XML
<int:transformer input-channel="in" output-channel="out"
       expression="new String(payload, headers['myCharset']" />

如果您需要将 Object 序列化为字节数组,或将字节数组反序列化为 Object,Spring Integration 提供了对称的序列化转换器。 这些转换器默认使用标准 Java 序列化,但您可以通过分别使用 serializerdeserializer 属性来提供 Spring SerializerDeserializer 策略的实现。 另请参阅 Transformers 工厂类。 以下示例展示了如何使用 Spring 的序列化和反序列化器:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("objectsIn")
             .transform(Transformers.serializer())
             .channel("bytesOut")
             .channel("bytesIn")
             .transform(Transformers.deserializer("com.mycom.*", "com.yourcom.*"))
             .channel("objectsOut")
             .get();
}
XML
<int:payload-serializing-transformer input-channel="objectsIn" output-channel="bytesOut"/>

<int:payload-deserializing-transformer input-channel="bytesIn" output-channel="objectsOut"
    allow-list="com.mycom.*,com.yourcom.*"/>
在从不可信源反序列化数据时,您应考虑添加包和类模式的 allow-list。 默认情况下,所有类都会被反序列化。
Object-to-MapMap-to-Object转换器

Spring Integration 还提供了 Object-to-MapMap-to-Object 转换器,它们使用 JSON 来序列化和反序列化对象图。 对象层次结构会被内省到最原始的类型(Stringint 等)。 到该类型的路径通过 SpEL 描述,它将成为转换后的 Map 中的 key。 原始类型将成为值。spring-doc.cadn.net.cn

考虑以下示例:spring-doc.cadn.net.cn

public class Parent{
    private Child child;
    private String name;
    // setters and getters are omitted
}

public class Child{
    private String name;
    private List<String> nickNames;
    // setters and getters are omitted
}

前述示例中的两个类被转换为以下 Mapspring-doc.cadn.net.cn

{person.name=George, person.child.name=Jenna, person.child.nickNames[0]=Jen ...}

基于 JSON 的 Map 允许您在不共享实际类型的情况下描述对象结构,这使得只要保持结构一致,即可将对象图还原并重建为不同类型的对象图。spring-doc.cadn.net.cn

例如,前面的结构可以使用 Map-to-Object 转换器恢复为以下对象图:spring-doc.cadn.net.cn

public class Father {
    private Kid child;
    private String name;
    // setters and getters are omitted
}

public class Kid {
    private String name;
    private List<String> nickNames;
    // setters and getters are omitted
}

如果您需要创建一个“结构化”的映射,可以提供 flatten 属性。 默认值为 'true'。 如果将其设置为 'false',则结构将是 MapMap 对象。spring-doc.cadn.net.cn

考虑以下示例:spring-doc.cadn.net.cn

public class Parent {
	private Child child;
	private String name;
	// setters and getters are omitted
}

public class Child {
	private String name;
	private List<String> nickNames;
	// setters and getters are omitted
}

前述示例中的两个类被转换为以下 Mapspring-doc.cadn.net.cn

{name=George, child={name=Jenna, nickNames=[Bimbo, ...]}}

要配置这些转换器,Spring Integration 提供了相应的 XML 组件和 Java DSL 工厂:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("directInput")
             .transform(Transformers.toMap())
             .channel("output")
             .get();
}
XML
<int:object-to-map-transformer input-channel="directInput" output-channel="output"/>

您也可以将 flatten 属性设置为 false,如下所示:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("directInput")
             .transform(Transformers.toMap(false))
             .channel("output")
             .get();
}
XML
<int:object-to-map-transformer input-channel="directInput" output-channel="output" flatten="false"/>

Spring Integration 提供了用于 Map-to-Object 的 XML 命名空间支持,Java DSL 工厂具有 fromMap() 方法,如下例所示:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("input")
             .transform(Transformers.fromMap(org.something.Person.class))
             .channel("output")
             .get();
}
XML
<int:map-to-object-transformer input-channel="input"
                         output-channel="output"
                         type="org.something.Person"/>

或者,您可以使用 ref 属性和原型作用域的 Bean,如下示例所示:spring-doc.cadn.net.cn

Java DSL
@Bean
IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("inputA")
             .transform(Transformers.fromMap("person"))
             .channel("outputA")
             .get();
}

@Bean
@Scope("prototype")
Person person() {
    return new Person();
}
XML
<int:map-to-object-transformer input-channel="inputA"
                               output-channel="outputA"
                               ref="person"/>
<bean id="person" class="org.something.Person" scope="prototype"/>
'ref' 和 'type' 属性是互斥的。 此外,如果您使用 'ref' 属性,则必须指向一个 'prototype' 作用域的 Bean。 否则,将抛出 BeanCreationException

从 5.0 版本开始,您可以为 ObjectToMapTransformer 提供自定义的 JsonObjectMapper —— 适用于需要日期特殊格式或空集合为 null 等场景(以及其他用途)。 有关 JsonObjectMapper 实现的更多信息,请参阅 JSON 转换器spring-doc.cadn.net.cn

流转换器

The StreamTransformerInputStream 负载转换为 byte[](如果提供了 charset,则转换为 String)。spring-doc.cadn.net.cn

以下示例展示了如何在 XML 中使用 stream-transformer 元素:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("input")
             .transform(Transformers.fromStream("UTF-8"))
             .channel("output")
             .get();
}
XML
<int:stream-transformer input-channel="directInput" output-channel="output"/> <!-- byte[] -->

<int:stream-transformer id="withCharset" charset="UTF-8"
    input-channel="charsetChannel" output-channel="output"/> <!-- String -->

以下示例展示了如何在 Java 中使用 StreamTransformer 类和 @Transformer 注解来配置流转换器:spring-doc.cadn.net.cn

@Bean
@Transformer(inputChannel = "stream", outputChannel = "data")
public StreamTransformer streamToBytes() {
    return new StreamTransformer(); // transforms to byte[]
}

@Bean
@Transformer(inputChannel = "stream", outputChannel = "data")
public StreamTransformer streamToString() {
    return new StreamTransformer("UTF-8"); // transforms to String
}
JSON 转换器

Spring Integration 提供对象到 JSON 和 JSON 到对象的转换器。 以下示例对展示了如何在 XML 中声明它们:spring-doc.cadn.net.cn

<int:object-to-json-transformer input-channel="objectMapperInput"/>

<int:json-to-object-transformer input-channel="objectMapperInput"
    type="foo.MyDomainObject"/>

默认情况下,上述列表中的转换器使用普通的 JsonObjectMapper。 它是基于类路径中的实现构建的。 您可以提供自己的自定义 JsonObjectMapper 实现,并配备适当的选项或基于所需的库(例如 GSON),如下示例所示:spring-doc.cadn.net.cn

<int:json-to-object-transformer input-channel="objectMapperInput"
    type="something.MyDomainObject" object-mapper="customObjectMapper"/>

从 3.0 版本开始,object-mapper 属性引用一个新策略接口的实例:JsonObjectMapper。 此抽象允许使用多种 JSON 映射器的实现。 提供了包装 Jackson 2 的实现,并在类路径上检测其版本。 该类分别是 Jackson2JsonObjectMapperspring-doc.cadn.net.cn

您可能希望考虑使用 FactoryBean 或工厂方法来创建具有所需特性的 JsonObjectMapper。 以下示例展示了如何使用此类工厂:spring-doc.cadn.net.cn

public class ObjectMapperFactory {

    public static Jackson2JsonObjectMapper getMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        return new Jackson2JsonObjectMapper(mapper);
    }
}

以下示例展示了如何在 XML 中实现相同的功能:spring-doc.cadn.net.cn

<bean id="customObjectMapper" class="something.ObjectMapperFactory"
            factory-method="getMapper"/>

从版本 2.2 开始,object-to-json-transformer 会将 content-type 头默认设置为 application/json,前提是输入消息中尚未包含该头。spring-doc.cadn.net.cn

如果您希望将 content-type 标头设置为其他值,或显式使用某个值(包括 application/json)覆盖任何现有标头,请使用 content-type 属性。 如果您希望抑制标头的设置,请将 content-type 属性设置为空字符串 ("")。 这样做会导致消息中不包含 content-type 标头,除非输入消息中原本存在该标头。spring-doc.cadn.net.cn

从 3.0 版本开始,ObjectToJsonTransformer 会在消息中添加反映源类型的头部信息。 同样地,JsonToObjectTransformer 在将 JSON 转换为对象时可以使用这些类型头部信息。 这些头部信息在 AMQP 适配器中进行了映射,从而与 Spring-AMQP 的 JsonMessageConverter 完全兼容。spring-doc.cadn.net.cn

这使以下流程无需任何特殊配置即可正常工作:spring-doc.cadn.net.cn

使用请求头确定类型时,不应提供 class 属性,因为它会优先于请求头。

除了 JSON 转换器外,Spring Integration 还提供内置的 #jsonPath SpEL 函数供表达式使用。 更多信息请参见 Spring 表达式语言 (SpEL)spring-doc.cadn.net.cn

从 3.0 版本开始,Spring Integration 还提供了一个内置的 #xpath SpEL 函数,可用于表达式中。 更多信息请参见 #xpath SpEL 函数spring-doc.cadn.net.cn

从 4.0 版本开始,ObjectToJsonTransformer支持resultType属性,用于指定节点的 JSON 表示形式。 结果节点树表示取决于提供的JsonObjectMapper的实现。 默认情况下,ObjectToJsonTransformer使用Jackson2JsonObjectMapper,并将对象转换为节点树的任务委托给ObjectMapper#valueToTree方法。 当下游消息流使用 SpEL 表达式并能够访问 JSON 数据的属性时,节点的 JSON 表示形式提供了使用JsonPropertyAccessor的效率。 有关更多信息,请参阅属性访问器spring-doc.cadn.net.cn

从版本 5.1 开始,可以将 resultType 配置为 BYTES,以便在操作此数据类型的下游处理器工作时生成带有 byte[] 负载的消息,从而提供便利。spring-doc.cadn.net.cn

从版本 5.2 开始,JsonToObjectTransformer 可以配置为 ResolvableType,以支持在使用目标 JSON 处理器进行反序列化时处理泛型。 此外,该组件现在会优先检查请求消息头中是否存在 JsonHeaders.RESOLVABLE_TYPEJsonHeaders.TYPE_ID,如果未找到则回退到已配置的类型。 ObjectToJsonTransformer 现在还会根据请求消息负载填充 JsonHeaders.RESOLVABLE_TYPE 头信息,以支持任何可能的下游场景。spring-doc.cadn.net.cn

从版本 5.2.6 开始,可以提供一个 JsonToObjectTransformer 和一个 valueTypeExpression,以便在运行时针对请求消息解析用于将 JSON 负载转换的 ResolvableType。 默认情况下,它会查询请求消息中的 JsonHeaders。 如果此表达式返回 nullResolvableType,构建过程将抛出 ClassNotFoundException,此时转换器将回退到提供的 targetType。 此逻辑以表达式的形式存在,因为 JsonHeaders 可能不包含实际的类值,而是某些类型 ID,需要根据外部注册表将其映射到目标类。spring-doc.cadn.net.cn

Apache Avro 转换器

版本 5.2 添加了简单的转换器,用于与 Apache Avro 进行相互转换。spring-doc.cadn.net.cn

它们是不成熟的,因为没有模式注册表;转换器仅使用从 Avro 模式生成的 SpecificRecord 实现中嵌入的模式。spring-doc.cadn.net.cn

发送到 SimpleToAvroTransformer 的消息必须具有实现 SpecificRecord 的负载;转换器可以处理多种类型。 SimpleFromAvroTransformer 必须配置一个用作反序列化默认类型的 SpecificRecord 类。 您还可以指定 SpEL 表达式,使用 setTypeExpression 方法来确定要反序列化的类型。 默认的 SpEL 表达式是 headers[avro_type]AvroHeaders.TYPE),默认情况下由 SimpleToAvroTransformer 填充源类的完全限定类名。 如果表达式返回 null,则将使用 defaultTypespring-doc.cadn.net.cn

The SimpleToAvroTransformer 也拥有一个 setTypeExpression 方法。 这实现了生产者和消费者之间的解耦,发送方可以将头信息设置为代表类型的某个Tokens,而接收方随后将该Tokens映射为具体的类型。spring-doc.cadn.net.cn

协议缓冲区转换器

版本 6.1 增加了对与 Protocol Buffers 数据内容相互转换的支持。spring-doc.cadn.net.cn

The ToProtobufTransformercom.google.protobuf.Message 消息负载转换为原生字节数组或 JSON 文本负载。 The application/x-protobuf 内容类型(默认使用)生成字节数组输出负载。 如果内容类型为 application/json,且在类路径中找到 com.google.protobuf:protobuf-java-util,则输出为文本 JSON 负载。 如果未设置内容类型头,ToProtobufTransformer 默认为 application/x-protobufspring-doc.cadn.net.cn

FromProtobufTransformer 将字节数组或文本 Protobuf 负载(取决于内容类型)转换回 com.google.protobuf.Message 实例。 FromProtobufTransformer 应显式指定预期的类类型(使用 setExpectedType 方法),或使用 SpEL 表达式通过 setExpectedTypeExpression 方法确定要反序列化的类型。 默认的 SpEL 表达式是 headers[proto_type]ProtoHeaders.TYPE),它由 ToProtobufTransformer 填充,包含源 com.google.protobuf.Message 类的完全限定类名。spring-doc.cadn.net.cn

例如,编译以下 IDL:spring-doc.cadn.net.cn

syntax = "proto2";
package tutorial;

option java_multiple_files = true;
option java_package = "org.example";
option java_outer_classname = "MyProtos";

message MyMessageClass {
  optional string foo = 1;
  optional string bar = 2;
}

将生成一个新的 org.example.MyMessageClass 类。spring-doc.cadn.net.cn

然后使用:spring-doc.cadn.net.cn

// Transforms a MyMessageClass instance into a byte array.
ToProtobufTransformer toTransformer = new ToProtobufTransformer();

MyMessageClass test = MyMessageClass.newBuilder()
                                .setFoo("foo")
                                .setBar("bar")
                                .build();
// message1 payload is byte array protocol buffer wire format.
Message message1 = toTransformer.transform(new GenericMessage<>(test));

// Transforms a byte array payload into a MyMessageClass instance.
FromProtobufTransformer fromTransformer = new FromProtobufTransformer();

// message2 payload == test
Message message2 =  fromTransformer.transform(message1);

使用注解配置转换器

您可以将 @Transformer 注解添加到期望接收 Message 类型或消息负载类型的方法中。 返回值的处理方式与前面描述的完全相同,参见描述 <transformer> 元素的章节。 以下示例展示了如何使用 @Transformer 注解将 String 转换为 Orderspring-doc.cadn.net.cn

@Transformer
Order generateOrder(String productId) {
    return new Order(productId);
}

转换器方法也可以接受 @Header@Headers 注解,如 Annotation Support 中所述。 以下示例展示了如何使用 @Header 注解:spring-doc.cadn.net.cn

@Transformer
Order generateOrder(String productId, @Header("customerName") String customer) {
    return new Order(productId, customer);
}

请求头过滤器

有时,您的转换用例可能非常简单,例如移除一些头部信息。 对于此类用例,Spring Integration 提供了一个头部过滤器(header filter),允许您指定某些应从未输出消息中移除的头部名称(例如,出于安全原因或移除仅临时需要的值而移除头部)。 基本上,头部过滤器是头部增强器(header enricher)的对立面。 后者在 头部增强器 中讨论。 以下示例定义了一个头部过滤器:spring-doc.cadn.net.cn

Java DSL
@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("inputChannel")
             .headerFilter("lastName", "state")
             .channel("outputChannel")
             .get();
}
XML
<int:header-filter input-channel="inputChannel"
		output-channel="outputChannel" header-names="lastName, state"/>

如您所见,配置头部过滤器非常简单。 它是一个典型的端点,具有输入和输出通道以及一个 header-names 属性。 该属性接受需要移除的头部名称(如果有多个,则以逗号分隔)。 因此,在上面的示例中,名为 'lastName' 和 'state' 的头部不会出现在出站消息中。spring-doc.cadn.net.cn

基于编解码器的转换器

内容增强器

有时,您可能需要为请求添加比目标系统提供的更多信息。 数据增强器模式描述了各种场景以及用于满足此类需求的组件(Enricher)。spring-doc.cadn.net.cn

Spring Integration Core 模块包含两个增强器:spring-doc.cadn.net.cn

它还包含三个特定于适配器的标头增强器:spring-doc.cadn.net.cn

请参阅本参考手册中特定于适配器的部分,以了解更多有关这些适配器的信息。spring-doc.cadn.net.cn

有关表达式支持的更多信息,请参见 Spring 表达式语言 (SpEL)spring-doc.cadn.net.cn

头部信息增强器

如果您只需要向消息添加头部,且这些头部并非由消息内容动态确定,那么引用自定义的转换器实现可能有些小题大做。 出于这个原因,Spring Integration 提供了对“头部增强器”模式的支持。 它通过 <header-enricher> 元素暴露出来。 以下示例展示了如何使用它:spring-doc.cadn.net.cn

<int:header-enricher input-channel="in" output-channel="out">
    <int:header name="foo" value="123"/>
    <int:header name="bar" ref="someBean"/>
</int:header-enricher>

标题增强器还提供了有用的子元素来设置常见的标题名称,如下例所示:spring-doc.cadn.net.cn

<int:header-enricher input-channel="in" output-channel="out">
    <int:error-channel ref="applicationErrorChannel"/>
    <int:reply-channel ref="quoteReplyChannel"/>
    <int:correlation-id value="123"/>
    <int:priority value="HIGHEST"/>
    <routing-slip value="channel1; routingSlipRoutingStrategy; request.headers[myRoutingSlipChannel]"/>
    <int:header name="bar" ref="someBean"/>
</int:header-enricher>

上述配置表明,对于已知的头部(例如 errorChannel, correlationId, priority, replyChannel, routing-slip 等),您可以直接使用便捷的子元素来设置这些值,而无需使用通用的 <header> 子元素(在通用子元素中您需要同时提供头部的“名称”和“值”)。spring-doc.cadn.net.cn

从版本 4.1 开始,头部增强器提供了一个 routing-slip 子元素。 有关更多信息,请参阅 路由单spring-doc.cadn.net.cn

POJO 支持

通常,标头值无法静态定义,而必须根据消息中的某些内容动态确定。 这就是为什么标头增强器还允许您使用 refmethod 属性来指定 Bean 引用。 指定的方法将计算标头值。 请考虑以下配置以及一个用于修改 String 的方法的 Bean:spring-doc.cadn.net.cn

<int:header-enricher input-channel="in" output-channel="out">
    <int:header name="something" method="computeValue" ref="myBean"/>
</int:header-enricher>

<bean id="myBean" class="thing1.thing2.MyBean"/>
public class MyBean {

    public String computeValue(String payload){
        return payload.toUpperCase() + "_US";
    }
}

您也可以将 POJO 配置为内部 Bean,如下示例所示:spring-doc.cadn.net.cn

<int:header-enricher  input-channel="inputChannel" output-channel="outputChannel">
    <int:header name="some_header">
        <bean class="org.MyEnricher"/>
    </int:header>
</int:header-enricher>

您可以类似地指向 Groovy 脚本,如下示例所示:spring-doc.cadn.net.cn

<int:header-enricher  input-channel="inputChannel" output-channel="outputChannel">
    <int:header name="some_header">
        <int-groovy:script location="org/SampleGroovyHeaderEnricher.groovy"/>
    </int:header>
</int:header-enricher>
SpEL 支持

在 Spring Integration 2.0 中,我们引入了Spring 表达式语言(SpEL)的便利性,以帮助配置许多不同的组件。 标头增强器就是其中之一。 再次查看前面展示的 POJO 示例。 您可以看到,用于确定标头值的计算逻辑非常简单。 一个自然的问题是:“是否有更简单的方法来实现这一点?”。 这正是 SpEL 展现其真正威力的地方。 考虑以下示例:spring-doc.cadn.net.cn

<int:header-enricher input-channel="in" output-channel="out">
    <int:header name="foo" expression="payload.toUpperCase() + '_US'"/>
</int:header-enricher>

通过使用 SpEL 处理此类简单情况,您无需再提供单独的类并在应用程序上下文中对其进行配置。 您只需将 expression 属性配置为有效的 SpEL 表达式即可。 'payload' 和 'headers' 变量已绑定到 SpEL 求值上下文,使您可以完全访问传入的消息。spring-doc.cadn.net.cn

使用 Java 配置配置 Header Enricher

以下两个示例展示了如何使用 Java 配置进行头部信息增强:spring-doc.cadn.net.cn

@Bean
@Transformer(inputChannel = "enrichHeadersChannel", outputChannel = "emailChannel")
public HeaderEnricher enrichHeaders() {
    Map<String, ? extends HeaderValueMessageProcessor<?>> headersToAdd =
            Collections.singletonMap("emailUrl",
                      new StaticHeaderValueMessageProcessor<>(this.imapUrl));
    HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
    return enricher;
}

@Bean
@Transformer(inputChannel="enrichHeadersChannel", outputChannel="emailChannel")
public HeaderEnricher enrichHeaders() {
    Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<>();
    headersToAdd.put("emailUrl", new StaticHeaderValueMessageProcessor<String>(this.imapUrl));
    Expression expression = new SpelExpressionParser().parseExpression("payload.from[0].toString()");
    headersToAdd.put("from",
               new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, String.class));
    HeaderEnricher enricher = new HeaderEnricher(headersToAdd);
    return enricher;
}

第一个示例添加单个字面量标头。 第二个示例添加两个标头,一个为字面量标头,另一个基于 SpEL 表达式。spring-doc.cadn.net.cn

使用 Java DSL 配置 Header Enricher

以下示例展示了用于标头增强器的 Java DSL 配置:spring-doc.cadn.net.cn

@Bean
public IntegrationFlow enrichHeadersInFlow() {
    return f -> f
                ...
                .enrichHeaders(h -> h.header("emailUrl", this.emailUrl)
                                     .headerExpression("from", "payload.from[0].toString()"))
                .handle(...);
}
头部通道注册表

从 Spring Integration 3.0 开始,提供了一个新的子元素 <int:header-channels-to-string/>。 它没有任何属性。 这个新的子元素将现有的 replyChannelerrorChannel 头信息(当它们是 MessageChannel 时)转换为 String,并将通道存储在注册表中以便稍后解析,当需要发送回复或处理错误时。 这在头信息可能会丢失的情况下非常有用,例如,当将消息序列化为消息存储时,或在通过 JMS 传输消息时。 如果该头信息尚不存在或不是 MessageChannel,则不进行任何更改。spring-doc.cadn.net.cn

使用此功能需要存在一个 HeaderChannelRegistry Bean。 默认情况下,框架会创建一个具有默认过期时间(60 秒)的 DefaultHeaderChannelRegistry。 在此时间之后,通道将从注册表中移除。 若要更改此行为,请定义一个 idintegrationHeaderChannelRegistry 的 Bean,并通过构造函数参数(以毫秒为单位)配置所需的默认延迟。spring-doc.cadn.net.cn

自 4.1 版本起,您可以在 <bean/> 定义上将名为 removeOnGet 的属性设置为 true,映射条目将在首次使用时立即被移除。 这在处理高吞吐量环境且通道仅使用一次的情况下可能非常有用,而无需等待清理程序将其移除。spring-doc.cadn.net.cn

HeaderChannelRegistry 拥有一个 size() 方法,用于确定注册表的当前大小。 runReaper() 方法会取消当前计划的任务并立即运行清理程序。 随后,任务将根据当前的延迟重新安排执行。 可以通过获取注册表的引用来直接调用这些方法,或者向控制总线发送包含以下内容的消息:spring-doc.cadn.net.cn

"@integrationHeaderChannelRegistry.runReaper()"

此子元素是一个便利选项,相当于指定以下配置:spring-doc.cadn.net.cn

<int:reply-channel
    expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.replyChannel)"
    overwrite="true" />
<int:error-channel
    expression="@integrationHeaderChannelRegistry.channelToChannelName(headers.errorChannel)"
    overwrite="true" />

从 4.1 版本开始,您可以覆盖注册表配置的清理器延迟,以确保通道映射至少保留指定的时间,而不管清理器延迟如何。 以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<int:header-enricher input-channel="inputTtl" output-channel="next">
    <int:header-channels-to-string time-to-live-expression="120000" />
</int:header-enricher>

<int:header-enricher input-channel="inputCustomTtl" output-channel="next">
    <int:header-channels-to-string
        time-to-live-expression="headers['channelTTL'] ?: 120000" />
</int:header-enricher>

在第一种情况下,每个头通道映射的存活时间将为两分钟。 在第二种情况下,存活时间在消息头中指定,并使用 Elvis 运算符:如果不存在头信息,则使用两分钟。spring-doc.cadn.net.cn

有效负载增强器

在某些情况下,前面讨论的头部增强器可能不够用,需要直接对负载本身进行增强,添加额外信息。 例如,进入 Spring Integration 消息系统的订单消息必须根据提供的客户编号查找对应的客户,然后将该信息丰富到原始负载中。spring-doc.cadn.net.cn

Spring Integration 2.1 引入了负载增强器。 负载增强器定义了一个端点,该端点将 Message 传递到暴露的请求通道,然后期望收到回复消息。 回复消息随后成为评估表达式以增强目标负载的根对象。spring-doc.cadn.net.cn

负载丰富器通过 enricher 元素提供完整的 XML 命名空间支持。 为了发送请求消息,负载丰富器拥有一个 request-channel 属性,允许您将消息分发到请求通道。spring-doc.cadn.net.cn

基本上,通过定义请求通道,负载增强器充当网关,等待发送到请求通道的消息返回。 然后,增强器使用回复消息提供的数据来扩充消息的负载。spring-doc.cadn.net.cn

向请求通道发送消息时,您也可以选择使用 request-payload-expression 属性仅发送原始负载的子集。spring-doc.cadn.net.cn

有效载荷的丰富化是通过 SpEL 表达式配置的,提供了最大程度的灵活性。 因此,您不仅可以使用回复通道的 Message 中的直接值来丰富有效载荷,还可以使用 SpEL 表达式从该消息中提取子集或应用额外的内联转换,从而进一步操作数据。spring-doc.cadn.net.cn

如果您只需要用静态值丰富负载,则无需提供 request-channel 属性。spring-doc.cadn.net.cn

富集器是转换器的一种变体。 在许多情况下,您可以使用有效负载富集器或通用转换器实现来向消息有效负载添加额外数据。 您应熟悉 Spring Integration 提供的所有具备转换能力的组件,并仔细选择语义上最符合您业务场景的实现方案。
配置

以下示例展示了有效负载增强器的所有可用配置选项:spring-doc.cadn.net.cn

<int:enricher request-channel=""                           (1)
              auto-startup="true"                          (2)
              id=""                                        (3)
              order=""                                     (4)
              output-channel=""                            (5)
              request-payload-expression=""                (6)
              reply-channel=""                             (7)
              error-channel=""                             (8)
              send-timeout=""                              (9)
              should-clone-payload="false">                (10)
    <int:poller></int:poller>                              (11)
    <int:property name="" expression="" null-result-expression="'Could not determine the name'"/>   (12)
    <int:property name="" value="23" type="java.lang.Integer" null-result-expression="'0'"/>
    <int:header name="" expression="" null-result-expression=""/>   (13)
    <int:header name="" value="" overwrite="" type="" null-result-expression=""/>
</int:enricher>
1 发送消息以获取用于丰富数据的通道。 可选。
2 生命周期属性,指示该组件是否应在应用程序上下文启动期间启动。 默认为 true。 可选。
3 底层 bean 定义的 ID,该值为 EventDrivenConsumerPollingConsumer。 可选。
4 指定当此端点作为订阅者连接到通道时的调用顺序。 当该通道使用“故障转移”分发策略时,这一点尤为重要。 如果此端点本身是带有队列的通道的轮询消费者,则此设置无效。 可选。
5 标识消息经过此端点处理后发送到的消息通道。 可选。
6 默认情况下,原始消息的负载被用作发送到 request-channel 的负载。 通过为 request-payload-expression 属性指定 SpEL 表达式作为值,您可以使用原始负载的子集、头信息值或任何其他可解析的 SpEL 表达式作为发送到请求通道的负载的基础。 对于表达式求值,完整消息可作为“根对象”使用。 例如,以下 SpEL 表达式(包括但不限于)是可行的:payload.somethingheaders.somethingnew java.util.Date()'thing1' + 'thing2'
7 期望收到回复消息的通道。 这是可选的。 通常,自动生成的临时回复通道就足够了。 可选。
8 如果 request-channel 下游发生 Exception,则发送 ErrorMessage 的通道。 这使您可以返回一个替代对象用于丰富数据。 如果未设置,则会向调用者抛出 Exception。 可选。
9 在发送消息到通道时,如果通道可能会阻塞,等待的最大毫秒数。 例如,如果队列通道的最大容量已达到,它可以阻塞直到有可用空间。 在内部,send() 超时被设置在 MessagingTemplate 上,并最终在调用 MessageChannel 上的发送操作时应用。 默认情况下,send() 超时设置为 '30'。 可选。
10 布尔值,指示是否应在将任何实现 Cloneable 的有效载荷发送到请求通道以获取丰富数据之前对其进行克隆。 克隆后的版本将用作最终回复的目标有效载荷。 默认值为 false。 可选。
11 如果此端点是轮询消费者,则允许您配置消息轮询器。 可选。
12 每个 property 子元素提供属性的名称(通过必需的 name 属性)。该属性应当可以在目标负载实例上进行设置。必须提供 valueexpression 属性中的且仅其中一个——前者用于设置字面量值,后者用于评估 SpEL 表达式。评估上下文的根对象是由该 enricher 启动的流返回的消息——如果没有请求通道,则为输入消息;否则为应用程序上下文(使用 @<beanName>.<beanProperty> SpEL 语法)。从版本 4 开始。0,在指定 value 属性时,您还可以选择性地指定 type 属性。当目标是一个类型化的 setter 方法时,框架会适当地强制转换该值(只要存在一个 PropertyEditor 来处理转换)。然而,如果目标有效负载为 Map,则条目将直接填充该值而无需转换。type 属性允许您将包含数字的 String 转换为目标有效载荷中的 Integer 值。从版本 4 开始。1,您还可以指定一个可选的 null-result-expression 属性。当 enricher 返回 null 时,将进行求值,并返回求值的输出结果。
13 每个 header 子元素提供消息头的名称(通过必需的 name 属性)。必须同时提供 valueexpression 属性中的恰好一个——前者用于设置字面量值,后者用于求值 SpEL 表达式。评估上下文的根对象是由该丰富器启动的流程返回的消息——如果没有请求通道,则为输入消息;否则为应用程序上下文(使用 '@')。' SpEL 语法)。请注意,类似于 <header-enricher><enricher> 元素的 header 元素具有 typeoverwrite 属性。然而,一个关键的区别在于,使用 <enricher> 时,overwrite 属性默认值为 true,以与 <enricher> 元素的 <property> 子元素保持一致。从版本 4 开始。1, 您也可以指定一个可选的 null-result-expression 属性。当 enricher 返回 null 时,会对其进行求值,并返回求值的输出结果。
示例

本节包含在各种情况下使用负载增强器的多个示例。spring-doc.cadn.net.cn

此处显示的代码示例属于 Spring Integration Samples 项目。 请参阅 Spring Integration Samples

在以下示例中,User 对象作为 Message 的负载被传递:spring-doc.cadn.net.cn

<int:enricher id="findUserEnricher"
              input-channel="findUserEnricherChannel"
              request-channel="findUserServiceChannel">
    <int:property name="email"    expression="payload.email"/>
    <int:property name="password" expression="payload.password"/>
</int:enricher>

The User 具有多个属性,但仅初始设置了 username。 enricher 的 request-channel 属性被配置为将 User 传递给 findUserServiceChannelspring-doc.cadn.net.cn

通过隐式设置的 reply-channel,返回一个 User 对象,并使用 property 子元素从回复中提取属性,以丰富原始负载。spring-doc.cadn.net.cn

如何将部分数据传递给请求通道?

当使用 request-payload-expression 属性时,可以将有效载荷的单个属性(而非完整消息)传递到请求通道。 在以下示例中,username 属性被传递到请求通道:spring-doc.cadn.net.cn

<int:enricher id="findUserByUsernameEnricher"
              input-channel="findUserByUsernameEnricherChannel"
              request-channel="findUserByUsernameServiceChannel"
              request-payload-expression="payload.username">
    <int:property name="email"    expression="payload.email"/>
    <int:property name="password" expression="payload.password"/>
</int:enricher>

请记住,尽管只传递了用户名,但发送到请求通道的结果消息包含完整的MessageHeaders集合。spring-doc.cadn.net.cn

如何丰富由集合数据组成的有效负载?

在以下示例中,传入的不是 User 对象,而是 Mapspring-doc.cadn.net.cn

<int:enricher id="findUserWithMapEnricher"
              input-channel="findUserWithMapEnricherChannel"
              request-channel="findUserByUsernameServiceChannel"
              request-payload-expression="payload.username">
    <int:property name="user" expression="payload"/>
</int:enricher>

Map 中的内容包含在 username 映射键下的用户名。 只有 username 被传递到请求通道。 回复中包含完整的 User 对象,该对象最终被添加到 Map 下的 user 键中。spring-doc.cadn.net.cn

如何在不使用请求通道的情况下用静态信息丰富负载?

以下示例完全不使用请求通道,而仅用静态值丰富消息的负载:spring-doc.cadn.net.cn

<int:enricher id="userEnricher"
              input-channel="input">
    <int:property name="user.updateDate" expression="new java.util.Date()"/>
    <int:property name="user.firstName" value="William"/>
    <int:property name="user.lastName"  value="Shakespeare"/>
    <int:property name="user.age"       value="42"/>
</int:enricher>

请注意,此处对“static”一词的使用较为宽松。 您仍然可以使用 SpEL 表达式来设置这些值。spring-doc.cadn.net.cn

索赔检查

在之前的章节中,我们介绍了几种内容增强器组件,它们可以帮助您处理消息缺少部分数据的情况。 我们还讨论了内容过滤,它允许您从消息中移除数据项。 然而,有时我们需要暂时隐藏数据。 例如,在分布式系统中,我们可能会收到一个负载非常大的消息。 某些间歇性的消息处理步骤可能不需要访问此负载,而另一些步骤可能只需要访问某些标头,因此在每个处理步骤中传递大型消息负载可能会导致性能下降、产生安全风险,并增加调试难度。spring-doc.cadn.net.cn

库中的存储(或称凭单)模式描述了一种机制,允许您将数据存储在一个已知的位置,同时仅保留指向该数据位置的指针(即凭单)。 您可以将该指针作为新消息的有效载荷进行传递,从而使消息流中的任意组件在需要时能够立即获取实际数据。 这种方法与挂号信流程非常相似:您会在信箱中收到一个取件凭单,然后必须前往邮局领取您的实际包裹。 这与飞行后或酒店中的行李提取处的理念也是相同的。spring-doc.cadn.net.cn

Spring Integration 提供两种类型的凭据检查转换器:spring-doc.cadn.net.cn

提供了基于命名空间的便捷机制来配置它们。spring-doc.cadn.net.cn

传入索赔检查转换器

传入的凭证检查转换器通过将消息存储在其message-store属性标识的消息存储中,来转换传入的消息。 以下示例定义了一个传入的凭证检查转换器:spring-doc.cadn.net.cn

<int:claim-check-in id="checkin"
        input-channel="checkinChannel"
        message-store="testMessageStore"
        output-channel="output"/>

在前述配置中,在 input-channel 上接收的消息会被持久化到由 message-store 属性标识的消息存储中,并使用生成的 ID 进行索引。 该 ID 即为该消息的凭证(claim check)。 该凭证同时成为发送到 output-channel 的新(转换后)消息的有效载荷。spring-doc.cadn.net.cn

现在,假设在某个时刻您确实需要访问实际的消息内容。 您可以手动访问消息存储并获取消息内容,或者使用相同的方法(创建转换器),但此时您通过使用出站Claim Check转换器将Claim Check转换为实际消息。spring-doc.cadn.net.cn

以下列表提供了传入索赔检查转换器的所有可用参数概述:spring-doc.cadn.net.cn

<int:claim-check-in auto-startup="true"             (1)
                    id=""                           (2)
                    input-channel=""                (3)
                    message-store="messageStore"    (4)
                    order=""                        (5)
                    output-channel=""               (6)
                    send-timeout="">                (7)
    <int:poller></int:poller>                       (8)
</int:claim-check-in>
1 生命周期属性,指示该组件是否应在应用程序上下文启动期间启动。 其默认值为 true。 此属性在 Chain 元素内不可用。 可选。
2 标识底层 bean 定义的 ID(MessageTransformingHandler)。 此属性在 Chain 元素内部不可用。 可选。
3 此端点的接收消息通道。 此属性在 Chain 元素内部不可用。 可选。
4 引用由该索赔检查转换器使用的 MessageStore。 如果未指定,默认引用为名为 messageStore 的 Bean。 可选。
5 指定当此端点作为订阅者连接到通道时的调用顺序。 当该通道使用 failover 分发策略时,这一点尤为重要。 如果此端点本身是带有队列的通道的轮询消费者,则此属性无效。 此属性在 Chain 元素内部不可用。 可选。
6 标识经过此端点处理后发送消息的消息通道。 此属性在 Chain 元素内部不可用。 可选。
7 指定向输出通道发送回复消息时等待的最长时间(以毫秒为单位)。 默认为 30 秒。 此属性在 Chain 元素内不可用。 可选。
8 定义一个轮询器。 此元素不可在 Chain 元素内部使用。 可选。

出站索赔检查转换器

出站声明检查转换器允许您将带有声明检查负载的消息转换为以原始内容作为负载的消息。spring-doc.cadn.net.cn

<int:claim-check-out id="checkout"
        input-channel="checkoutChannel"
        message-store="testMessageStore"
        output-channel="output"/>

在上述配置中,在 input-channel 上接收到的消息应将其负载作为索赔检查(claim check)。 出站索赔检查转换器通过查询消息存储中由提供的索赔检查标识的消息,将其转换为具有原始负载的消息。 然后,它将新提取的消息发送到 output-channelspring-doc.cadn.net.cn

以下列表提供了出站索赔检查转换器的所有可用参数概述:spring-doc.cadn.net.cn

<int:claim-check-out auto-startup="true"             (1)
                     id=""                           (2)
                     input-channel=""                (3)
                     message-store="messageStore"    (4)
                     order=""                        (5)
                     output-channel=""               (6)
                     remove-message="false"          (7)
                     send-timeout="">                (8)
    <int:poller></int:poller>                        (9)
</int:claim-check-out>
1 生命周期属性,指示该组件是否应在应用程序上下文启动期间启动。 其默认值为 true。 此属性在 Chain 元素内不可用。 可选。
2 标识底层 bean 定义的 ID(MessageTransformingHandler)。 此属性在 Chain 元素内部不可用。 可选。
3 此端点的接收消息通道。 此属性在 Chain 元素内部不可用。 可选。
4 引用由该索赔检查转换器使用的 MessageStore。 如果未指定,默认引用为名为 messageStore 的 Bean。 可选。
5 指定当此端点作为订阅者连接到通道时的调用顺序。 当该通道使用 failover 分发策略时,这一点尤为重要。 如果此端点本身是带有队列的通道的轮询消费者,则此属性无效。 此属性在 Chain 元素内部不可用。 可选。
6 标识经过此端点处理后发送消息的消息通道。 此属性在 Chain 元素内部不可用。 可选。
7 如果设置为 true,此转换器将从 MessageStore 中移除该消息。 当消息只能被“认领”一次时,此设置非常有用。 默认值为 false。 可选。
8 指定向输出通道发送回复消息时等待的最长时间(以毫秒为单位)。 默认为 30 秒。 此属性在 Chain 元素内不可用。 可选。
9 定义一个轮询器。 此元素不可在 Chain 元素内部使用。 可选。

领取一次

有时,特定的消息必须只能被认领一次。 作为一个类比,可以考虑处理飞机行李的过程。 您在出发时办理行李托运,在到达时领取行李。 一旦行李被领取,如果不先重新办理托运,就不能再次领取。 为了适应此类情况,我们在 remove-message 转换器上引入了一个布尔属性 claim-check-out。 该属性的默认值为 false。 然而,如果将其设置为 true,已认领的消息将从 MessageStore 中移除,以便无法再次被认领。spring-doc.cadn.net.cn

此功能会影响存储空间,尤其是在基于内存的 Map-based SimpleMessageStore 的情况下,如果未能删除消息,最终可能导致 OutOfMemoryException。 因此,如果您不期望有多个声明,我们建议将 remove-message 属性的值设置为 true。 以下示例展示了如何使用 remove-message 属性:spring-doc.cadn.net.cn

<int:claim-check-out id="checkout"
        input-channel="checkoutChannel"
        message-store="testMessageStore"
        output-channel="output"
        remove-message="true"/>

关于消息存储的说明

虽然我们很少关心索赔检查的细节(只要它们能正常工作),但您应该知道,Spring Integration 中实际索赔检查(即指针)的当前实现使用 UUID 来确保唯一性。spring-doc.cadn.net.cn

org.springframework.integration.store.MessageStore 是一个用于存储和检索消息的策略接口。 Spring Integration 提供了两种方便的实现:spring-doc.cadn.net.cn

编解码器

Spring Integration 4.2 版本引入了 Codec 抽象。 编解码器将对象编码和解码为 byte[]。 它们提供了 Java 序列化的替代方案。 一个优点是,通常对象无需实现 Serializable。 我们提供了一个使用 Kryo 进行序列化的实现,但您可以提供自己的实现在以下任何组件中使用:spring-doc.cadn.net.cn

EncodingPayloadTransformer

此转换器使用编解码器将有效负载编码为 byte[]。 它不影响消息头。spring-doc.cadn.net.cn

有关更多信息,请参阅javadocspring-doc.cadn.net.cn

DecodingTransformer

此转换器使用编解码器对 byte[] 进行解码。 它需要配置目标 Class,对象将被解码到该位置(或解析为 Class 的表达式)。 如果生成的对象是 Message<?>,则不会保留入站标头。spring-doc.cadn.net.cn

有关更多信息,请参阅javadocspring-doc.cadn.net.cn

CodecMessageConverter

某些端点(如 TCP 和 Redis)没有消息头的概念。 它们支持使用 MessageConverter,并且可以使用 CodecMessageConverter 将消息转换为 byte[] 或从 byte[] 转换以进行传输。spring-doc.cadn.net.cn

有关更多信息,请参阅javadocspring-doc.cadn.net.cn

Kryo

目前,这是 Codec 的唯一实现,它提供了两种 Codecspring-doc.cadn.net.cn

该框架提供多种自定义序列化器:spring-doc.cadn.net.cn

第一个可以与 PojoCodec 一起使用,通过将其用 FileKryoRegistrar 初始化。 第二个和第三个与 MessageCodec 一起使用,该对象由 MessageKryoRegistrar 初始化。spring-doc.cadn.net.cn

自定义 Kryo

默认情况下,Kryo 将未知的 Java 类型委托给其 FieldSerializer。 Kryo 还为每种基本类型注册了默认序列化器,以及 StringCollectionMapFieldSerializer 使用反射来导航对象图。 一种更高效的方法是实现一个自定义序列化器,该序列化器能够感知对象的结构,并可以直接序列化选定的基本字段。 以下示例展示了此类序列化器:spring-doc.cadn.net.cn

public class AddressSerializer extends Serializer<Address> {

    @Override
    public void write(Kryo kryo, Output output, Address address) {
        output.writeString(address.getStreet());
        output.writeString(address.getCity());
        output.writeString(address.getCountry());
    }

    @Override
    public Address read(Kryo kryo, Input input, Class<Address> type) {
        return new Address(input.readString(), input.readString(), input.readString());
    }
}

Serializer 接口公开了 KryoInputOutput,它们提供了对包含哪些字段以及其他内部设置的完全控制,如 Kryo 文档中所述。spring-doc.cadn.net.cn

当注册自定义序列化器时,您需要一个注册 ID。 注册 ID 是任意的。 然而,在我们的案例中,ID 必须显式定义,因为分布式应用程序中的每个 Kryo 实例都必须使用相同的 ID。 Kryo 推荐使用小的正整数,并保留了一些 ID(值 < 10)。 Spring Integration 目前默认使用 40、41 和 42(用于前述的文件和消息头序列化器)。 我们建议您从 60 开始,以便为框架的扩展留出空间。 您可以通过配置前述的注册程序来覆盖这些框架默认值。
使用自定义 Kryo 序列化器

如果您需要自定义序列化,请参阅 Kryo 文档,因为您需要使用原生 API 来进行自定义。 有关示例,请参见 MessageCodec 实现。spring-doc.cadn.net.cn

实现 KryoSerializable

如果您无法访问领域对象的源代码(write),您可以按照此处所述实现KryoSerializable。 在这种情况下,该类自行提供序列化方法,无需进一步配置。 然而,基准测试表明,这种方式不如显式注册自定义序列化器高效。 以下示例展示了一个自定义 Kryo 序列化器:spring-doc.cadn.net.cn

public class Address implements KryoSerializable {
    ...

    @Override
    public void write(Kryo kryo, Output output) {
        output.writeString(this.street);
        output.writeString(this.city);
        output.writeString(this.country);
    }

    @Override
    public void read(Kryo kryo, Input input) {
        this.street = input.readString();
        this.city = input.readString();
        this.country = input.readString();
    }
}

您也可以使用此技术来封装除 Kryo 之外的其他序列化库。spring-doc.cadn.net.cn

使用@DefaultSerializer注解

Kryo 还提供 @DefaultSerializer 注解,如此处所述。spring-doc.cadn.net.cn

@DefaultSerializer(SomeClassSerializer.class)
public class SomeClass {
       // ...
}

如果您无法访问域对象(write),这可能是一种指定自定义序列化器的更简单方法。 请注意,这不会将类注册为 ID,因此该技术在某些情况下可能没有帮助。spring-doc.cadn.net.cn