服務啟用器

服務啟用器是將任何 Spring 管理的物件連線到輸入通道的端點型別,使其能夠扮演服務的角色。如果服務產生輸出,它也可以連線到輸出通道。另外,一個產生輸出的服務可以位於處理管道或訊息流的末端,在這種情況下可以使用入站訊息的 replyChannel 訊息頭。如果未定義輸出通道,則這是預設行為。與此處描述的大多數配置選項一樣,相同的行為實際上適用於大多數其他元件。

服務啟用器本質上是一個通用端點,用於使用輸入訊息(載荷和訊息頭)呼叫某個物件上的方法。其內部邏輯基於 MessageHandler,它可以是針對特定用例的任何可能的實現,例如 DefaultMessageSplitterAggregatingMessageHandlerSftpMessageHandlerJpaOutboundGateway 等。因此,本參考手冊中提到的任何出站閘道器和出站通道介面卡都應視為此服務啟用器端點的特定擴充套件;它們最終都呼叫了某個物件的方法。

配置服務啟用器

使用 Java 和註解配置時,只需使用 @ServiceActivator 註解標記相應的方法,框架在訊息從輸入通道消費時會呼叫它。

public class SomeService {

    @ServiceActivator(inputChannel = "exampleChannel")
    public void exampleHandler(SomeData payload) {
        ...
    }

}

更多資訊請參見 註解支援

對於 Java、Groovy 或 Kotlin DSL,IntegrationFlow.handle() 運算子代表一個服務啟用器。

  • Java DSL

  • Kotlin DSL

  • Groovy DSL

@Bean
public IntegrationFlow someFlow() {
    return IntegrationFlow
             .from("exampleChannel")
             .handle(someService, "exampleHandler")
             .get();
}
@Bean
fun someFlow() =
    integrationFlow("exampleChannel") {
        handle(someService, "exampleHandler")
    }
@Bean
someFlow() {
    integrationFlow 'exampleChannel',
            {
                handle someService, 'exampleHandler'
            }
}

更多關於 DSL 的資訊請參見相應的章節。

使用 XML 配置建立服務啟用器時,使用 'service-activator' 元素並帶有 'input-channel' 和 'ref' 屬性,如下例所示。

<int:service-activator input-channel="exampleChannel" ref="exampleHandler"/>

上述配置選擇了 exampleHandler 中符合以下訊息傳遞要求的任何方法:

  • 使用 @ServiceActivator 註解標記

  • public

  • 如果 requiresReply == true 則不返回 void

執行時要呼叫的目標方法是為每個請求訊息根據其 payload 型別選擇的,如果目標類上存在一個 Message<?> 型別的方法,則會回退到該方法。

從版本 5.0 開始,一個服務方法可以使用 @org.springframework.integration.annotation.Default 進行標記,作為所有不匹配情況的備用。當使用 內容型別轉換 並且目標方法在轉換後被呼叫時,這會很有用。

要委託給任何物件的顯式定義方法,可以新增 method 屬性,如下例所示:

<int:service-activator input-channel="exampleChannel" ref="somePojo" method="someMethod"/>

在任何一種情況下,當服務方法返回非 null 值時,端點會嘗試將回復訊息傳送到適當的回覆通道。為了確定回覆通道,它首先檢查端點配置中是否提供了 output-channel,如下例所示:

<int:service-activator input-channel="exampleChannel" output-channel="replyChannel"
                       ref="somePojo" method="someMethod"/>

如果方法返回結果並且未定義 output-channel,框架會檢查請求訊息的 replyChannel 訊息頭的值。如果該值可用,它會檢查其型別。如果它是 MessageChannel,回覆訊息會發送到該通道。如果它是一個 String,端點會嘗試將通道名稱解析為通道例項。如果通道無法解析,會丟擲 DestinationResolutionException。如果可以解析,訊息會被髮送到那裡。如果請求訊息沒有 replyChannel 訊息頭且 reply 物件是一個 Message,則會查閱其 replyChannel 訊息頭以確定目標地址。這是 Spring Integration 中用於請求-回覆訊息傳遞的技術,它也是返回地址模式的一個例子。

如果你的方法返回結果,並且你希望丟棄它並結束流程,你應該將 output-channel 配置為傳送到 NullChannel。為了方便,框架會註冊一個名為 nullChannel 的通道。更多資訊請參見 特殊通道

服務啟用器是那些不需要產生回覆訊息的元件之一。如果你的方法返回 null 或具有 void 返回型別,則服務啟用器在方法呼叫後退出,不傳送任何訊號。此行為可以透過 AbstractReplyProducingMessageHandler.requiresReply 選項控制,該選項在使用 XML namespace 配置時也暴露為 requires-reply。如果該標誌設定為 true 且方法返回 null,則會丟擲 ReplyRequiredException

服務方法中的引數可以是訊息或任意型別。如果是後者,則假定它是訊息載荷,該載荷會從訊息中提取並注入到服務方法中。我們通常推薦這種方法,因為它遵循並推廣了在使用 Spring Integration 時的 POJO 模型。引數還可以帶有 @Header@Headers 註解,如 註解支援 中所述。

服務方法不需要有任何引數,這意味著你可以實現事件風格的服務啟用器(你只需要關注服務方法的呼叫),而無需擔心訊息的內容。可以將其視為一個 null JMS 訊息。這種實現的示例用例是一個簡單的計數器或監視器,用於監控輸入通道上接收到的訊息數量。

