Spring Integration功能概览

Spring Integration涵盖了大量的集成场景。如果想将所有的内容放到一章中,就像把一头大象装进信封一样不现实。在这里,我只会向你展示Spring Integration这头大象的照片,而不是对Spring Integration进行面面俱到的讲解,目的就是让你能够了解它是如何运行的。随后,我们会再创建一个集成流,为Taco Cloud应用添加新的功能。

集成流是由一个或多个如下介绍的组件组成的。在继续编写代码之前,我们先看一下这些组件在集成流中所扮演的角色。

  • 通道(channel):将消息从一个元素传递到另一个元素。

  • 过滤器(filter):基于某些断言,条件化地允许某些消息通过流。

  • 转换器(transformer):改变消息的值、将消息载荷从一种类型转换成另一种类型。

  • 路由器(router):将消息路由至一个或多个通道,通常会基于消息的头信息进行路由。

  • 切分器(splitter):将传入的消息切分成两份或更多份,然后发送至不同的通道。

  • 聚合器(aggregator):与切分器的操作相反,将来自不同通道的多个消息合并成一个消息。

  • 服务激活器(service activator):将消息传递给某个Java方法处理,并将返回值发布到输出通道上。

  • 通道适配器(channel adapter):将通道连接到某些外部系统或传输方式。可以接受输入,也可以写出到外部系统。

  • 网关(gateway):通过接口,将数据传递到集成流中。

在定义文件写入集成流时,我们已经看过其中的一些组件了。FileWriterGateway是一个网关,通过它,应用可以提交要写入文件的文本。我们还定义了一个转换器,将给定的文本转换成大写的形式,随后,我们定义了一个出站通道适配器,它执行将文本写入文件的任务。这个流有两个通道:textInChannel和fileWriterChannel,它们将应用中的其他组件连接在一起。现在,我们按照承诺快速看一下这些集成流组件。

消息通道

消息通道是消息穿行集成通道的一种方式(如图10.2所示)。它们是连接Spring Integration其他组成部分的管道。

image 2024 03 14 00 05 39 982
Figure 1. 图10.2 消息通道是集成流中数据在其他组件之间流动的管道

Spring Integration提供了多种通道实现。

  • PublishSubscribeChannel:发送到PublishSubscribeChannel的消息会传递到一个或多个消费者中。如果有多个消费者,则它们都会接收到消息。

  • QueueChannel:发送到QueueChannel的消息会存储到一个队列中,按照FIFO的方式被拉取[插图]。如果有多个消费者,只有其中的一个消费者会接收到消息。

  • PriorityChannel:与QueueChannel类似,但它不是FIFO的方式,而是会基于消息的priority头信息被消费者拉取。

  • RendezvousChannel:与QueueChannel类似,但是发送者会一直阻塞通道,直到消费者接收到消息。它实际上会同步发送者和消费者。

  • DirectChannel:与PublishSubscribeChannel类似,但是消息只会发送至一个消费者。它会在与发送者相同的线程中调用消费者。这种方式允许跨通道的事务。

  • ExecutorChannel:与DirectChannel类似,但消息分发是通过TaskExecutor实现的,这样会在与发送者独立的线程中执行。这种通道类型不支持跨通道的事务。

  • FluxMessageChannel:反应式流的发布者消息通道,基于Reactor项目的Flux。(我们会在第11章讨论反应式流、Reactor和Flux。)

在Java配置和Java DSL中,输入通道都是自动创建的,默认使用DirectChannel。但是,如果想要使用不同的通道实现,就需要将通道声明为bean并在集成流中引用它。例如,要声明PublishSubscribeChannel,需要声明如下的@Bean方法:

@Bean
public MessageChannel orderChannel() {
  return new PublishSubscribeChannel();
}

随后,可以在集成流定义中根据通道名称引用它。例如,如果这个通道要被一个服务激活器bean所消费,我们可以在@ServiceActivator注解的inputChannel属性中引用它:

@ServiceActivator(inputChannel = "orderChannel")

或者,使用Java DSL配置风格,可以调用channel()来引用它:

@Bean
public IntegrationFlow orderFlow() {
  return IntegrationFlows
      ...
      .channel("orderChannel")
      ...
      .get();
}

很重要的一点是,如果使用QueueChannel,消费者必须配置一个poller。例如,假设我们声明了一个这样的QueueChannel bean:

@Bean
public MessageChannel orderChannel() {
  return new QueueChannel();
}

