Spring Integration Java DSL (Java 8 之前的版本): 逐行教程

工程 | Artem Bilan | 2014年12月01日 | ...

親愛的Spring社群!

最近我們釋出了 Spring Integration Java DSL: 逐行教程,其中廣泛使用了 Java 8 Lambda。我們收到一些反饋,認為這是 DSL 的一個很好的介紹,但對於那些無法遷移到 Java 8 或尚未熟悉 Lambdas 但希望利用它的人來說,需要一個類似的教程。

因此,為了幫助那些希望從 XML 配置遷移到 Java 和註解配置的 Spring Integration 使用者,我們提供了這個 逐行教程 來演示,即使沒有 Lambdas,我們也能從 Spring Integration Java DSL 的使用中獲益良多。當然,大多數人會同意 Lambda 語法提供了更簡潔的定義。

我們在這裡分析的是同一個 Cafe Demo 示例,但使用了 Java 8 之前的配置版本。許多選項是相同的,所以我們只是將它們的描述複製/貼上到這裡,以獲得完整的圖景。由於這個 Spring Integration Java DSL 配置與 Java 8 Lambda 風格非常不同,所有使用者都可以從中學習如何透過 Spring Integration Java DSL 提供的豐富選項來實現相同的結果。

我們的應用程式的原始碼放在一個類中,這是一個 Boot 應用程式;重要行都標有相應的數字,對應下面的註釋。

@SpringBootApplication                                                   // 1
@IntegrationComponentScan                                                // 2
public class Application {

  public static void main(String[] args) throws Exception {
  	ConfigurableApplicationContext ctx =
			SpringApplication.run(Application.class, args);              // 3

  	Cafe cafe = ctx.getBean(Cafe.class);                                 // 4
  	for (int i = 1; i <= 100; i++) {                                     // 5
  	  Order order = new Order(i);
  	  order.addItem(DrinkType.LATTE, 2, false);
  	  order.addItem(DrinkType.MOCHA, 3, true);
  	  cafe.placeOrder(order);
  	}

  	System.out.println("Hit 'Enter' to terminate");                      // 6
  	System.in.read();
  	ctx.close();
  }

  @MessagingGateway                                                      // 7
  public interface Cafe {

  	@Gateway(requestChannel = "orders.input")                            // 8
  	void placeOrder(Order order);                                        // 9

  }

  private final AtomicInteger hotDrinkCounter = new AtomicInteger();

  private final AtomicInteger coldDrinkCounter = new AtomicInteger();    // 10

  @Autowired
  private CafeAggregator cafeAggregator;                                 // 11

  @Bean(name = PollerMetadata.DEFAULT_POLLER)
  public PollerMetadata poller() {                                       // 12
  	return Pollers.fixedDelay(1000).get();
  }

  @Bean
  @SuppressWarnings("unchecked")
  public IntegrationFlow orders() {                                      // 13
  	return IntegrationFlows.from("orders.input")                         // 14
	  .split("payload.items", (Consumer) null)                           // 15
	  .channel(MessageChannels.executor(Executors.newCachedThreadPool()))// 16
	  .route("payload.iced",                                             // 17
	    new Consumer<RouterSpec<ExpressionEvaluatingRouter>>() {         // 18

	      @Override
	      public void accept(RouterSpec<ExpressionEvaluatingRouter> spec) {
	      	spec.channelMapping("true", "iced")
                .channelMapping("false", "hot");                         // 19
  	      }

  	    })
  	  .get();                                                            // 20
  }

  @Bean
  public IntegrationFlow icedFlow() {                                    // 21
  	return IntegrationFlows.from(MessageChannels.queue("iced", 10))      // 22
	  .handle(new GenericHandler<OrderItem>() {                          // 23

	  	@Override
	  	public Object handle(OrderItem payload, Map<String, Object> headers) {
	  	  Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
	  	  System.out.println(Thread.currentThread().getName()
	  	    + " prepared cold drink #" + coldDrinkCounter.incrementAndGet()
	  	    + " for order #" + payload.getOrderNumber() + ": " + payload);
	  	  return payload;                                                // 24
  	  	}

  	  })
  	  .channel("output")                                                 // 25
  	  .get();
  }