從版本 4.1 開始,框架會正確地將訊息屬性(payloadheaders)轉換為 Java 8 的 Optional POJO 方法引數,如下例所示:

public class MyBean {
    public String computeValue(Optional<String> payload,
               @Header(value="foo", required=false) String foo1,
               @Header(value="foo") Optional<String> foo2) {
        if (payload.isPresent()) {
            String value = payload.get();
            ...
        }
        else {
           ...
       }
    }

}

如果自定義服務啟用器處理程式實現可以在其他 <service-activator> 定義中重用,我們通常建議使用 ref 屬性。但是,如果自定義服務啟用器處理程式實現僅在單個 <service-activator> 定義中使用,你可以提供一個內部 bean 定義,如下例所示:

<int:service-activator id="exampleServiceActivator" input-channel="inChannel"
            output-channel = "outChannel" method="someMethod">
    <beans:bean class="org.something.ExampleServiceActivator"/>
</int:service-activator>
在同一個 <service-activator> 配置中同時使用 ref 屬性和內部處理程式定義是不允許的,因為它會建立歧義條件並導致丟擲異常。
如果 ref 屬性引用了一個擴充套件 AbstractMessageProducingHandler 的 bean(例如框架本身提供的處理程式),則透過將輸出通道直接注入到處理程式中來最佳化配置。在這種情況下,每個 ref 都必須引用一個獨立的 bean 例項(或 prototype 作用域的 bean),或者使用內部 <bean/> 配置型別。如果意外地從多個 bean 引用了同一個訊息處理程式,則會收到配置異常。

服務啟用器與 Spring Expression Language (SpEL)

自 Spring Integration 2.0 起,服務啟用器也可以利用 SpEL

例如,你可以呼叫任何 bean 方法,而無需在 ref 屬性中指向 bean 或將其作為內部 bean 定義包含在內,如下所示:

<int:service-activator input-channel="in" output-channel="out"
	expression="@accountService.processAccount(payload, headers.accountId)"/>

	<bean id="accountService" class="thing1.thing2.Account"/>

在上述配置中,我們沒有使用 ref 或內部 bean 注入 'accountService',而是使用了 SpEL 的 @beanId 標記並呼叫了一個接受與訊息載荷相容型別的方法。我們還傳遞了一個訊息頭值。任何有效的 SpEL 表示式都可以針對訊息中的任何內容進行評估。對於簡單場景,如果所有邏輯都可以封裝在這樣的表示式中,你的服務啟用器無需引用 bean,如下例所示:

<int:service-activator input-channel="in" output-channel="out" expression="payload * 2"/>

在上述配置中,我們的服務邏輯是將載荷值乘以二。SpEL 讓我們能夠相對輕鬆地處理它。

更多關於配置服務啟用器的資訊,請參見 Java DSL 章中的 服務啟用器與 .handle() 方法

非同步服務啟用器

服務啟用器由呼叫執行緒呼叫。如果輸入通道是 SubscribableChannel,這就是上游執行緒;如果是 PollableChannel,這就是輪詢執行緒。如果服務返回 CompletableFuture<?>,預設操作是將它作為傳送到輸出(或回覆)通道的訊息的載荷。從版本 4.3 開始,你現在可以將 async 屬性設定為 true(使用 Java 配置時透過 setAsync(true))。如果當 async 屬性設定為 true 時服務返回 CompletableFuture<?>,則呼叫執行緒會立即釋放,回覆訊息會在完成 future 的執行緒(在你服務內部)上傳送。這對於使用 PollableChannel 的長時間執行的服務特別有利,因為輪詢執行緒被釋放,可以在框架內執行其他服務。

如果服務以 Exception 完成 future,則會發生正常的錯誤處理。ErrorMessage 會發送到 errorChannel 訊息頭(如果存在)。否則,ErrorMessage 會發送到預設的 errorChannel(如果可用)。

從版本 6.1 開始,如果 AbstractMessageProducingHandler 的輸出通道配置為 ReactiveStreamsSubscribableChannel,則預設啟用非同步模式。如果處理程式結果不是響應式型別或 CompletableFuture<?>,則無論輸出通道型別如何,都會發生常規的回覆生成過程。

另請參見 Reactive Streams 支援 以獲取更多資訊。

服務啟用器和方法返回型別

服務方法可以返回任何型別,該型別將成為回覆訊息的載荷。在這種情況下,會建立一個新的 Message<?> 物件,並複製請求訊息中的所有訊息頭。這對於大多數 Spring Integration 的 MessageHandler 實現以相同方式工作,當互動基於 POJO 方法呼叫時。

方法也可以返回完整的 Message<?> 物件。但是,請記住,與 轉換器 不同,對於服務啟用器,如果回覆訊息中不存在請求訊息中的訊息頭,則該訊息會透過複製請求訊息中的訊息頭進行修改。因此,如果你的方法引數是 Message<?> 並且你在服務方法中複製了一些(但不是全部)現有訊息頭,它們會重新出現在回覆訊息中。服務啟用器不負責從回覆訊息中刪除訊息頭,並且為了遵循松耦合原則,最好在整合流中新增 HeaderFilter。另外,可以使用轉換器代替服務啟用器,但在那種情況下,當返回完整的 Message<?> 時,方法完全負責該訊息,包括複製請求訊息頭(如果需要)。你必須確保重要框架訊息頭(例如 replyChannelerrorChannel),如果存在,必須得到保留。