那么,我们需要确保消费者配置成轮询该通道的消息。如果是消息激活器,@ServiceActivator注解可能会如下所示:

@ServiceActivator(inputChannel = "orderChannel",
                  poller = @Poller(fixedRate = "1000"))

在本例中,服务激活器每秒(或者说每1000毫秒)都会轮询名为orderChannel的通道。

过滤器

过滤器放置于集成管道的中间,它能够根据断言允许或拒绝消息进入流程的下一步(如图10.3所示)。

image 2024 03 14 09 20 21 486
Figure 2. 图10.3 过滤器会基于某个断言,允许或拒绝消息在管道中进行处理

例如,假设消息包含了整型的值,要通过名为numberChannel进行发布,但是我们只想让偶数进入名为evenNumberChannel的通道。在这种情况下,可以使用@Filter注解定义一个过滤器:

@Filter(inputChannel = "numberChannel",
        outputChannel = "evenNumberChannel")
public boolean evenNumberFilter(Integer number) {
  return number % 2 == 0;
}

作为替代方案,如果使用Java DSL配置风格来定义集成流,可以按照如下的方式来调用filter():

@Bean
public IntegrationFlow evenNumberFlow(AtomicInteger integerSource) {
  return IntegrationFlows
      ...
      .<Integer>filter((p) -> p % 2 == 0)
      ...
      .get();
}

在本例中,我们使用lambda表达式来实现过滤器。但实际上,filter()方法会接受GenericSelector作为参数。这意味着,如果我们的过滤器过于复杂,不适合放到一个简单的lambda表达式中,那么我们可以实现GenericSelector接口作为替代方案。

转换器

转换器会对消息执行一些操作,一般会导致不同的消息形成,还有可能会产生不同的载荷类型(如图10.4所示)。转换过程可以非常简单,比如执行数字的数学运算或者操作String值。转换过程也可以比较复杂,比如根据代表ISBN的String值查询并返回对应图书的详细信息。

image 2024 03 14 09 21 50 189
Figure 3. 图10.4 转换器会改变流经集成流的消息

例如,假设整型值会通过名为numberChannel的通道进行发布,我们希望将这些数字转换成它们的罗马数字形式,以String类型来表示。在这种情况下,可以声明一个GenericTransformer类型的bean并为其添加@Transformer注解:

@Bean
@Transformer(inputChannel = "numberChannel",
             outputChannel = "romanNumberChannel")
public GenericTransformer<Integer, String> romanNumTransformer() {
  return RomanNumbers::toRoman;
}

@Transformer注解可以将这个bean声明为转换器bean,它会从名为numberChannel的通道接收Integer值,然后使用静态方法toRoman()进行转换(toRoman()是静态方法,定义在名为RomanNumbers的类中,这里使用方法引用来使用它)。转换后的结果会发布到名为romanNumberChannel的通道中。

在Java DSL配置风格中,调用transform()会更加简单,我们只需将对toRoman()的方法引用传递进来:

@Bean
public IntegrationFlow transformerFlow() {
  return IntegrationFlows
      ...
      .transform(RomanNumbers::toRoman)
      ...
      .get();
}

尽管这两个转换器代码中都使用了方法引用,但是转换器也可以使用lambda表达式声明。或者,如果转换器足够复杂,需要使用一个单独的类,那么可以将其作为一个bean注入流定义,并将引用传递给transform()方法:

@Bean
public RomanNumberTransformer romanNumberTransformer() {
  return new RomanNumberTransformer();
}

@Bean
public IntegrationFlow transformerFlow(
                    RomanNumberTransformer romanNumberTransformer) {
  return IntegrationFlows
      ...
      .transform(romanNumberTransformer)
      ...
      .get();
}

在这里,我们声明了RomanNumberTransformer类型的bean,它本身是Spring Integration Transformer或GenericTransformer接口的实现。这个bean注入了transformerFlow()方法,并且在定义集成流的时候传递给了transform()方法。

路由器

路由器能够基于某个路由断言,实现集成流的分支,从而将消息发送至不同的通道上,如图10.5所示。

image 2024 03 14 09 23 44 818
Figure 4. 图10.5 路由器会根据应用于消息的断言,将消息定向至不同的通道

例如,假设我们有一个名为numberChannel的通道,它会传输整型值。我们想要将带有偶数的消息定向到名为evenChannel的通道,将带有奇数的消息定向到名为oddChannel的通道。要在集成流中创建这样一个路由器,我们可以声明一个AbstractMessageRouter类型的bean,并为其添加@Router注解:

