轉換器
訊息轉換器在實現訊息生產者和訊息消費者的鬆散耦合方面發揮著非常重要的作用。您可以在這些元件之間新增轉換器,而無需每個訊息生產元件都知道下一個消費者期望的型別。通用轉換器,例如將 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-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 返回值時丟擲異常,與轉換器相同。
轉換器和 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 工廠),其中輸出是一個帶有 String payload 的 Message。該 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 名稱空間中的“出站通道介面卡”。雖然該通道介面卡預設只支援 String、位元組陣列或 java.io.File 負載,但在介面卡之前立即新增此轉換器可以處理必要的轉換。只要 toString() 呼叫的結果是您想要寫入檔案的內容,這就很有效。否則,您可以使用前面所示的通用“轉換器”元素提供自定義的基於 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 序列化,但您可以透過使用 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-to-Map 和 Map-to-Object 轉換器
Spring Integration 還提供了 Object-to-Map 和 Map-to-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-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
}
如果您需要建立“結構化”對映,可以提供 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-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 屬性和原型範圍的 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 -->
以下示例顯示瞭如何使用 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 提供物件到 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 開始, |
您可能希望考慮使用 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 開始,如果輸入訊息尚未包含 如果您希望將 |
從版本 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 使用 JacksonJsonObjectMapper 並將物件的轉換委託給節點樹到 ObjectMapper#valueToTree 方法。節點 JSON 表示為在使用 JacksonPropertyAccessor 訪問 JSON 資料屬性的 SpEL 表示式的下游訊息流提供了效率。有關更多資訊,請參閱屬性訪問器。
從版本 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。
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 資料內容進行轉換的支援。
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 註解將 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”的標頭不會出現在出站訊息中。
基於編解碼器的轉換器
請參閱編解碼器。