转换器

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

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

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

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

对于简单的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>元素用于创建消息转换端点。除了input-channeloutput-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引用相同的message handler,则会得到配置异常。

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

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

转换器和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提供了一些转换器实现。

对象到字符串转换器

由于使用对象的toString()表示形式相当常见,因此Spring Integration提供了一个ObjectToStringTransformer(另请参见Transformers工厂),其中输出是一个具有StringpayloadMessage。该String是调用传入消息有效负载上的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能够记录消息有效负载。有关更多详细信息,请参见线路监听

对象到字符串转换器非常简单。它调用传入有效负载上的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序列化,但您可以通过使用serializerdeserializer属性分别提供SpringSerializerDeserializer策略的实现。另请参见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。默认情况下,所有类都将被反序列化。

ObjectMapMapObject转换器

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
}

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

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

基于JSON的Map允许您描述对象结构而无需共享实际类型,这允许您将对象图恢复和重建为不同类型的对象图,只要您保持结构即可。

例如,前面的结构可以使用MapObject转换器恢复到以下对象图

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对象。

考虑以下示例

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到Object提供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转换器

流转换器

StreamTransformerInputStream有效负载转换为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。它基于类路径中的实现。您可以使用适当的选项或基于所需的库(例如GSON)提供您自己的自定义JsonObjectMapper实现,如下例所示。

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

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

以下示例显示了如何在XML中执行相同的操作。

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

从 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→…​

    其中 outbound 适配器配置了JsonMessageConverter,而 inbound 适配器使用默认的SimpleMessageConverter

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

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

    其中 outbound 适配器配置了SimpleMessageConverter,而 inbound 适配器使用默认的JsonMessageConverter

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

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

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

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

除了 JSON 转换器之外,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 表示提高了使用JsonPropertyAccessor 的效率,当后续消息流使用 SpEL 表达式访问 JSON 数据的属性时。更多信息请参见 属性访问器

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

从 5.2 版本开始,JsonToObjectTransformer 可以配置ResolvableType 以在使用目标 JSON 处理程序反序列化时支持泛型。此外,此组件现在首先检查请求消息头中是否存在JsonHeaders.RESOLVABLE_TYPEJsonHeaders.TYPE_ID,否则回退到配置的类型。ObjectToJsonTransformer 现在也基于请求消息负载填充JsonHeaders.RESOLVABLE_TYPE 头,用于任何可能的后续场景。

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

Apache Avro 转换器

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

它们很简单,因为没有模式注册表;转换器只是使用嵌入在从 Avro 模式生成的SpecificRecord 实现中的模式。

发送到SimpleToAvroTransformer 的消息必须具有实现SpecificRecord 的有效负载;转换器可以处理多种类型。SimpleFromAvroTransformer 必须配置一个SpecificRecord 类,该类用作反序列化的默认类型。您还可以指定一个 SpEL 表达式来确定使用setTypeExpression 方法反序列化的类型。默认 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;
}

将生成一个新的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 注解将String 转换为Order

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

转换器方法还可以接受@Header@Headers 注解,如注解支持 中所述。以下示例显示如何使用@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”的头不在出站消息中。

基于编解码器的转换器

参见 编解码器