  @Bean
  public IntegrationFlow hotFlow() {                                     // 26
  	return IntegrationFlows.from(MessageChannels.queue("hot", 10))
	  .handle(new GenericHandler<OrderItem>() {

	  	@Override
	  	public Object handle(OrderItem payload, Map<String, Object> headers) {
	  	  Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS);    // 27
	  	  System.out.println(Thread.currentThread().getName()
	  	    + " prepared hot drink #" + hotDrinkCounter.incrementAndGet()
	  	    + " for order #" + payload.getOrderNumber() + ": " + payload);
	  	  return payload;
  	  	}

  	  })
  	  .channel("output")
  	  .get();
  }

  @Bean
  public IntegrationFlow resultFlow() {                                  // 28
    return IntegrationFlows.from("output")                               // 29
      .transform(new GenericTransformer<OrderItem, Drink>() {            // 30

        @Override
        public Drink transform(OrderItem orderItem) {
          return new Drink(orderItem.getOrderNumber(),
            orderItem.getDrinkType(),
            orderItem.isIced(),
            orderItem.getShots());                                       // 31
        }

      })
      .aggregate(new Consumer<AggregatorSpec>() {                        // 32

        @Override
        public void accept(AggregatorSpec aggregatorSpec) {
          aggregatorSpec.processor(cafeAggregator, null);                // 33
        }

      }, null)
      .handle(CharacterStreamWritingMessageHandler.stdout())             // 34
    .get();
  }


  @Component
  public static class CafeAggregator {                                   // 35

  	@Aggregator                                                          // 36
  	public Delivery output(List<Drink> drinks) {
  	  return new Delivery(drinks);
  	}

  	@CorrelationStrategy                                                 // 37
  	public Integer correlation(Drink drink) {
  	  return drink.getOrderNumber();
  	}

  }

}

逐行檢查程式碼...