@Bean
@Router(inputChannel = "numberChannel")
public AbstractMessageRouter evenOddRouter() {
  return new AbstractMessageRouter() {
    @Override
    protected Collection<MessageChannel>
              determineTargetChannels(Message<?> message) {
      Integer number = (Integer) message.getPayload();
      if (number % 2 == 0) {
        return Collections.singleton(evenChannel());
      }
      return Collections.singleton(oddChannel());
    }
  };
}

@Bean
public MessageChannel evenChannel() {
  return new DirectChannel();
}

@Bean
public MessageChannel oddChannel() {
  return new DirectChannel();
}

这里定义的AbstractMessageRouter接收名为numberChannel的输入通道的消息。它的实现以匿名内部类的形式检查消息的载荷,如果是偶数,返回名为evenChannel的通道(在路由器bean之后同样以bean的方式进行了声明)。否则,通道载荷中的数字必然是奇数,在这种情况下,返回名为oddChannel的通道(同样以bean方法的形式进行了声明)。

在Java DSL风格中,路由器是通过在流定义中调用route()方法来声明的,如下所示:

@Bean
public IntegrationFlow numberRoutingFlow(AtomicInteger source) {
  return IntegrationFlows
    ...
      .<Integer, String>route(n -> n%2 = =0 ? "EVEN":"ODD", mapping -> mapping
        .subFlowMapping("EVEN", sf -> sf
             .<Integer, Integer>transform(n -> n * 10)
             .handle((i,h) -> { ... })
             )
         .subFlowMapping("ODD", sf -> sf
             .transform(RomanNumbers::toRoman)
             .handle((i,h) -> { ... })
             )
         )
       .get();
}

尽管我们依然可以定义AbstractMessageRouter并将其传递到route(),但是在这个样例中使用了lambda表达式来确定消息载荷是偶数还是奇数:对于偶数,返回EVEN;对于奇数,返回ODD。然后这些值会用来确定该使用哪个子映射处理消息。

切分器

在集成流中,有时候将一个消息切分为多个消息独立处理可能会非常有用。切分器将会负责切分并处理这些消息,如图10.6所示。

image 2024 03 14 09 29 13 569
Figure 5. 图10.6 切分器会将消息拆分为两个或更多独立的消息,它们可以由独立的子流分别进行处理

在很多场景中,切分器都非常有用,尤其是以下两种特殊的场景。

  • 消息载荷中包含了相同类型条目的一个列表。我们希望将它们作为单独的消息载荷来进行处理。例如,消息中携带了一个商品列表,可以切分为多个消息,每个消息的载荷分别对应一件商品。

  • 消息载荷所携带的信息尽管有所关联,但是可以拆分为两个或更多个不同类型的消息。例如,一个购买订单可能会包含投递信息、账单、商品项的信息。可以将投递细节交由某个子流来处理,账单交由另一个子流来处理,而商品项再交由其他的子流来处理。在这种情况下,切分器后面通常会紧跟一个路由器,根据消息的载荷类型进行路由,确保数据都由正确的子流处理。

在我们将消息载荷切分为两个或更多个不同类型的消息时,通常定义一个POJO就足够了。它提取传入消息不同的组成部分,并将其以元素集合的形式返回。

例如,假设我们想要将带有购买订单的消息切分为两个消息,其中一个会携带账单信息,另一个携带商品项的信息。如下的OrderSplitter就可以完成该任务:

public class OrderSplitter {
  public Collection<Object> splitOrderIntoParts(PurchaseOrder po) {
    ArrayList<Object> parts = new ArrayList<>();
    parts.add(po.getBillingInfo());
    parts.add(po.getLineItems());
    return parts;
  }
}

接下来,我们声明一个OrderSplitter bean,并通过@Splitter注解将其作为集成流的一部分:

@Bean
@Splitter(inputChannel = "poChannel",
          outputChannel = "splitOrderChannel")
public OrderSplitter orderSplitter() {
  return new OrderSplitter();
}

在这里,购买订单会到达名为poChannel的通道,它们会被OrderSplitter切分。然后,所返回集合中的每个条目都会作为集成流中独立的消息发布到名为splitOrderChannel的通道中。此时,我们可以在流中声明一个PayloadTypeRouter,将账单信息和商品项分别路由至它们自己的子流:

