Transformer
消息转换器在实现消息生产者和消息消费者之间的松耦合方面扮演着至关重要的角色。无需要求每个消息生产组件都了解下一个消费者期望的类型,您可以在这些组件之间添加转换器。通用转换器,例如将 String 转换为 XML 文档的转换器,也具有高度的可重用性。
在某些系统中,提供规范数据模型可能是最佳选择,但 Spring Integration 的核心理念并不强制要求任何特定格式。相反,为了获得最大灵活性,Spring Integration 致力于提供最易于扩展的模型。与其他端点类型一样,通过 XML 或 Java 注解的声明式配置,可以使简单的 POJO 适配为消息转换器的角色。本章后续内容将详细介绍这些配置选项。
为了最大限度地提高灵活性,Spring 不要求使用基于 XML 的消息负载。然而,如果基于 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、Groovy或Kotlin DSL,IntegrationFlow的.transform()操作符代表一个转换器端点:
- Java DSL
- Kotlin DSL
- Groovy DSL
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("transformChannel")
.transform(someService, "exampleTransformer")
.channel("nextServiceChannel")
.get();
}
@Bean
fun someFlow() =
integrationFlow("transformChannel") {
transform(someService, "exampleTransformer")
channel("nextServiceChannel")
}
@Bean
someFlow() {
integrationFlow 'transformChannel',
{
transform someService, 'exampleTransformer'
channel 'nextServiceChannel'
}
}
有关 DSL 的更多信息,请参阅相应章节:
使用 XML 配置 Transformer
<transformer> 元素用于创建消息转换端点。除了 input-channel 和 output-channel 属性外,它还需要一个 ref 属性。ref 可以指向一个在单个方法上包含 @Transformer 注解的对象(参见使用注解配置转换器),也可以与 method 属性中提供的显式方法名值结合使用。
<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 定义,如下例所示:
<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 Integration 2.0 起,消息转换器的转换方法不再允许返回 null。返回 null 会导致异常(确切地说是 MessageTransformationException),因为消息转换器应当始终将每个源消息转换为有效的目标消息。换言之,不应将消息转换器用作消息过滤器,因为系统已为此提供了专门的 <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() + ']'"/>
前面的示例在不编写自定义转换器的情况下转换了载荷。我们的载荷(假设为 String)被转换为大写,与当前时间戳拼接,并应用了一些格式化。
常用转换器
Spring Integration 提供了几种转换器实现。
对象转字符串转换器
由于使用 Object 的 toString() 表示形式相当常见,Spring Integration 提供了 ObjectToStringTransformer(另请参阅 Transformers 工厂),其输出是一个 Message,其 payload 为 String。该 String 是对入站消息的 payload 调用 toString() 操作的结果。以下示例展示了如何声明对象到字符串转换器的实例:
- Java DSL
- Kotlin DSL
- Groovy DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("in")
.transform(Transformers.objectToString())
.channel("out")
.get();
}
@Bean
fun someFlow() =
integrationFlow("in") {
transform(Transformers.objectToString())
channel("out")
}
@Bean
someFlow() {
integrationFlow 'in',
{
transform Transformers.objectToString()
channel 'out'
}
}
<int:object-to-string-transformer input-channel="in" output-channel="out"/>
这种转换器的一个潜在用途是将任意对象发送到 file 命名空间中的 'outbound-channel-adapter'。虽然该通道适配器默认仅支持 String、字节数组或 java.io.File 类型的有效负载,但通过在适配器前添加此转换器即可处理必要的转换。只要 toString() 调用的结果符合您希望写入文件的内容,这种方式就能正常工作。否则,您可以使用先前展示的通用 'transformer' 元素来提供基于 POJO 的自定义转换器。
在调试时,通常不需要此转换器,因为 logging-channel-adapter 能够记录消息负载。更多详情请参阅 Wire Tap。
对象到字符串转换器非常简单。它会在入站有效负载上调用 toString() 方法。自 Spring Integration 3.0 起,此规则有两个例外情况:
-
若载荷为
char[],则调用new String(payload)。 -
若载荷为
byte[],则调用new String(payload, charset),其中charset默认为 UTF-8。可通过在转换器上设置 charset 属性来修改charset。
对于更复杂的场景(例如在运行时动态选择字符集),您可以使用基于SpEL表达式的转换器,如下例所示:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("in")
.transform("new String(payload, headers['myCharset']")
.channel("out")
.get();
}
<int:transformer input-channel="in" output-channel="out"
expression="new String(payload, headers['myCharset']" />
若需将Object序列化为字节数组或将字节数组反序列化为Object,Spring Integration 提供了对称的序列化转换器。默认情况下,这些转换器使用标准的 Java 序列化机制,但您也可以通过serializer和deserializer属性分别提供 Spring Serializer或Deserializer策略的实现。另请参阅Transformers工厂类。以下示例展示了如何使用 Spring 的序列化器与反序列化器:
- Java DSL
- XML
@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();
}
<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 到 Map 以及 Map 到 Object 的转换器
Spring Integration 还提供了 Object 到 Map 以及 Map 到 Object 的转换器,它们使用 JSON 来序列化和反序列化对象图。对象层次结构会被内省至最原始的类型(String、int 等)。到达此类型的路径通过 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
}
前面例子中的两个类被转换为以下 Map:
{person.name=George, person.child.name=Jenna, person.child.nickNames[0]=Jen ...}
基于 JSON 的 Map 允许您在不共享实际类型的情况下描述对象结构,只要保持结构一致,您就可以将对象图恢复并重建为不同类型化的对象图。
例如,通过使用 Map 到 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
}
如果你需要创建一个“结构化”映射,可以设置 flatten 属性。其默认值为 'true'。若将其设为 'false',则结构将变为 Map 对象的 Map。
考虑以下示例:
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
}
前面例子中的两个类被转换为以下 Map:
{name=George, child={name=Jenna, nickNames=[Bimbo, ...]}}
要配置这些转换器,Spring Integration 提供了相应的 XML 组件和 Java DSL 工厂:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("directInput")
.transform(Transformers.toMap())
.channel("output")
.get();
}
<int:object-to-map-transformer input-channel="directInput" output-channel="output"/>
你也可以将 flatten 属性设置为 false,如下所示:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("directInput")
.transform(Transformers.toMap(false))
.channel("output")
.get();
}
<int:object-to-map-transformer input-channel="directInput" output-channel="output" flatten="false"/>
Spring Integration 为 Map 到对象的转换提供了 XML 命名空间支持,并且 Java DSL 工厂提供了 fromMap() 方法,如下例所示:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("input")
.transform(Transformers.fromMap(org.something.Person.class))
.channel("output")
.get();
}
<int:map-to-object-transformer input-channel="input"
output-channel="output"
type="org.something.Person"/>
或者,您也可以使用 ref 属性和原型作用域的 bean,如下例所示:
- Java DSL
- XML
@Bean
IntegrationFlow someFlow() {
return IntegrationFlow
.from("inputA")
.transform(Transformers.fromMap("person"))
.channel("outputA")
.get();
}
@Bean
@Scope("prototype")
Person person() {
return new Person();
}
<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 —— 适用于需要为日期或空集合的空值设置特殊格式(以及其他用途)的场景。有关 JsonObjectMapper 实现的更多信息,请参阅 JSON 转换器。
流转换器
StreamTransformer 将 InputStream 负载转换为 byte[](如果提供了 charset,则转换为 String)。
以下示例展示了如何在 XML 中使用 stream-transformer 元素:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("input")
.transform(Transformers.fromStream("UTF-8"))
.channel("output")
.get();
}
<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 注解来配置流式转换器:
@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 中声明它们:
<int:object-to-json-transformer input-channel="objectMapperInput"/>
<int:json-to-object-transformer input-channel="objectMapperInput"
type="foo.MyDomainObject"/>
默认情况下,上述列表中的转换器使用标准的 JsonObjectMapper。它基于类路径中的实现。您可以提供自定义的 JsonObjectMapper 实现,并配置适当的选项或基于所需的库(例如 GSON),如下例所示:
<int:json-to-object-transformer input-channel="objectMapperInput"
type="something.MyDomainObject" object-mapper="customObjectMapper"/>
从版本 3.0 开始,object-mapper 属性引用了一个新的策略接口实例:JsonObjectMapper。这个抽象允许使用多种 JSON 映射器的实现。提供了封装 Jackson 3 的实现,其版本会在类路径上被检测到。相应的类是 JacksonJsonObjectMapper。
您可能需要考虑使用 FactoryBean 或工厂方法来创建具有所需特性的 JsonObjectMapper。以下示例展示了如何使用此类工厂:
public class ObjectMapperFactory {
public static JacksonJsonObjectMapper getMapper() {
ObjectMapper mapper = JsonMapper.builder()
.configure(JsonReadFeature.ALLOW_JAVA_COMMENTS, true)
.build();
return new JacksonJsonObjectMapper(mapper);
}
}
以下示例展示了如何在 XML 中实现相同的操作:
<bean id="customObjectMapper" class="something.ObjectMapperFactory"
factory-method="getMapper"/>
从 2.2 版本开始,如果输入消息尚未包含 content-type 头,object-to-json-transformer 默认会将其设置为 application/json。
如果您希望将 content-type 头设置为其他值,或显式地用某个值(包括 application/json)覆盖任何现有的头,请使用 content-type 属性。如果您希望禁止设置该头,请将 content-type 属性设置为空字符串 ("")。这样做会导致消息没有 content-type 头,除非输入消息中已存在该头。
从版本 3.0 开始,ObjectToJsonTransformer 会向消息添加标头,以反映源类型。同样地,JsonToObjectTransformer 在将 JSON 转换为对象时可以使用这些类型标头。这些标头在 AMQP 适配器中进行了映射,因此它们与 Spring-AMQP 的 JsonMessageConverter 完全兼容。
这使得以下流程无需特殊配置即可工作:
-
…→amqp出站适配器---→ -
---→amqp入站适配器→json到对象转换器→…其中出站适配器配置了
JsonMessageConverter,而入站适配器使用默认的SimpleMessageConverter。 -
…→对象到json转换器→amqp出站适配器---→ -
---→amqp入站适配器→…其中出站适配器配置了
SimpleMessageConverter,而入站适配器使用默认的JsonMessageConverter。 -
…→对象到json转换器→amqp出站适配器---→ -
---→amqp入站适配器→json到对象转换器→其中两个适配器都配置了
SimpleMessageConverter。
当使用头部信息来确定类型时,不应提供 class 属性,因为它会优先于头部信息。
除了 JSON 转换器,Spring Integration 还提供了一个内置的 #jsonPath SpEL 函数,可在表达式中使用。更多信息请参阅 Spring Expression Language (SpEL)。
自 3.0 版本起,Spring Integration 还提供了一个内置的 #xpath SpEL 函数,用于在表达式中使用。更多信息请参阅 #xpath SpEL 函数。
从 4.0 版本开始,ObjectToJsonTransformer 支持 resultType 属性,用于指定节点的 JSON 表示形式。结果节点树的表示形式取决于所提供的 JsonObjectMapper 的实现。默认情况下,ObjectToJsonTransformer 使用 JacksonJsonObjectMapper,并将对象到节点树的转换委托给 ObjectMapper#valueToTree 方法。当下游消息流使用 SpEL 表达式访问 JSON 数据的属性时,节点 JSON 表示为使用 JacksonPropertyAccessor 提供了效率。更多信息请参阅属性访问器。
从 5.1 版本开始,resultType 可配置为 BYTES,以生成包含 byte[] 负载的消息。这在处理使用此数据类型的下游处理器时提供了便利。
从版本 5.2 开始,JsonToObjectTransformer 可以配置一个 ResolvableType,以便在使用目标 JSON 处理器进行反序列化时支持泛型。此外,该组件现在会首先检查请求消息头中是否存在 JsonHeaders.RESOLVABLE_TYPE 或 JsonHeaders.TYPE_ID,否则回退到配置的类型。ObjectToJsonTransformer 现在还会根据请求消息的有效负载填充 JsonHeaders.RESOLVABLE_TYPE 头,以供任何可能的下游场景使用。
从5.2.6版本开始,可以为 JsonToObjectTransformer 提供 valueTypeExpression,以便在运行时根据请求消息解析出用于从JSON转换的负载的 ResolvableType。默认情况下,它会查询请求消息中的 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 数据内容进行双向转换的支持。
ToProtobufTransformer 将 com.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;
}
将生成一个新的 org.example.MyMessageClass 类。
然后使用:
// 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> 元素的章节中所述完全相同。以下示例展示了如何使用 @Transformer 注解将 String 转换为 Order:
@Transformer
Order generateOrder(String productId) {
return new Order(productId);
}
Transformer 方法同样支持 @Header 和 @Headers 注解,具体用法请参考 [注解支持](configuration/annotations.md) 文档。以下示例展示了如何使用 @Header 注解:
@Transformer
Order generateOrder(String productId, @Header("customerName") String customer) {
return new Order(productId, customer);
}
另请参阅使用注解通知端点。
表头筛选
有时,您的转换用例可能简单到只需移除几个头部信息。对于此类用例,Spring Integration 提供了一个头部过滤器,允许您指定应从输出消息中移除的特定头部名称(例如,出于安全原因移除头部,或移除仅临时需要的值)。本质上,头部过滤器与头部增强器功能相反。后者在头部增强器中讨论。以下示例定义了一个头部过滤器:
- Java DSL
- XML
@Bean
public IntegrationFlow someFlow() {
return IntegrationFlow
.from("inputChannel")
.headerFilter("lastName", "state")
.channel("outputChannel")
.get();
}
<int:header-filter input-channel="inputChannel"
output-channel="outputChannel" header-names="lastName, state"/>
如您所见,配置头部过滤器相当简单。它是一个典型的端点,具有输入和输出通道,以及一个 header-names 属性。该属性接受需要移除的头部名称(如果有多个,则用逗号分隔)。因此,在前面的示例中,名为 'lastName' 和 'state' 的头部不会出现在出站消息中。
基于编解码器的变换器
参见编解码器。