1. ````java @SpringBootApplication ```` 這是 Spring Boot 1.2 中的一個新元註解。它包含 `@Configuration` 和 `@EnableAutoConfiguration`。由於我們處於 Spring Integration 應用程式中,並且 Spring Boot 為其提供了自動配置,因此 `@EnableIntegration` 會被自動應用,以初始化 Spring Integration 基礎設施,包括一個用於 Java DSL 的環境 - `DslIntegrationConfigurationInitializer`,它由 `/META-INF/spring.factories` 中的 `IntegrationConfigurationBeanFactoryPostProcessor` 拾取。 2. ````java @IntegrationComponentScan ```` 這是 Spring Integration 中 `@ComponentScan` 的類似物,用於掃描基於介面的元件(Spring Framework 的 `@ComponentScan` 只檢視類)。Spring Integration 支援發現用 `@MessagingGateway` 註解的介面(見下面的 #7)。 3. ````java ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args); ```` 我們類的 `main` 方法旨在啟動 Spring Boot 應用程式,使用此類的配置,並透過 Spring Boot 啟動一個 `ApplicationContext`。此外,它還將命令列引數委託給 Spring Boot。例如,您可以指定 `--debug` 來檢視有關啟動自動配置報告的日誌。 4. ````java Cafe cafe = ctx.getBean(Cafe.class); ```` 由於我們已經有了一個 `ApplicationContext`,我們可以開始與應用程式互動。`Cafe` 是入口點 - 在 EIP 術語中是一個 `gateway`。閘道器只是介面,應用程式不直接與訊息傳遞 API 互動;它只處理域(見下面的 #7)。 5. ````java for (int i = 1; i <= 100; i++) { ```` 為了演示咖啡館的“工作”,我們發起了 100 個訂單,包含兩種飲品 - 一種熱飲和一種冷飲。並將 `Order` 傳送給 `Cafe` 閘道器。 6. ````java System.out.println("Hit 'Enter' to terminate"); ```` 通常 Spring Integration 應用程式是非同步的,因此為了避免從 `main` 執行緒過早退出,我們在使用者透過命令列進行某些互動之前,會阻塞 `main` 方法。非守護執行緒會保持應用程式開啟,但 `System.read()` 為我們提供了一個乾淨關閉應用程式的機制。 7. ````java @MessagingGateway ```` 這個註解用於標記一個業務介面,表明它是應用程式與整合層之間的 `gateway`。它是 Spring Integration XML 配置中 `` 元件的類似物。Spring Integration 會為這個介面建立一個 `Proxy`,並將其作為一個 bean 註冊到應用程式上下文中。這個 `Proxy` 的目的是將引數封裝到 `Message` 物件中,並根據提供的選項將其傳送到 `MessageChannel`。 8. ````java @Gateway(requestChannel = "orders.input") ```` 方法級別的註解,透過方法以及目標整合流來區分業務邏輯。在這個示例中,我們使用 `orders.input` 的 `requestChannel` 引用,這是我們 `IntegrationFlow` 輸入通道的 `MessageChannel` bean 名稱(見下面的 #14)。 9. ````java void placeOrder(Order order); ```` 介面方法是從應用程式與整合層互動的中心點。此方法返回型別為 `void`。這意味著我們的整合流是 `單向`的,我們只發送訊息到整合流,而不等待回覆。 10. ````java private AtomicInteger hotDrinkCounter = new AtomicInteger(); private AtomicInteger coldDrinkCounter = new AtomicInteger(); ```` 兩個計數器,用於收集我們的咖啡館如何處理飲品的資訊。 11. ````java @Autowired private CafeAggregator cafeAggregator; ```` 用於 `Aggregator` 邏輯的 POJO(見下面的 #33 和 #35)。由於這是一個 Spring bean,我們可以簡單地將其注入到當前的 `@Configuration` 中,並在下面的任何地方使用它,例如從 `.aggregate()` EIP 方法中。 12. ````java @Bean(name = PollerMetadata.DEFAULT_POLLER) public PollerMetadata poller() { ```` `預設` `poller` bean。它是 Spring Integration XML 配置中 `` 元件的類似物。對於 `inputChannel` 是 `PollableChannel` 的端點是必需的。在這種情況下,它對於兩個 Cafe `佇列` - 熱飲和冷飲是必需的(見下面的 #18)。這裡我們使用 DSL 專案的 `Pollers` 工廠,並使用其方法鏈式的流暢 API 來構建 poller 元資料。注意,如果需要為某個端點使用一個特定的 `poller`(而不是預設 poller),可以直接在 `IntegrationFlow` 定義中使用 `Pollers`。 13. ````java @Bean public IntegrationFlow orders() { ```` `IntegrationFlow` bean 定義。它是 Spring Integration Java DSL 的核心元件,雖然它在執行時不扮演任何角色,只在 bean 註冊階段起作用。所有下面的程式碼都向 `IntegrationFlow` 物件註冊 Spring Integration 元件(`MessageChannel`、`MessageHandler`、`EventDrivenConsumer`、`MessageProducer`、`MessageSource` 等),該物件由 `IntegrationFlowBeanPostProcessor` 解析,以處理這些元件並將它們註冊為應用程式上下文中的 bean(一些元素,如通道,可能已存在)。 14. ````java return IntegrationFlows.from("orders.input") ```` `IntegrationFlows` 是啟動 `IntegrationFlow` 的主要 `工廠` 類。它提供了許多過載的 `.from()` 方法,允許從 `SourcePollingChannelAdapter`(用於 `MessageSource` 實現,例如 `JdbcPollingChannelAdapter`)、`MessageProducer`(例如 `WebSocketInboundChannelAdapter`)或簡單地從 `MessageChannel` 開始一個流程。所有 ".from()" 選項都有幾種便捷的變體來配置適合 `IntegrationFlow` 起點的元件。這裡我們只使用一個通道名稱,它在解析 `IntegrationFlow` 期間被轉換為 `DirectChannel` bean 定義。在 Java 8 版本中,我們這裡使用了 `Lambda 定義` - 這個 `MessageChannel` 是以基於 `IntegrationFlow` bean 名稱生成的 bean 名稱隱式建立的。 15. ````java .split("payload.items", (Consumer) null) ```` 由於我們的整合流透過 `orders.input` 通道接收訊息,我們已準備好處理它們。在我們的場景中,第一個 EIP 方法是 `.split()`。我們知道來自 `orders.input` 通道的 `payload` 是一個 `Order` 域物件,所以我們可以簡單地在這裡使用 Spring (SpEL) 表示式來返回 `Collection`。這樣,它就執行了 `split` EI 模式,並將集合中的每個條目作為一個單獨的訊息傳送到下一個通道。在後臺,`.split()` 方法會註冊一個 `ExpressionEvaluatingSplitter` `MessageHandler` 實現和一個 `EventDrivenConsumer` 來處理該 `MessageHandler`,並將 `orders.input` 通道作為 `inputChannel` 連線起來。

`.split()` EIP 方法的第二個引數是 `endpointConfigurer`,用於自定義 `autoStartup`、`requiresReply`、`adviceChain` 等選項。我們在這裡使用 `null` 來表明我們依賴於端點的預設選項。許多 EIP 方法都提供了帶和不帶 `endpointConfigurer` 的過載版本。目前,沒有提供不帶 `endpointConfigurer` 引數的 `.split(String expression)` EIP 方法;這將在未來的版本中解決。

16. ````java .channel(MessageChannels.executor(Executors.newCachedThreadPool())) ```` `.channel()` EIP 方法允許在端點之間指定具體的 `MessageChannel`,就像透過 Spring Integration XML 配置中的 `output-channel`/`input-channel` 屬性對一樣。預設情況下,DSL 整合流定義中的端點透過 `DirectChannel` 連線,這些通道的 bean 名稱是根據 `IntegrationFlow` bean 名稱和流鏈中的 `索引` 生成的。在本例中,我們從 `Channels` 工廠類中選擇了一個特定的 `MessageChannel` 實現;這裡選擇的通道是一個 `ExecutorChannel`,它允許將訊息從 `splitter` 分發到單獨的 `Thread` 中,以便在下游流程中並行處理它們。 17. ````java .route("payload.iced", ```` 我們場景中的下一個 EIP 方法是 `.route()`,用於將 `hot/iced` 訂單項傳送到不同的咖啡館廚房。我們再次使用 SpEL 表示式從傳入訊息中獲取 `routingKey`。在 Java 8 版本中,我們使用了 `方法引用` Lambda 表示式,但對於 Java 8 之前的版本,我們必須使用 SpEL 或內聯介面實現。流程中的許多匿名類會使流程難以閱讀,因此我們大多數情況下更偏愛 SpEL。 18. ````java new Consumer>() { ```` `.route()` EIP 方法的第二個引數是一個函式式介面 `Consumer`,用於使用 `RouterSpec` Builder 指定 `ExpressionEvaluatingRouter` 選項。由於在 Java 8 之前的版本中我們沒有其他選擇,我們在這裡只提供這個介面的一個內聯實現。 19. ````java spec.channelMapping("true", "iced") .channelMapping("false", "hot"); ```` 透過 `Consumer>#accept()` 實現,我們可以提供所需的 `AbstractMappingMessageRouter` 選項。其中之一是 `channelMappings`,我們透過路由表示式的結果以及相應結果的目標 `MessageChannel` 來指定路由邏輯。在這種情況下,`iced` 和 `hot` 是下面 `IntegrationFlow` 的 `MessageChannel` 名稱。 20. ````java .get(); ```` 這完成了流程。任何 `IntegrationFlows.from()` 方法都會返回一個 `IntegrationFlowBuilder` 例項,而這個 `get()` 方法從 `IntegrationFlowBuilder` 配置中提取一個 `IntegrationFlow` 物件。從 `.from()` 開始到 `.get()` 方法之前的所有內容都是一個 `IntegrationFlow` 定義。所有定義的元件都儲存在 `IntegrationFlow` 中,並在 bean 建立階段由 `IntegrationFlowBeanPostProcessor` 處理。 21. ````java @Bean public IntegrationFlow icedFlow() { ```` 這是第二個 `IntegrationFlow` bean 定義 - 用於 `iced`(冷飲)飲品。這裡我們演示瞭如何將多個 `IntegrationFlow` 串聯起來建立一個複雜的應用程式。注意:不建議將一個 `IntegrationFlow` 注入到另一個;這可能會導致意外行為。由於它們提供用於 bean 註冊的整合元件和 `MessageChannel`,其中之一,最好的連線和注入方式是透過 `MessageChannel` 或 `@MessagingGateway` 介面。 22. ````java return IntegrationFlows.from(MessageChannels.queue("iced", 10)) ```` `iced` `IntegrationFlow` 從一個容量為 `10` 條訊息的 `QueueChannel` 開始;它以 `iced` 的名稱註冊為一個 bean。您還記得我們使用這個名稱作為路由對映之一(見上面的 #19)。

在我們的示例中,我們這裡使用了一個受限的 QueueChannel 來反映現實生活中咖啡館廚房繁忙的狀態。而在這裡,我們需要為監聽此通道的下一個端點設定 全域性 poller

23. ````java .handle(new GenericHandler() { ```` `iced` 流程的 `.handle()` EIP 方法演示了具體的咖啡館廚房工作。由於我們無法像使用 Java 8 Lambda 表示式那樣最小化程式碼,我們在這裡提供了一個 `GenericHandler` 函式式介面的內聯實現,並指定了預期的 `payload` 型別作為泛型引數。在 Java 8 示例中,我們將這個 `.handle()` 分配給 `PublishSubscribeChannel` 的多個訂閱者子流程。然而,在這種情況下,邏輯全部在一個方法中實現。 24. ````java Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); System.out.println(Thread.currentThread().getName() + " prepared cold drink #" + coldDrinkCounter.incrementAndGet() + " for order #" + payload.getOrderNumber() + ": " + payload); return payload; ```` 當前 `.handle()` EIP 元件的業務邏輯實現。透過 `Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);`,我們只是阻塞當前 `Thread` 一段時間以演示咖啡館廚房準備飲品的效率。之後,我們向 `STDOUT` 報告飲品已準備好,並從 `GenericHandler` 返回當前的 `OrderItem` 以便在我們的 `IntegrationFlow` 中傳遞給下一個端點。在後臺,DSL 框架會為 `MethodInvokingMessageProcessor` 註冊一個 `ServiceActivatingHandler`,以便在執行時呼叫 `GenericHandler#handle`。此外,框架還會為上面的 `QueueChannel` 註冊一個 `PollingConsumer` 端點。這個端點依賴於 `default poller` 從佇列中輪詢訊息。當然,我們總是可以為任何具體的端點使用一個特定的 `poller`。在這種情況下,我們需要為 `.handle()` EIP 方法提供第二個 `endpointConfigurer` 引數。 25. ````java .channel("output") ```` 由於這並不是我們咖啡館場景的結束,我們使用便捷的 EIP 方法 `.channel()` 和 `MessageChannel` bean 的名稱(見下面的 #29)將當前流程的結果傳送到 `output` 通道。這是當前冷飲子流程的邏輯結束,所以我們使用 `.get()` 方法返回 `IntegrationFlow`。以回覆生成處理程式結束但沒有最終 `.channel()` 的流程會將回復返回到訊息的 `replyChannel` 頭。 26. ````java @Bean public IntegrationFlow hotFlow() { ```` 用於 `hot`(熱飲)飲品的 `IntegrationFlow` 定義。它與前面的 `iced` 飲品流程類似,但具有特定的 `hot` 業務邏輯。它從 `hot` `QueueChannel` 開始,該通道從上面的路由器對映而來。 27. ````java Uninterruptibles.sleepUninterruptibly(5, TimeUnit.SECONDS); ```` 熱飲的 `sleepUninterruptibly`。沒錯,我們需要更多時間來燒水! 28. ````java @Bean public IntegrationFlow resultFlow() { ```` 另一個 `IntegrationFlow` bean 定義,用於根據 `Drink`(飲品)為咖啡館客戶準備 `Delivery`(配送)。 29. ````java return IntegrationFlows.from("output") ```` `resultFlow` 從一個 `DirectChannel` 開始,該通道在 bean 定義階段使用此提供的名稱建立。您應該記住,我們在這些定義中的最後一個 `.channel()` 中使用了來自咖啡館廚房流程的 `output` 通道名稱。 30. ````java .transform(new GenericTransformer() { ```` `.transform()` EIP 方法用於適當的模式實現,並期望將某個物件轉換為另一個載荷。在我們的示例中,我們使用 `GenericTransformer` 函式式介面的內聯實現將 `OrderItem` 轉換為 `Drink`,並透過泛型引數指定。在後臺,DSL 框架註冊了一個 `MessageTransformingHandler` 和一個 `EventDrivenConsumer` 端點,並使用預設選項從 `output` `MessageChannel` 消費訊息。 31. ````java public Drink transform(OrderItem orderItem) { return new Drink(orderItem.getOrderNumber(), orderItem.getDrinkType(), orderItem.isIced(), orderItem.getShots()); } ```` 特定於業務的 `GenericTransformer#transform()` 實現,演示了我們如何利用 Java 泛型將一個 `payload` 轉換為另一個。注意:Spring Integration 在呼叫任何方法之前會使用 `ConversionService`,如果您提供特定的 `Converter` 實現,則當框架具有適當註冊的 `Converter` 時,某些域 `payload` 可以自動轉換為另一個。 32. ````java .aggregate(new Consumer() { ```` `.aggregate()` EIP 方法提供了配置 `AggregatingMessageHandler` 及其端點的選項,類似於使用 Spring Integration XML 配置中的 `` 元件時所做的。當然,使用 Java DSL,我們可以在原地進行更強大的聚合器配置,而無需任何其他額外的 bean。但是,我們在這裡演示了一個使用註解的聚合器配置(見下面的 #35)。從咖啡館的業務邏輯角度來看,我們正在為初始 `Order` 組合 `Delivery`,因為我們在流程的開始附近將原始訂單 `.split()` 成了 `OrderItem`s。 33. ````java public void accept(AggregatorSpec aggregatorSpec) { aggregatorSpec.processor(cafeAggregator, null); } ```` `AggregatorSpec` 的 `Consumer` 的內聯實現。使用 `aggregatorSpec` Builder,我們可以為 `aggregator` 元件提供所需的選項,它將作為 `AggregatingMessageHandler` bean 註冊。在這裡,我們僅提供 `processor` 作為對自動注入的(見上面的 #11)`CafeAggregator` 元件(見下面的 #35)的引用。`.processor()` 選項的第二個引數是 `methodName`。由於我們依賴於 POJO 的聚合器註解配置,因此無需在此處提供方法,框架將在後臺確定正確的 POJO 方法。 34. ````java .handle(CharacterStreamWritingMessageHandler.stdout()) ```` 這是我們流程的結尾 - `Delivery` 已交付給客戶!我們只是使用 Spring Integration 核心中的開箱即用 `CharacterStreamWritingMessageHandler` 將訊息 `payload` 列印到 STDOUT。這是一個展示如何從 Java DSL 使用 Spring Integration Core(及其模組)中的現有元件的示例。 35. ````java @Component public static class CafeAggregator { ```` 指定上面 `aggregator` 業務邏輯的 bean。這個 bean 被 `@SpringBootApplication` 元註解的一部分 `@ComponentScan` 拾取(見上面的 #1)。因此,這個元件成為一個 bean,我們可以將其自動注入 (`@Autowired`) 到應用程式上下文中的其他元件(見上面的 #11)。 36. ````java @Aggregator public Delivery output(List drinks) { return new Delivery(drinks); } ```` POJO 特定的 `MessageGroupProcessor`,用於基於聚合訊息的載荷構建輸出 `payload`。由於我們用 `@Aggregator` 註解標記了這個方法,所以目標 `AggregatingMessageHandler` 可以提取這個方法用於 `MethodInvokingMessageGroupProcessor`。 37. ````java @CorrelationStrategy public Integer correlation(Drink drink) { return drink.getOrderNumber(); } ```` POJO 特定的 `CorrelationStrategy`,用於從每個傳入的聚合器訊息中提取自定義 `correlationKey`。由於我們用 `@CorrelationStrategy` 註解標記了這個方法,所以目標 `AggregatingMessageHandler` 可以提取這個方法用於 `MethodInvokingCorrelationStrategy`。有一個類似的、不言自明的 `@ReleaseStrategy` 註解,但在我們的 Cafe 示例中,我們只依賴於預設的 `SequenceSizeReleaseStrategy`,它基於我們整合流程開始時 `splitter` 填充的 `sequenceDetails` 訊息頭。

好了,我們已經描述了基於 Spring Integration Java DSL 的 Cafe Demo 示例,其中不包含 Java Lambda 支援。將其與 XML 示例 進行比較,並查閱 Lambda 支援教程 以獲取更多關於 Spring Integration 的資訊。

正如您所見,在沒有 Lambda 的情況下使用 DSL 會稍微冗長一些,因為您需要為函式式介面的內聯匿名實現提供樣板程式碼。然而,我們相信支援 DSL 對於那些還無法遷移到 Java 8 的使用者來說很重要。DSL 的許多優勢(流暢的 API、編譯時驗證等)對所有使用者都是可用的。

Lambda 的使用延續了 Spring Framework 減少或消除樣板程式碼的傳統,因此我們鼓勵使用者嘗試 Java 8 和 Lambda,並鼓勵他們的組織考慮允許在 Spring Integration 應用程式中使用 Java 8。

此外,請參閱 參考手冊 以獲取更多資訊。

一如既往,我們期待您的評論和反饋(StackOverflow (spring-integration 標籤)、Spring JIRAGitHub),我們非常歡迎貢獻

感謝您的時間和耐心閱讀本文!

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有