DSL 基礎

org.springframework.integration.dsl 包包含前面提到的 IntegrationFlowBuilder API 和許多 IntegrationComponentSpec 實現。這些實現也是構建器,提供了流暢的 API 來配置具體的端點。IntegrationFlowBuilder 基礎設施為基於訊息的應用程式提供了常見的企業整合模式 (EIP),例如通道、端點、輪詢器和通道攔截器。

重要

IntegrationComponentSpec 是一個 FactoryBean 實現,因此其 getObject() 方法不得從 bean 定義中呼叫。IntegrationComponentSpec 實現必須保持原樣用於 bean 定義,框架將管理其生命週期。對於 IntegrationFlow bean 定義,必須使用目標 IntegrationComponentSpec 型別(一個 FactoryBean 值)的 bean 方法引數注入,而不是使用 bean 方法引用。

在 DSL 中,端點被表達為動詞以提高可讀性。以下列表包含常見的 DSL 方法名稱和關聯的 EIP 端點

  • transform → Transformer (轉換)

  • filter → Filter (過濾)

  • handle → ServiceActivator (處理)

  • split → Splitter (分割)

  • aggregate → Aggregator (聚合)

  • route → Router (路由)

  • bridge → Bridge (橋接)

從概念上講,整合過程是透過將這些端點組合成一個或多個訊息流來構建的。請注意,EIP 並未正式定義“訊息流”這個術語,但將其視為使用眾所周知的訊息傳遞模式的工作單元很有用。DSL 提供了一個 IntegrationFlow 元件來定義通道及其之間的端點組合,但現在 IntegrationFlow 僅起配置作用,用於在應用程式上下文中填充真正的 bean,並且不在執行時使用。然而,可以將 IntegrationFlow 的 bean 自動裝配為 Lifecycle 以控制整個流的 start()stop(),這會委託給與此 IntegrationFlow 相關聯的所有 Spring Integration 元件。以下示例使用 IntegrationFlow 流暢 API,透過 IntegrationFlowBuilder 中的 EIP 方法來定義一個 IntegrationFlow bean

@Bean
public IntegrationFlow integerFlow() {
    return IntegrationFlow.from("input")
            .<String, Integer>transform(Integer::parseInt)
            .get();
}

transform 方法接受一個 lambda 作為端點引數來操作訊息載荷。該方法的實際引數是一個 GenericTransformer<S, T> 例項。因此,這裡可以使用任何提供的轉換器(ObjectToJsonTransformerFileToStringTransformer 等)。

在底層,IntegrationFlowBuilder 會識別 MessageHandler 和相應的端點,分別使用 MessageTransformingHandlerConsumerEndpointFactoryBean。考慮另一個示例

@Bean
public IntegrationFlow myFlow() {
    return IntegrationFlow.from("input")
                .filter("World"::equals)
                .transform("Hello "::concat)
                .handle(System.out::println)
                .get();
}

前面的示例組合了一個 Filter → Transformer → Service Activator 的序列。該流是“單向的”。也就是說,它不提供回覆訊息,只將載荷列印到 STDOUT。端點透過直接通道自動連線。

Lambda 和 Message<?> 引數

在使用 EIP 方法中的 lambda 時,“輸入”引數通常是訊息載荷。如果你希望訪問整個訊息,請使用接受 Class<?> 作為第一個引數的過載方法之一。例如,這不起作用

.<Message<?>, Foo>transform(m -> newFooFromMessage(m))

這將在執行時因 ClassCastException 而失敗,因為 lambda 不會保留引數型別,並且框架將嘗試將載荷轉換為 Message<?>

相反,請使用

.(Message.class, m -> newFooFromMessage(m))
Bean 定義覆蓋

Java DSL 可以為流定義中內聯定義的物件註冊 bean,也可以重用現有的、注入的 bean。如果內聯物件和現有 bean 定義定義了相同的 bean 名稱,則會丟擲 BeanDefinitionOverrideException,表明此類配置是錯誤的。但是,當你處理 prototype bean 時,無法從整合流處理器檢測到現有的 bean 定義,因為每次我們從 BeanFactory 呼叫 prototype bean 時都會獲得一個新例項。因此,提供的例項在 IntegrationFlow 中原樣使用,不進行任何 bean 註冊,也不對其與現有 prototype bean 定義進行任何可能的檢查。然而,如果該物件具有顯式的 id 且該名稱的 bean 定義處於 prototype 範圍,則會為該物件呼叫 BeanFactory.initializeBean()