@Bean
@Router(inputChannel = "splitOrderChannel")
public MessageRouter splitOrderRouter() {
  PayloadTypeRouter router = new PayloadTypeRouter();
  router.setChannelMapping(
      BillingInfo.class.getName(), "billingInfoChannel");
  router.setChannelMapping(
      List.class.getName(), "lineItemsChannel");
  return router;
}

顾名思义,PayloadTypeRouter会根据消息的载荷将它们路由至不同的通道。按照这里的配置,载荷为BillingInfo类型的消息将会被路由至名为billingInfoChannel的通道,供后续进行处理。至于商品项,它们会放到一个java.util.List集合中,因此,我们将List类型的载荷映射到名为lineItemsChannel的通道中。

按照目前的状况,流将会被切分成两个子流,一个是BillingInfo对象的流,另一个则是List<LineItem>的流。假设我们想要进一步进行拆分,例如不想处理LineItems的列表,而是想要分别处理每个LineItem,又该怎么办呢?要将商品列表拆分为多个消息,其中每个消息包含一个条目,只需要编写一个方法(而不是一个bean),这个方法带有@Splitter注解并且返回LineItem的集合,如下所示:

@Splitter(inputChannel="lineItemsChannel", outputChannel="lineItemChannel")
public List<LineItem> lineItemSplitter(List<LineItem> lineItems) {
  return lineItems;
}

当带有List<LineItem>载荷的消息抵达名为lineItemsChannel通道时,消息会进入lineItemSplitter()。按照切分器的规则,这个方法必须要返回切分后条目的集合。在本例中,我们已经有了LineItem的集合,所以我们直接返回这个集合就可以了。这样做的结果是,集合中的每个LineItem都会发布到一个消息中,这些消息会被发送到名为lineItemChannel的通道中。

如果想要使用Java DSL声明相同的splitter/router配置,则可以通过调用split()和route()来实现:

return IntegrationFlows
  ...
    .split(orderSplitter())
    .<Object, String> route(
        p -> {
          if (p.getClass().isAssignableFrom(BillingInfo.class)) {
            return "BILLING_INFO";
          } else {
            return "LINE_ITEMS";
          }
        }, mapping -> mapping
          .subFlowMapping("BILLING_INFO", sf -> sf
               .<BillingInfo> handle((billingInfo, h) -> {
                 ...
               }))
           .subFlowMapping("LINE_ITEMS", sf -> sf
               .split()
               .<LineItem> handle((lineItem, h) -> {
                 ...
               }))

        )
    .get();

DSL所组成的流定义相当简洁,但是可能有点难以理解。它使用与Java配置样例相同的OrderSplitter来切分订单。我们可以将lambda表达式抽取到方法中,使其更为整洁,例如使用如下所示的3个方法来取代流定义中的lambda表达式:

private String route(Object p) {
  return p.getClass().isAssignableFrom(BillingInfo.class)
      ? "BILLING_INFO"
      : "LINE_ITEMS";
}

private BillingInfo handleBillingInfo(
        BillingInfo billingInfo, MessageHeaders h) {
  // ...
}

private LineItem handleLineItems(
        LineItem lineItem, MessageHeaders h) {
  // ...
}

然后,使用方法引用重写集成流:

return IntegrationFlows
  ...
    .split()
    .route(
      this::route,
      mapping -> mapping
        .subFlowMapping("BILLING_INFO", sf -> sf
          .<BillingInfo> handle(this::handleBillingInfo))
        .subFlowMapping("LINE_ITEMS", sf -> sf
          .split()
          .<LineItem> handle(this::handleLineItems)));

不管采用哪种方式,都会像Java配置样例那样,使用相同的OrderSplitter的切分订单。在订单切分之后,根据类型路由至两个独立的子流。

服务激活器

服务激活器接收来自输入通道的消息并将这些消息发送至一个MessageHandler的实现,如图10.7所示。

image 2024 03 14 09 34 18 940
Figure 6. 图10.7 在接收到消息时,服务激活器会通过MessageHandler调用某个服务

Spring Integration提供了多个“开箱即用”的MessageHandler(PayloadTypeRouter甚至就是MessageHandler的一个实现),但是我们通常会需要为其提供一些自定义的实现作为服务激活器。作为样例,如下的代码展现了如何声明MessageHandler bean并将其配置为服务激活器:

@Bean
@ServiceActivator(inputChannel = "someChannel")
public MessageHandler sysoutHandler() {
  return message -> {
    System.out.println("Message payload: " + message.getPayload());
  };
}

