跳到主要内容

Transformer

QWen Plus 中英对照 Transformer

消息转换器在实现消息生产者和消息消费者松耦合方面发挥着非常重要的作用。与其要求每个消息生成组件都知道下一个消费者期望的类型,不如在这两个组件之间添加转换器。通用转换器,例如将 String 转换为 XML 文档的转换器,也具有很高的可重用性。

对于某些系统,提供一个规范数据模型可能是最好的选择,但 Spring Integration 的总体理念是不要求任何特定格式。相反,为了最大限度的灵活性,Spring Integration 致力于提供最简单的扩展模型。与其他端点类型一样,使用 XML 或 Java 注解的声明式配置可以将简单的 POJO 适应为消息转换器的角色。本章的其余部分将描述这些配置选项。

备注

为了最大限度地提高灵活性,Spring 不要求基于 XML 的消息有效负载。然而,如果这确实是你的应用程序的正确选择,框架确实为处理基于 XML 的有效负载提供了一些方便的转换器。有关这些转换器的更多信息,请参阅 XML 支持 - 处理 XML 有效负载

使用 Java 和其他 DSL 配置 Transformer

对于简单的 Java & 注解配置,Spring bean POJO 方法必须标记有 @Transformer 注解,当从输入通道消费消息时,框架会调用该方法:

public class SomeService {

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

}
java

请在 Annotation Support 中查看更多信息。

对于 Java、Groovy 或 Kotlin DSL,IntegrationFlow.transform() 操作符表示一个转换器端点:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("transformChannel")
.transform(someService, "exampleTransformer")
.channel("nextServiceChannel")
.get();
}
java

有关DSL的更多信息,请参见相关章节:

使用 XML 配置 Transformer

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

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

使用 ref 属性通常是推荐的做法,如果自定义转换器处理程序的实现可以在其他 <transformer> 定义中重用的话。但是,如果自定义转换器处理程序的实现应该仅限于单个 <transformer> 定义的作用域内,你可以定义一个内部的 bean 定义,如下例所示:

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

在同一 <transformer> 配置中使用 ref 属性和内部处理器定义是不允许的,因为它会创建一个模棱两可的条件,并导致抛出异常。

important

如果 ref 属性引用了一个扩展 AbstractMessageProducingHandler 的 bean(例如框架本身提供的转换器),则通过直接将输出通道注入处理程序来优化配置。在这种情况下,每个 ref 必须引用一个单独的 bean 实例(或 prototype 范围的 bean)或使用内部 <bean/> 配置类型。如果您不小心从多个 bean 引用了相同的消息处理程序,则会得到一个配置异常。

当使用 POJO 时,用于转换的方法可能期望 Message 类型或传入消息的有效载荷类型。它也可以通过分别使用 @Header@Headers 参数注解来接受单个消息头值或完整的头映射。方法的返回值可以是任何类型。如果返回值本身是一个 Message,那么它将被传递到转换器的输出通道。

从 Spring Integration 2.0 开始,消息转换器的转换方法不能再返回 null。返回 null 会导致异常,因为消息转换器应该始终将每个源消息转换为有效的目标消息。换句话说,消息转换器不应该用作消息过滤器,因为为此有一个专门的 <filter> 选项。然而,如果你确实需要这种行为(即组件可能会返回 null,并且这不应该被视为错误),你可以使用服务激活器。它的 requires-reply 值默认为 false,但可以将其设置为 true 以在返回 null 时抛出异常,就像转换器一样。

Transformers 和 Spring 表达式语言 (SpEL)

像路由器、聚合器和其他组件一样,从 Spring Integration 2.0 开始,转换器也可以在转换逻辑相对简单时受益于 SpEL 支持。以下示例展示了如何使用 SpEL 表达式:

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

前面的例子在不编写自定义转换器的情况下转换了有效负载。我们的有效负载(假设为 String 类型)被转换为大写,与当前时间戳连接在一起,并应用了一些格式。

常见转换器

Spring Integration 提供了几种转换器实现。

对象到字符串转换器

因为使用 ObjecttoString() 表示相当常见,Spring Integration 提供了一个 ObjectToStringTransformer(也请参阅 Transformers 工厂),其输出是一个带有字符串 payloadMessage。该 String 是通过调用入站消息的有效负载的 toString() 操作得到的结果。以下示例展示了如何声明对象到字符串转换器的一个实例:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("in")
.transform(Transformers.objectToString())
.channel("out")
.get();
}
java

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

提示

在调试时,通常不需要这个转换器,因为 logging-channel-adapter 能够记录消息有效负载。更多详情请参见 线路插入

对象转字符串转换器非常简单。它对传入的有效载荷调用 toString()。自 Spring Integration 3.0 起,有两个例外情况:

  • 如果有效载荷是 char[],它会调用 new String(payload)

  • 如果有效载荷是 byte[],它会调用 new String(payload, charset),其中 charset 默认是 UTF-8。可以通过在转换器上提供字符集属性来修改 charset

