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 社区所有即将举行的活动。

查看所有