这个bean使用了@ServiceActivator注解,表明它会作为一个服务激活器处理来自someChannel通道的消息。对于MessageHandler本身,它是通过一个lambda表达式实现的。这是一个简单的MessageHandler,当得到消息之后,它会将消息的载荷打印至标准输出流。

我们还可以声明一个服务激活器,让它在返回新载荷之前处理输入消息中的数据。在这种情况下,bean应该是GenericHandler,而不是MessageHandler:

@Bean
@ServiceActivator(inputChannel = "orderChannel",
                  outputChannel = "completeChannel")
public GenericHandler<EmailOrder> orderHandler(
                             OrderRepository orderRepo) {
  return (payload, headers) -> {
    return orderRepo.save(payload);
  };
}

在本例中,服务激活器是一个GenericHandler,它会接收载荷类型为EmailOrder的消息。订单抵达时,我们会通过一个存储库将它保存起来,并返回保存之后的EmailOrder,这个EmailOrder随后被发送至名为completeChannel的输出通道。

你可能已经注意到了,GenericHandler不仅能够得到载荷,还能得到消息头(虽然我们这个样例根本没有用到这些头信息)。我们还可以在Java DSL配置风格中使用服务激活器,只需将MessageHandler或GenericHandler传递到流定义的handle()方法中:

public IntegrationFlow someFlow() {
  return IntegrationFlows
    ...
      .handle(msg -> {
        System.out.println("Message payload: " + msg.getPayload());
       })
      .get();
}

在本例中,MessageHandler会得到一个lambda表达式,但是我们也可以为其提供一个方法引用,甚至实现MessageHandler接口的类实例。如果想要为其提供lambda表达式或方法引用,需要记住它们均接受消息作为其参数。

类似地,如果不想将服务激活器作为流的终点,handle()还可以接受GenericHandler。如果要将前面提到的订单保存服务激活器添加进来,可以按照如下的形式使用Java DSL配置流:

public IntegrationFlow orderFlow(OrderRepository orderRepo) {
  return IntegrationFlows
    ...
       .<EmailOrder>handle((payload, headers) -> {
           return orderRepo.save(payload);
       })
    ...
      .get();
}

使用GenericHandler时,lambda表达式或方法引用会接受消息载荷和头信息作为参数。如果选择使用GenericHandler作为流的终点,就需要其返回null,否则就会出现错误,提示没有指定输出通道。

网关

通过网关,应用可以提交数据到集成流中,并且能够可选地接收流的结果作为响应。网关会声明为接口,借助Spring Integration的实现,应用可以调用它来向集成流发送消息(如图10.8所示)。

image 2024 03 14 09 38 17 047
Figure 7. 图10.8 服务网关是接口,应用可以借助它向集成流提交消息

我们已经看过消息网关的样例,也就是FileWriterGateway。FileWriterGateway是一个单向的网关,有一个接受String类型的方法,该方法会将文本写入到文件中,并返回void。编写双向的网关同样简单。在编写网关接口时,需要确保方法要返回某个值以便推送到集成流中。

作为样例,假设网关面对的是一个简单的集成流,这个流会接受一个String并将给定的String转换成全大写的形式。这个网关接口大致如下所示:

package sia6;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.stereotype.Component;

@Component
@MessagingGateway(defaultRequestChannel = "inChannel",
                  defaultReplyChannel = "outChannel")
public interface UpperCaseGateway {
  String uppercase(String in);
}

让人开心的是,这个接口不需要实现。Spring Integration会在运行时自动提供一个通过特定通道发送和接收消息的实现。

当uppercase()被调用时,给定的String会发布到集成流中,进入名为inChannel的通道。不管流是如何定义的、干了些什么,当数据进入名为outChannel通道时,都会从uppercase()方法返回。

我们这个用以转换大写格式的集成流是一个非常简单的流,只需要一个将String转换成大写格式的步骤。它可以通过Java DSL配置声明如下:

@Bean
public IntegrationFlow uppercaseFlow() {
  return IntegrationFlows
    .from("inChannel")
    .<String, String> transform(s -> s.toUpperCase())
    .channel("outChannel")
    .get();
}

按照这里的定义,这个流随着进入inChannel通道的数据开始。消息载荷会由转换器处理,执行大写操作(在这里是通过lambda表达式定义的)。形成的结果消息会发送到名为outChannel的通道,也就是我们在UpperCaseGateway中声明的答复通道。