对于更复杂的情况(例如在运行时动态选择字符集),你可以使用基于 SpEL 表达式的转换器,如下例所示:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("in")
.transform("new String(payload, headers['myCharset']")
.channel("out")
.get();
}
java

如果你需要将一个 Object 序列化为字节数组或将字节数组反序列化回 Object,Spring Integration 提供了对称的序列化转换器。这些默认使用标准 Java 序列化,但是你可以通过使用 serializerdeserializer 属性提供 Spring SerializerDeserializer 策略的实现。另请参阅 Transformers 工厂类。以下示例展示了如何使用 Spring 的序列化程序和反序列化程序:

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

在从不可信来源反序列化数据时,您应该考虑添加一个 allow-list 来指定允许的包和类模式。默认情况下,所有类都会被反序列化。

Object-to-MapMap-to-Object 转换器

Spring Integration 还提供了 ObjectMapMapObject 的转换器,它们使用 JSON 来序列化和反序列化对象图。对象层次结构被内省到最原始的类型(Stringint 等)。通往这种类型的路径是用 SpEL 描述的,这将成为转换后的 Map 中的 key。原始类型成为值。

考虑以下示例:

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

前面示例中的两个类被转换为以下 Map

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

基于 JSON 的 Map 让你可以描述对象结构而不共享实际的类型,这使得你可以在保持结构不变的情况下,将对象图还原和重建为不同类型的对象图。

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

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

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

考虑以下示例:

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

前面示例中的两个类被转换为以下 Map

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

要配置这些转换器,Spring Integration 提供了相应的 XML 组件和 Java DSL 工厂:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("directInput")
.transform(Transformers.toMap())
.channel("output")
.get();
}
java

你也可以将 flatten 属性设置为 false,如下所示:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("directInput")
.transform(Transformers.toMap(false))
.channel("output")
.get();
}
java

Spring Integration 提供了 Map-to-Object 的 XML 命名空间支持,Java DSL 工厂有 fromMap() 方法,如下例所示:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("input")
.transform(Transformers.fromMap(org.something.Person.class))
.channel("output")
.get();
}
java

或者,你可以使用 ref 属性和原型作用域的 bean,如下例所示:

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

@Bean
@Scope("prototype")
Person person() {
return new Person();
}
java
备注

'ref' 和 'type' 属性是互斥的。另外,如果你使用 'ref' 属性,则必须指向一个 'prototype' 作用域的 bean。否则,将抛出一个 BeanCreationException

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

流变换器

StreamTransformerInputStream 负载转换为 byte[](如果提供了 charset,则转换为 String)。

以下示例展示了如何在 XML 中使用 stream-transformer 元素:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("input")
.transform(Transformers.fromStream("UTF-8"))
.channel("output")
.get();
}
java

以下示例展示了如何使用 StreamTransformer 类和 @Transformer 注解在 Java 中配置流转换器:

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

JSON 转换器

Spring Integration 提供了对象到 JSON 和 JSON 到对象的转换器。以下示例对显示了如何在 XML 中声明它们:

<int:object-to-json-transformer />
<int:json-to-object-transformer type="com.example.MyClass" />
xml
<int:object-to-json-transformer input-channel="objectMapperInput"/>

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

默认情况下,前面列表中的转换器使用普通的 JsonObjectMapper。它是基于类路径中的实现。你可以提供自己的自定义 JsonObjectMapper 实现,并带有适当的选项或基于所需的库(例如 GSON),如下例所示:

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

从 3.0 版本开始,object-mapper 属性引用了一个新的策略接口 JsonObjectMapper 的实例。这种抽象允许多个 JSON 映射器的实现被使用。提供了一个包装 Jackson 2 的实现,并且会在类路径上检测其版本。相应的类是 Jackson2JsonObjectMapper

你可能希望考虑使用 FactoryBean 或工厂方法来创建具有所需特征的 JsonObjectMapper。以下示例展示了如何使用此类工厂:

public class ObjectMapperFactory {

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

下面的示例展示了如何在 XML 中做同样的事情:

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

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

如果您希望将 content-type 标头设置为其他值或明确覆盖任何现有标头的值(包括 application/json),请使用 content-type 属性。如果您希望抑制标头的设置,请将 content-type 属性设置为空字符串 ("")。这样做会导致消息没有 content-type 标头,除非输入消息中已经存在该标头。

从 3.0 版本开始,ObjectToJsonTransformer 会添加反映源类型的头信息到消息中。类似地,JsonToObjectTransformer 在将 JSON 转换为对象时可以使用这些类型头信息。这些头信息在 AMQP 适配器中进行了映射,因此它们与 Spring-AMQP JsonMessageConverter 完全兼容。

这使得以下流程无需任何特殊配置即可工作:

  • …​→amqp-outbound-adapter---→

