轉換器

訊息轉換器在實現訊息生產者和訊息消費者之間的鬆耦合方面扮演著非常重要的角色。您可以添加轉換器在這些組件之間,而不是要求每個訊息生產組件都知道下一個消費者期望的類型。通用轉換器(例如將 String 轉換為 XML 文件的轉換器)也具有高度的可重複使用性。

對於某些系統,提供 標準資料模型 可能最好,但 Spring Integration 的總體哲學是不需要任何特定格式。相反地,為了最大的靈活性,Spring Integration 旨在提供最簡單的模型以進行擴展。與其他端點類型一樣,在 XML 或 Java 註解中使用宣告式設定,使簡單的 POJO 能夠適應訊息轉換器的角色。本章的其餘部分描述了這些設定選項。

為了最大限度地提高靈活性,Spring 並不要求基於 XML 的訊息 Payload。儘管如此,如果基於 XML 的 Payload 確實是您的應用程式的正確選擇,則框架確實提供了一些方便的轉換器來處理它們。有關這些轉換器的更多資訊,請參閱 XML 支援 - 處理 XML Payload

使用 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 引用同一個訊息處理器,您將收到設定例外。

當使用 POJO 時,用於轉換的方法可以預期接收訊息類型或輸入訊息的 Payload 類型。它也可以透過使用 @Header@Headers 參數註解,單獨或作為完整 Map 接受訊息標頭值。該方法的傳回值可以是任何類型。如果傳回值本身是一個 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() + ']'"/>

前面的範例在沒有編寫自訂轉換器的情況下轉換了 Payload。我們的 Payload(假定為 String)被轉換為大寫,與當前時間戳記串聯,並應用了一些格式設定。

常見轉換器

Spring Integration 提供了一些轉換器實作。

物件到字串轉換器

由於使用 ObjecttoString() 表示形式相當常見,因此 Spring Integration 提供了 ObjectToStringTransformer(另請參閱 Transformers 工廠),其中輸出是具有 String payloadMessage。該 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 Payload,但在適配器之前立即添加此轉換器會處理必要的轉換。只要 toString() 呼叫的結果是您想要寫入檔案的內容,這就可以正常運作。否則,您可以透過使用先前顯示的通用 'transformer' 元素來提供基於自訂 POJO 的轉換器。

在偵錯時,此轉換器通常不是必需的,因為 logging-channel-adapter 能夠記錄訊息 Payload。有關更多詳細資訊,請參閱 Wire Tap

物件到字串轉換器非常簡單。它在輸入 Payload 上調用 toString()。自 Spring Integration 3.0 以來,此規則有兩個例外

  • 如果 Payload 是 char[],它會調用 new String(payload)

  • 如果 Payload 是 byte[],它會調用 new String(payload, charset),其中 charset 預設為 UTF-8。可以透過在轉換器上提供 charset 屬性來修改 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 屬性來提供 Spring SerializerDeserializer 策略的實作。另請參閱 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.*"/>
從不受信任的來源反序列化資料時,您應該考慮添加 package 和 class 模式的 allow-list。預設情況下,所有類別都會被反序列化。

Object-to-MapMap-to-Object 轉換器

Spring Integration 還提供了 Object-to-MapMap-to-Object 轉換器,它們使用 JSON 來序列化和反序列化物件圖。物件階層結構被內省為最原始的類型(Stringint 等)。到此類型的路徑使用 SpEL 描述,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-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
}

如果您需要建立「結構化」Map,您可以提供 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-to-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 屬性和 prototype 範圍的 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 - 當您需要日期或空集合的 null 的特殊格式時(以及其他用途)。請參閱 JSON 轉換器 以取得有關 JsonObjectMapper 實作的更多資訊。

串流轉換器

StreamTransformerInputStream Payload 轉換為 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 -->

以下範例顯示如何使用 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
}

JSON 轉換器

Spring Integration 提供了 Object-to-JSON 和 JSON-to-Object 轉換器。以下一對範例顯示如何在 XML 中宣告它們

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

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

預設情況下,先前清單中的轉換器使用 vanilla JsonObjectMapper。它基於類別路徑中的實作。您可以提供自己的自訂 JsonObjectMapper 實作,其中包含適當的選項或基於所需的程式庫(例如 GSON),如下例所示

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

    其中輸出適配器配置了 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 轉換器之外,Spring Integration 還提供了一個內建的 #jsonPath SpEL 函數,用於運算式中。有關更多資訊,請參閱 Spring 運算式語言 (SpEL)

自 3.0 版以來,Spring Integration 還提供了一個內建的 #xpath SpEL 函數,用於運算式中。有關更多資訊,請參閱 #xpath SpEL 函數

從 4.0 版開始,ObjectToJsonTransformer 支援 resultType 屬性,以指定節點 JSON 表示法。結果節點樹狀結構表示法取決於所提供的 JsonObjectMapper 的實作。預設情況下,ObjectToJsonTransformer 使用 Jackson2JsonObjectMapper,並將物件到節點樹狀結構的轉換委派給 ObjectMapper#valueToTree 方法。當下游訊息流將 SpEL 運算式與對 JSON 資料屬性的存取一起使用時,節點 JSON 表示法為使用 JsonPropertyAccessor 提供了效率。有關更多資訊,請參閱 屬性存取器

從 5.1 版開始,resultType 可以設定為 BYTES,以產生具有 byte[] Payload 的訊息,以便在與使用此資料類型的下游處理器一起工作時提供便利。

從 5.2 版開始,可以使用 ResolvableType 設定 JsonToObjectTransformer,以支援在使用目標 JSON 處理器進行反序列化期間的泛型。此外,此元件現在首先在請求訊息標頭中查詢是否存在 JsonHeaders.RESOLVABLE_TYPEJsonHeaders.TYPE_ID,否則會退回到設定的類型。ObjectToJsonTransformer 現在還根據請求訊息 Payload 填充 JsonHeaders.RESOLVABLE_TYPE 標頭,以用於任何可能的下游情境。

從 5.2.6 版開始,可以為 JsonToObjectTransformer 提供 valueTypeExpression,以在執行時期根據請求訊息解析要從 JSON 轉換的 Payload 的 ResolvableType。預設情況下,它會查詢請求訊息中的 JsonHeaders。如果此運算式傳回 nullResolvableType 建構拋出 ClassNotFoundException,則轉換器會退回到提供的 targetType。此邏輯以運算式的形式存在,因為 JsonHeaders 可能沒有真實的類別值,而是一些必須根據某些外部登錄對應到目標類別的類型 ID。

Apache Avro 轉換器

5.2 版新增了簡單的轉換器,用於轉換為/從 Apache Avro。

它們是不複雜的,因為沒有架構登錄檔;轉換器僅使用從 Avro 架構產生的 SpecificRecord 實作中嵌入的架構。

傳送到 SimpleToAvroTransformer 的訊息必須具有實作 SpecificRecord 的 Payload;轉換器可以處理多種類型。必須使用 SpecificRecord 類別配置 SimpleFromAvroTransformer,該類別用作反序列化的預設類型。您還可以指定 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 註解,如 Annotation Support 中所述。以下範例展示如何使用 @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' 的標頭在輸出訊息中不存在。

基於編解碼器的轉換器

請參閱 編解碼器