通道适配器

通道适配器代表了集成流的入口和出口。数据通过入站通道适配器(inbound channel adapter)进入一个集成流,通过出站通道适配器离开一个集成流。如图10.9所示。

image 2024 03 14 09 40 56 803
Figure 8. 图10.9 通道适配器是集成流的入口和出口

根据要引入集成流的数据源,入站通道适配器可以有很多形式。例如,我们可以声明一个入站通道适配器,将来自AtomicInteger的、不断递增的数字引入流。使用Java配置,则如下所示:

@Bean
@InboundChannelAdapter(
    poller = @Poller(fixedRate = "1000"), channel = "numberChannel")
public MessageSource<Integer> numberSource(AtomicInteger source) {
  return () -> {
    return new GenericMessage<>(source.getAndIncrement());
  };
}

这个@Bean方法通过@InboundChannelAdapter注解声明了一个入站通道适配器,它根据注入的AtomicInteger每隔一秒(也就是1000毫秒)提交一个数字给名为numberChannel的通道。

使用Java配置时,我们可以通过@InboundChannelAdapter注解声明入站通道适配器,而使用Java DSL定义集成流时,我们需要使用from()方法完成同样的事情。如下的流定义展现了类似的入站通道适配器,它是使用Java DSL定义的:

@Bean
public IntegrationFlow someFlow(AtomicInteger integerSource) {
  return IntegrationFlows
      .from(integerSource, "getAndIncrement",
          c -> c.poller(Pollers.fixedRate(1000)))
    ...
      .get();
}

通常,通道适配器是由Spring Integration的众多端点模块提供的。假设我们需要一个入站通道适配器监控一个特定的目录,并将写入该目录的文件以消息的形式提交到file-channel通道中。如下的Java配置使用来自Spring Integration file端点模块的FileReadingMessageSource实现该功能:

@Bean
@InboundChannelAdapter(channel="file-channel",
                          poller = @Poller(fixedDelay="1000"))
public MessageSource<File> fileReadingMessageSource() {
  FileReadingMessageSource sourceReader = new FileReadingMessageSource();
  sourceReader.setDirectory(new File(INPUT_DIR));
  sourceReader.setFilter(new SimplePatternFileListFilter(FILE_PATTERN));
  return sourceReader;
}

如果要使用Java DSL编写同等功能的入站通道适配器,可以使用Files类的inboundAdapter()方法。出站通道适配器是集成流的终点,会将最终的消息传递给应用或其他外部系统:

@Bean
public IntegrationFlow fileReaderFlow() {
  return IntegrationFlows
      .from(Files.inboundAdapter(new File(INPUT_DIR))
          .patternFilter(FILE_PATTERN))
      .get();
}

我们通常会将消息激活器实现为消息处理器,让它作为出站通道适配器,对数据需要传递给应用本身的情况更是如此。我们已经讨论过消息激活器,这里就没有必要重复讨论了。

但是,需要注意,Spring Integration端点模块为多个通用场景提供了消息处理器。在程序清单10.3中,我们已经见过这种出站通道适配器的样例FileWritingMessageHandler。提到Spring Integration端点模块,不妨看一下都有哪些直接可用的集成端点模块。

端点模块

Spring Integration允许我们创建自己的通道适配器,这一点非常好,但更棒的是Spring Integration提供了20余个包含通道适配器(同时包括入站和出站的适配器)的端点模块,用于和各种常见的外部系统实现集成,我们将其列到了表10.1中。

Chapter10 table 1
Figure 9. 表10.1  Spring Integration提供的端点模块

从表10.1中可以清楚地看到,Spring Integration提供了用途广泛的一组组件,能够满足非常多的集成需求。虽然大多数应用程序使用的功能只是Spring Integration所提供功能的九牛一毛,但我们最好知道Spring Integration能够提供哪些功能。

另外,我不可能在一章的篇幅中介绍表10.1中的所有的通道适配器。我们已经看到了如何使用文件系统模块写入文件的样例。我们随后将会看到如何使用email模块来读取电子邮件。

对于每个端点模块的通道适配器,我们可以在Java配置中将其声明为bean,也可以在Java DSL配置中以静态方法的方式引用它们。我建议你探索一下自己最感兴趣的其他端点模块。你会发现它们在使用方式上是非常一致的。但是,现在,我们关注一下email端点模块,看一下如何将它用到Taco Cloud应用中。