  • ---→amqp-inbound-adapter→json-to-object-transformer→…​

    其中,出站适配器配置了 JsonMessageConverter,而入站适配器使用默认的 SimpleMessageConverter

  • …​→object-to-json-transformer→amqp-outbound-adapter---→

  • ---→amqp-inbound-adapter→…​

    其中,出站适配器配置了 SimpleMessageConverter,而入站适配器使用默认的 JsonMessageConverter

  • …​→object-to-json-transformer→amqp-outbound-adapter---→

  • ---→amqp-inbound-adapter→json-to-object-transformer→

    其中,两个适配器都配置了 SimpleMessageConverter

备注

在使用标题来确定类型时,你不应提供 class 属性,因为它的优先级高于标题。

除了 JSON Transformers,Spring Integration 还提供了一个内置的 #jsonPath SpEL 函数,用于在表达式中使用。有关更多信息,请参阅 Spring 表达式语言 (SpEL)

自从 3.0 版本以来,Spring Integration 还提供了一个内置的 #xpath SpEL 函数,可用于表达式中。有关更多信息,请参阅 #xpath SpEL 函数

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

从 5.1 版开始,可以将 resultType 配置为 BYTES,以生成带有 byte[] 负载的消息,这在与使用此类数据类型的下游处理器协同工作时非常方便。

从 5.2 版本开始,JsonToObjectTransformer 可以配置一个 ResolvableType,以便在使用目标 JSON 处理器反序列化时支持泛型。此外,此组件现在会首先检查请求消息头中是否存在 JsonHeaders.RESOLVABLE_TYPEJsonHeaders.TYPE_ID,否则将回退到配置的类型。ObjectToJsonTransformer 现在也会根据请求消息的有效负载填充一个 JsonHeaders.RESOLVABLE_TYPE 头,以应对任何可能的下游场景。

从 5.2.6 版本开始,JsonToObjectTransformer 可以提供一个 valueTypeExpression 来解析一个 ResolvableType,用于在运行时将 JSON 转换为请求消息的有效负载。默认情况下,它会在请求消息中咨询 JsonHeaders。如果此表达式返回 null 或者构建 ResolvableType 时抛出 ClassNotFoundException,转换器将回退到提供的 targetType。这种逻辑以表达式的形式存在,是因为 JsonHeaders 可能没有实际的类值,而是一些类型 ID,这些类型 ID 必须根据某些外部注册表映射到目标类。

Apache Avro 转换器

版本 5.2 添加了简单的转换器,用于在 Apache Avro 之间进行转换。

它们的不复杂之处在于没有模式注册表;转换器只是使用从 Avro 模式生成的 SpecificRecord 实现中嵌入的模式。

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

SimpleToAvroTransformer 还有一个 setTypeExpression 方法。这允许解耦生产者和消费者,发送者可以将头设置为表示类型的某个令牌,然后消费者将该令牌映射到一个类型。

Protocol Buffers 变换器

版本 6.1 增加了对从 Protocol Buffers 数据内容转换的支持。

ToProtobufTransformercom.google.protobuf.Message 消息负载转换为原生字节数组或 json 文本负载。application/x-protobuf 内容类型(默认使用)生成字节数组输出负载。如果内容类型是 application/json,请添加 com.google.protobuf:protobuf-java-util(如果在类路径上找到),然后输出将是文本 json 负载。如果未设置内容类型头,ToProtobufTransformer 默认为 application/x-protobuf

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

例如,编译以下 IDL:

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

将生成一个新的 org.example.MyMessageClass 类。

然后使用:

:
plaintext
// 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);
java

使用注解配置 Transformer

你可以将 @Transformer 注解添加到期望 Message 类型或消息有效负载类型的方法上。返回值的处理方式与前面描述的 在描述 <transformer> 元素的部分中所述完全相同。以下示例展示了如何使用 @Transformer 注解将一个 String 转换为 Order

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

Transformer 方法也可以接受 @Header@Headers 注解,如在 注解支持 中所记录的。以下示例显示了如何使用 @Header 注解:

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

Header 过滤器

有时,你的转换用例可能简单到只需要移除几个标题。对于这样的用例,Spring Integration 提供了一个标题过滤器,它允许你指定一些应该从输出消息中移除的标题名称(例如,由于安全原因移除标题或仅需临时使用的值)。基本上,标题过滤器是标题增强器的相反。后者在 Header Enricher 中讨论。以下示例定义了一个标题过滤器:

@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("inputChannel")
.headerFilter("lastName", "state")
.channel("outputChannel")
.get();
}
java

如您所见,配置标题过滤器非常简单。它是一个典型的端点,具有输入和输出通道以及一个 header-names 属性。该属性接受需要移除的标题名称(如果有多个,则用逗号分隔)。因此,在前面的例子中,名为 'lastName' 和 'state' 的标题在 outbound 消息中不存在。

基于编解码器的 Transformer

参见 Codec