Spring 集成示例

Spring 集成示例

从 Spring Integration 2.0 开始,Spring Integration 发行版不再包含样本。 相反,我们已经切换到了一个更简单的协作模式,它应该会促进更好的社区参与,理想情况下,会促进更多的贡献。 示例现在具有专用的 GitHub 存储库。 示例开发也有自己的生命周期,该生命周期不依赖于框架版本的生命周期,但出于兼容性原因,存储库仍会标记每个主要版本。spring-doc.cn

对社区的巨大好处是,我们现在可以添加更多示例并立即提供给您,而无需等待下一个版本。 拥有自己的 GitHub 存储库,而不与实际框架绑定,这也是一个很大的好处。 现在,您有一个专用位置来建议样本以及报告现有样本的问题。 您也可以将样本作为 Pull Request 提交给我们。 如果我们认为您的样本增加了价值,我们将非常高兴将其添加到“样本”存储库中,并适当地注明您是作者。spring-doc.cn

从何处获取样本

Spring 集成示例项目托管在 GitHub 上。 要签出或克隆示例,必须在系统上安装 Git 客户端。 有几种基于 GUI 的产品可用于许多平台(例如用于 Eclipse IDE 的 EGit)。 一个简单的 Google 搜索可以帮助您找到它们。 您还可以使用 Git 的命令行界面。spring-doc.cn

如果您需要有关如何安装或使用 Git 的更多信息,请访问:https://git-scm.com/

要使用 Git 命令行工具克隆(签出)Spring 集成示例存储库,请发出以下命令:spring-doc.cn

$ git clone https://github.com/spring-projects/spring-integration-samples.git

上述命令将整个示例存储库克隆到您发出该命令的工作目录中命名的目录中。 由于 samples 存储库是实时存储库,因此您可能希望执行定期拉取 (更新) 以获取新示例和对现有示例的更新。 为此,请发出以下命令:spring-integration-samplesgitgit pullspring-doc.cn

$ git pull

提交 Samples或 Samples申请

您可以提交新样本和样本请求。 我们非常感谢为改进样本所做的任何努力,包括分享好的想法。spring-doc.cn

如何提供自己的样本?

GitHub 用于社交编码:如果您想将自己的代码示例提交到 Spring 集成示例项目,我们鼓励通过此存储库的分支拉取请求做出贡献。 如果您想以这种方式贡献代码,请尽可能参考 GutHub 问题,该问题提供了有关您的示例的一些详细信息。spring-doc.cn

签署贡献者许可协议

非常重要:在我们接受您的 Spring 集成示例之前,我们需要您签署 SpringSource 贡献者许可协议 (CLA)。 签署贡献者协议并不授予任何人对主仓库的提交权限,但这确实意味着我们可以接受您的贡献,如果我们接受,您将获得作者信用。 要阅读并签署 CLA,请转到:spring-doc.cn

Project 下拉列表中,选择 Spring Integration。 项目负责人是 Artem Bilan。spring-doc.cn

代码贡献流程

有关实际的代码贡献过程,请阅读 Spring 集成的贡献者指南。 他们也申请了样本项目。 您可以在 https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md 找到它们spring-doc.cn

此过程可确保每个提交都经过同行评审。 事实上,核心 Committer 遵循完全相同的规则。 我们非常期待您的 Spring 集成示例!spring-doc.cn

示例请求

如前所述,Spring Integration Samples 项目使用 GitHub issue 作为 bug 跟踪系统。 要提交新的样本请求,请访问 https://github.com/spring-projects/spring-integration-samples/issuesspring-doc.cn

Samples结构

从 Spring Integration 2.0 开始,示例的结构发生了变化。 随着更多 Samples的推出,我们意识到并非所有 Samples都有相同的目标。 它们都有一个共同的目标,即向您展示如何应用和使用 Spring Integration 框架。 但是,它们的不同之处在于,一些示例侧重于技术使用案例,而另一些示例则侧重于业务使用案例。 此外,一些示例还展示了可用于解决某些场景(技术和业务)的各种技术。 新的 Samples分类使我们能够更好地根据每个 Samples解决的问题对 Samples进行组织,同时为您提供一种更简单的方法来查找适合您需求的 Samples。spring-doc.cn

目前,有四个类别。 在示例存储库中,每个类别都有自己的目录,该目录以类别名称命名:spring-doc.cn

基本 (samples/basic)

这是一个很好的起点。 此处的示例是技术驱动的,并演示了配置和代码的最低限度。 这些应该通过向您介绍 Spring 集成的基本概念、API 和配置以及企业集成模式(EIP)来帮助您快速入门。 例如,如果您正在寻找有关如何实现服务激活器并将其连接到消息通道、如何使用消息传递网关作为消息交换的门面或如何开始使用 MAIL、TCP/UDP 或其他模块的答案,那么这里就是找到好样本的正确位置。 最重要的是,这是一个开始的好地方。samples/basicspring-doc.cn

中级 (samples/intermediate)

此类别针对已经熟悉 Spring 集成框架(除了入门之外)但在解决切换到消息传递体系结构后可能遇到的更高级技术问题时需要更多指导的开发人员。 例如,如果你正在寻找有关如何处理各种消息交换场景中的错误,或者如何正确配置聚合器以应对某些消息从未到达进行聚合的情况,或者超出特定组件的基本实现和配置并暴露“其他什么”类型问题的任何其他问题的答案, 这是查找此类样本的正确位置。spring-doc.cn

高级 (samples/advanced)

此类别面向非常熟悉 Spring Integration 框架,但希望通过使用 Spring Integration 的公共 API 对其进行扩展以满足特定自定义需求的开发人员。 例如,如果你正在寻找演示如何实现自定义通道或消费者(基于事件或基于轮询)的示例,或者你正在尝试找出在 Spring 集成 bean 解析器层次结构之上实现自定义 bean 解析器的最合适方法(可能在为自定义组件实现你自己的名称空间和模式时), 这是正确的查找位置。 在这里,您还可以找到有助于您进行适配器开发的示例。 Spring 集成带有一个广泛的适配器库,允许您将远程系统与 Spring 集成消息传递框架连接起来。 但是,您可能需要与核心框架未为其提供适配器的系统集成。 如果是这样,你可能会决定实现你自己的(请考虑贡献它)。 此类别将包括向您展示如何操作的示例。spring-doc.cn

应用 (samples/applications)

此类别面向对消息驱动架构和 EIP 有很好的理解,并且对 Spring 和 Spring Integration 有高于平均水平的理解,他们正在寻找解决特定业务问题的示例的开发人员和架构师。 换句话说,此类别中示例的重点是业务用例,以及如何使用消息驱动的体系结构,特别是 Spring Integration 来解决它们。 例如,如果您想了解如何使用 Spring Integration 实现和自动化贷款经纪人或旅行社流程,那么这是查找这些类型示例的正确位置。spring-doc.cn

Spring Integration 是一个社区驱动的框架。 因此,社区参与很重要。 这包括样本。 如果您找不到所需的内容,请告诉我们!

Samples

目前, Spring Integration 附带了相当多的示例,您只能期待更多。 为了帮助您更好地浏览它们,每个示例都附带了自己的文件,其中包含有关示例的多个详细信息(例如,它解决了哪些 EIP 模式、它试图解决什么问题、如何运行示例以及其他详细信息)。 但是,某些示例需要更详细的解释,有时需要图形解释。 在本节中,您可以找到我们认为需要特别注意的样本的详细信息。readme.txtspring-doc.cn

贷款经纪人

本节介绍了 Spring 集成示例中包含的贷款经纪人示例应用程序。 此示例的灵感来自 Gregor Hohpe 和 Bobby Woolf 的 Enterprise Integration Patterns 一书中的一个示例。spring-doc.cn

下图显示了整个过程:spring-doc.cn

贷款经纪人 EIP
图 1.贷款经纪人样本

EIP 架构的核心是非常简单但功能强大的管道、过滤器,当然还有消息的概念。 端点 (过滤器) 通过通道 (管道) 相互连接。 生产终端节点将消息发送到通道,而使用终端节点检索消息。 此体系结构旨在定义各种机制,这些机制描述如何在终结点之间交换信息,而无需了解这些终结点是什么或它们正在交换什么信息。 因此,它提供了一个非常松散耦合和灵活的协作模型,同时还将集成关注点与业务关注点解耦。 EIP 通过进一步定义来扩展此架构:spring-doc.cn

  • 管道的类型 (点对点通道、发布-订阅通道、通道适配器等)spring-doc.cn

  • 有关过滤器如何与管道协作的核心过滤器和模式(消息路由器、拆分器和聚合器、各种消息转换模式等)spring-doc.cn

EIP 书的第 9 章很好地描述了此用例的细节和变化,但以下是简要总结:在购买最佳贷款报价时,消费者订阅了贷款经纪人的服务,该服务处理以下详细信息:spring-doc.cn

  • 消费者预先筛选(例如,获取和查看消费者的信用记录)spring-doc.cn

  • 确定最合适的银行(例如,根据消费者的信用记录或评分)spring-doc.cn

  • 向每个选定的银行发送贷款报价请求spring-doc.cn

  • 从每个 SoundBank 收集响应spring-doc.cn

  • 根据消费者的要求筛选响应并确定最佳报价。spring-doc.cn

  • 将 Loan 报价传递回给消费者。spring-doc.cn

获得贷款报价的真实过程通常要复杂一些。 然而,由于我们的目标是演示如何在 Spring Integration 中实现和实现企业集成模式,因此用例已被简化为仅集中在该过程的集成方面。 它并不是试图为您提供消费财务方面的建议。spring-doc.cn

通过聘请贷款经纪人,消费者与贷款经纪人的运营细节无关,并且每个贷款经纪人的运营可能会相互推迟以保持竞争优势,因此无论我们组装和实施什么,都必须是灵活的,以便可以快速轻松地引入任何变化。spring-doc.cn

贷款经纪人样本实际上并没有与任何“虚构的”银行或征信机构交谈。 这些服务被淘汰了。

我们的目标是将整个流程的集成方面作为一个整体进行组装、编排和测试。 只有这样,我们才能开始考虑将这些流程连接到真正的服务。 此时,无论特定贷款经纪人与之交易的银行数量或使用何种通信媒体(或协议)(JMS、WS、TCP 等)与这些银行通信,组装流程及其配置都不会更改。spring-doc.cn

设计

在分析前面列出的 6 个需求时,您可以看到它们都是集成问题。 例如,在消费者预筛选步骤中,我们需要收集有关消费者和消费者愿望的其他信息,并使用额外的元信息来丰富贷款请求。 然后,我们必须过滤此类信息以选择最合适的 SoundBank 列表,依此类推。 Enrich、Filter 和 select 都是 EIP 以模式形式定义解决方案的集成问题。 Spring 集成提供了这些模式的实现。spring-doc.cn

下图显示了消息网关的表示形式:spring-doc.cn

网关
图 2.消息网关

消息网关模式提供了一种访问消息系统(包括我们的贷款经纪人)的简单机制。 在 Spring 集成中,你可以将网关定义为一个普通的旧 java 接口(你不需要提供实现),使用 XML 元素或 Java 中的 Comments 对其进行配置,并像使用任何其他 Spring bean 一样使用它。 Spring 集成通过生成消息(有效负载映射到方法的输入参数)并将其发送到指定的通道,负责将方法调用委托和映射到消息传递基础设施。 以下示例演示如何使用 XML 定义此类网关:<gateway>spring-doc.cn

<int:gateway id="loanBrokerGateway"
  default-request-channel="loanBrokerPreProcessingChannel"
  service-interface="org.springframework.integration.samples.loanbroker.LoanBrokerGateway">
  <int:method name="getBestLoanQuote">
    <int:header name="RESPONSE_TYPE" value="BEST"/>
  </int:method>
</int:gateway>

我们当前的 gateway 提供了两个可以调用的方法。 一个返回最佳单引号,另一个返回所有引号。 不知何故,在下游,我们需要知道调用方需要什么类型的回复。 在消息传递体系结构中实现此目的的最佳方法是使用一些描述您的意图的元数据来丰富消息的内容。 内容扩充器是解决此问题的模式之一。 为了方便起见, Spring 集成确实提供了一个单独的配置元素,以使用任意数据来丰富消息头(稍后描述) 但是,由于该元素负责构造初始消息,因此它包括使用任意消息标头丰富新创建的消息的功能。 在我们的示例中,我们添加了一个 header,其值为 whenever 调用该方法。 对于其他方法,我们不添加任何 header。 现在我们可以在下游检查是否存在此标头。 根据它的存在和值,我们可以确定调用方想要什么类型的回复。gatewayRESPONSE_TYPEBESTgetBestQuote()spring-doc.cn

根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些顶级银行只接受满足最低信用评分要求的消费者的报价请求。 因此,如果消息在转发到银行之前能用这些信息来丰富,那就太好了。 如果需要完成多个流程以提供此类元信息,则可以将这些流程分组到一个单元中,那也很好。 在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则,选择要向其发送报价请求的消息渠道(银行渠道)列表。spring-doc.cn

组合消息处理器

组合消息处理器模式描述了有关构建终端节点的规则,这些终端节点保持对消息流(由多个消息处理器组成)的控制。 在 Spring Integration 中,组合消息处理器模式由元素实现。<chain>spring-doc.cn

下图显示了链模式:spring-doc.cn

链
图 3.链

上图显示,我们有一个链,其中包含一个内部 header-enricher 元素,该元素使用 header 和值(由对 credit service 的调用确定——一个由 'creditBureau' 名称标识的简单 POJO spring bean)进一步丰富了消息的内容。 然后,它将委托给消息路由器。CREDIT_SCOREspring-doc.cn

下图显示了消息路由器模式:spring-doc.cn

银行路由器
图 4.消息路由器

Spring 集成提供了消息路由模式的多种实现。 在这种情况下,我们使用一个路由器,它根据评估一个表达式(在 Spring 表达式语言中)来确定通道列表,该表达式查看信用评分(在上一步中确定),并从 bean 中选择通道列表,其值为 或 ,基于信用评分的值。 选择通道列表后,消息将路由到这些通道。Mapidbankspremiersecondaryspring-doc.cn

现在,贷款经纪人需要做的最后一件事是接收来自银行的贷款报价,按消费者汇总它们(我们不想向另一个消费者展示报价),根据消费者的选择标准(单个最佳报价或所有报价)组装响应,并将回复发送给消费者。spring-doc.cn

下图显示了消息聚合器模式:spring-doc.cn

报价聚合器
图 5.消息聚合器

聚合器模式描述将相关消息分组为单个消息的终端节点。 可以提供标准和规则来确定聚合和关联策略。 Spring 集成提供了聚合器模式的多种实现以及方便的基于名称空间的配置。spring-doc.cn

以下示例说明如何定义聚合器:spring-doc.cn

<int:aggregator id="quotesAggregator"
      input-channel="quotesAggregationChannel"
      method="aggregateQuotes">
  <beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>

我们的 Loan Broker 定义了一个带有元素的 'quotesAggregator' bean,它提供了一个默认的聚合和关联策略。 默认关联策略根据标头关联消息(请参阅 EIP 书籍中的关联标识符模式)。 请注意,我们从未提供此标头的值。 它是由 router 在较早时自动设置的,当它为每个 bank 通道生成单独的消息时。<aggregator>correlationIdspring-doc.cn

消息关联后,它们将发布到实际的聚合器实现。 尽管 Spring 集成提供了一个默认的聚合器,但它的策略(从所有消息中收集有效负载列表,并以此列表作为其有效负载构造一条新消息)并不能满足我们的要求。 在消息中包含所有结果是一个问题,因为我们的使用者可能需要一个或所有的最佳报价。 为了传达消费者的意图,我们在流程的早期设置了 header。 现在我们必须评估这个标头并返回所有报价(默认聚合策略有效)或最佳报价(默认聚合策略不起作用,因为我们必须确定哪个贷款报价最好)。RESPONSE_TYPEspring-doc.cn

在更实际的应用程序中,选择最佳报价可能基于可能影响聚合器实施和配置复杂性的复杂标准。 不过,现在,我们正在简化它。 如果消费者想要最好的报价,我们会选择利率最低的报价。 为此,该类按利率对所有报价进行排序,并返回第一个报价。 该类实现以根据 rate 属性比较报价。 创建响应消息后,该消息将发送到启动该进程的消息网关的默认回复通道(进而发送到使用者)。 我们的消费者得到了贷款报价!LoanQuoteAggregatorLoanQuoteComparablespring-doc.cn

总之,一个相当复杂的过程基于 POJO(即现有的或遗留的)逻辑和一个轻量级的、可嵌入的消息传递框架(Spring Integration)组装而成,该框架具有松散耦合的编程模型,旨在简化异构系统的集成,而无需类似 ESB 的重量级引擎或专有的开发和部署环境。 作为开发人员,您不需要仅仅因为有集成问题就将 Swing 或基于控制台的应用程序移植到类似 ESB 的服务器或实现专有接口。spring-doc.cn

此示例和本节中的其他示例都是在 Enterprise Integration Patterns 的基础上构建的。 您可以将它们视为解决方案的 “构建块”。 它们不是完整的解决方案。 所有类型的应用程序(无论是否基于服务器)都存在集成问题。 我们的目标是使集成应用程序不需要更改设计、测试和部署策略。spring-doc.cn

Cafe 示例

本节介绍了 Spring 集成示例中包含的 café 示例应用程序。 此示例的灵感来自 Gregor Hohpe 的 Ramblings 中的另一个示例。spring-doc.cn

该域是咖啡馆的域,下图描述了基本流程:spring-doc.cn

Café EIP 咖啡厅
图 6.咖啡厅示例

对象可能包含多个 . 下订单后,拆分器会将复合订单消息拆分为每种饮料的单个消息。 然后,这些都由路由器处理,该路由器确定饮料是热的还是冷的(通过检查对象的 'isIced' 属性)。 准备每种饮料,但热饮和冷饮的准备由两种不同的方法处理:“prepareHotDrink”和“prepareColdDrink”。 然后,准备好的饮料被发送到它们聚合到一个对象中的位置。OrderOrderItemsOrderItemBaristaWaiterDeliveryspring-doc.cn

下面的清单显示了 XML 配置:spring-doc.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:beans="http://www.springframework.org/schema/beans"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/integration
  https://www.springframework.org/schema/integration/spring-integration.xsd
  http://www.springframework.org/schema/integration/stream
  https://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd">

    <int:gateway id="cafe" service-interface="o.s.i.samples.cafe.Cafe"/>

    <int:channel  id="orders"/>
    <int:splitter input-channel="orders" ref="orderSplitter"
                  method="split" output-channel="drinks"/>

    <int:channel id="drinks"/>
    <int:router  input-channel="drinks"
                 ref="drinkRouter" method="resolveOrderItemChannel"/>

    <int:channel id="coldDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="coldDrinks" ref="barista"
                           method="prepareColdDrink" output-channel="preparedDrinks"/>

    <int:channel id="hotDrinks"><int:queue capacity="10"/></int:channel>
    <int:service-activator input-channel="hotDrinks" ref="barista"
                           method="prepareHotDrink" output-channel="preparedDrinks"/>

    <int:channel id="preparedDrinks"/>
    <int:aggregator input-channel="preparedDrinks" ref="waiter"
                    method="prepareDelivery" output-channel="deliveries"/>

    <int-stream:stdout-channel-adapter id="deliveries"/>

    <beans:bean id="orderSplitter"
                class="org.springframework.integration.samples.cafe.xml.OrderSplitter"/>

    <beans:bean id="drinkRouter"
                class="org.springframework.integration.samples.cafe.xml.DrinkRouter"/>

    <beans:bean id="barista" class="o.s.i.samples.cafe.xml.Barista"/>
    <beans:bean id="waiter"  class="o.s.i.samples.cafe.xml.Waiter"/>

    <int:poller id="poller" default="true" fixed-rate="1000"/>

</beans:beans>

每个消息终端节点都连接到输入通道和/或输出通道。 每个端点都管理自己的生命周期(默认情况下,端点在初始化时自动启动,为防止这种情况,请添加值为 of 的属性)。 最重要的是,请注意,这些对象是具有强类型方法参数的简单 POJO。 以下示例显示了 Splitter:auto-startupfalsespring-doc.cn

public class OrderSplitter {
    public List<OrderItem> split(Order order) {
        return order.getItems();
    }
}

对于 router,返回值不必是实例(尽管它可以是)。 在此示例中,将返回一个包含通道名称的值,如下面的清单所示。MessageChannelStringspring-doc.cn

public class DrinkRouter {
    public String resolveOrderItemChannel(OrderItem orderItem) {
        return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks";
    }
}

现在,回到 XML,您可以看到有两个元素。 它们中的每一个都委托给同一个实例,但具有不同的方法( 或 ),每个方法对应于已路由 order 项的两个通道之一。 下面的清单显示了 Barista 类(其中包含 and 方法)<service-activator>BaristaprepareHotDrinkprepareColdDrinkprepareHotDrinkprepareColdDrinkspring-doc.cn

public class Barista {

    private long hotDrinkDelay = 5000;
    private long coldDrinkDelay = 1000;

    private AtomicInteger hotDrinkCounter = new AtomicInteger();
    private AtomicInteger coldDrinkCounter = new AtomicInteger();

    public void setHotDrinkDelay(long hotDrinkDelay) {
        this.hotDrinkDelay = hotDrinkDelay;
    }

    public void setColdDrinkDelay(long coldDrinkDelay) {
        this.coldDrinkDelay = coldDrinkDelay;
    }

    public Drink prepareHotDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.hotDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared hot drink #" + hotDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber()
                    + ": " + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }

    public Drink prepareColdDrink(OrderItem orderItem) {
        try {
            Thread.sleep(this.coldDrinkDelay);
            System.out.println(Thread.currentThread().getName()
                    + " prepared cold drink #" + coldDrinkCounter.incrementAndGet()
                    + " for order #" + orderItem.getOrder().getNumber() + ": "
                    + orderItem);
            return new Drink(orderItem.getOrder().getNumber(), orderItem.getDrinkType(),
                    orderItem.isIced(), orderItem.getShots());
        }
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
    }
}

从前面的代码摘录中可以看出,这些方法具有不同的延迟(热饮的准备时间是热饮的 5 倍)。 这模拟以不同的速率完成工作。 当 'main' 方法运行时,它会循环 100 次,每次发送一杯热饮和一杯冷饮。 它实际上通过调用接口上的 'placeOrder' 方法来发送消息。 在前面的 XML 配置中,您可以看到 element 被指定。 这将触发一个代理的创建,该代理实现给定的服务接口并将其连接到通道。 通道名称在接口的 annotation 上提供,如下面的接口定义所示:BaristaCafeDemoCafe<gateway>@GatewayCafespring-doc.cn

public interface Cafe {

    @Gateway(requestChannel="orders")
    void placeOrder(Order order);

}

最后,看一下 本身的方法:main()CafeDemospring-doc.cn

public static void main(String[] args) {
    AbstractApplicationContext context = null;
    if (args.length > 0) {
        context = new FileSystemXmlApplicationContext(args);
    }
    else {
        context = new ClassPathXmlApplicationContext("cafeDemo.xml", CafeDemo.class);
    }
    Cafe cafe = context.getBean("cafe", Cafe.class);
    for (int i = 1; i <= 100; i++) {
        Order order = new Order(i);
        order.addItem(DrinkType.LATTE, 2, false);
        order.addItem(DrinkType.MOCHA, 3, true);
        cafe.placeOrder(order);
    }
}
要运行此示例以及其他 8 个示例,请参阅主发行版目录中的 (如本章开头所述)。README.txtsamples

当你跑步时,你可以看到冷饮最初比热饮准备得更快。 因为有一个聚合器,所以冷饮实际上受到热饮制备速率的限制。 这是意料之中的,基于它们各自的 1000 毫秒和 5000 毫秒的延迟。 但是,通过使用并发任务执行程序配置 Poller,您可以显著更改结果。 例如,您可以为热饮咖啡师使用具有 5 个工作线程的线程池执行程序,同时保持冷饮咖啡师不变。 下面的清单配置了这样的安排:cafeDemospring-doc.cn

<int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks"/>

  <int:service-activator input-channel="hotDrinks"
                     ref="barista"
                     method="prepareHotDrink"
                     output-channel="preparedDrinks">
      <int:poller task-executor="pool" fixed-rate="1000"/>
  </int:service-activator>

  <task:executor id="pool" pool-size="5"/>

另请注意,每次调用都会显示工作线程名称。 您可以看到热饮是由 task-executor 线程准备的。 如果你提供更短的 Poller 间隔(例如 100 毫秒),你可以看到它偶尔会通过强制任务计划程序(调用者)调用操作来限制输入。spring-doc.cn

除了试验 Poller 的并发设置之外,您还可以添加 'transactional' 子元素,然后引用上下文中的任何实例。PlatformTransactionManager

XML 消息传送示例

中的 XML 消息传递示例显示了如何使用提供的一些处理 XML 有效负载的组件。 该示例使用处理表示为 XML 的书籍订单的思路。basic/xmlspring-doc.cn

此示例显示命名空间前缀可以是您想要的任何内容。 虽然我们通常使用,但对于集成 XML 组件,该示例使用 . ( 是 “Integration” 的缩写,是 “Spring Integration” 的缩写。int-xmlsi-xmlintsi

首先,将订单拆分为多个消息,每个消息表示 XPath 拆分器组件中的单个 order 项。 以下清单显示了 splitter 的配置:spring-doc.cn

<si-xml:xpath-splitter id="orderItemSplitter" input-channel="ordersChannel"
              output-channel="stockCheckerChannel" create-documents="true">
      <si-xml:xpath-expression expression="/orderNs:order/orderNs:orderItem"
                                namespace-map="orderNamespaceMap" />
  </si-xml:xpath-splitter>

然后,服务激活器将消息传递到股票检查器 POJO 中。 订单项文档包含来自库存检查器的有关订单项库存水平的信息。 然后,此扩充的订单项消息用于路由消息。 如果订单项有库存,则消息将路由到仓库。 下面的清单配置了路由消息的 :xpath-routerspring-doc.cn

<si-xml:xpath-router id="inStockRouter" input-channel="orderRoutingChannel" resolution-required="true">
    <si-xml:xpath-expression expression="/orderNs:orderItem/@in-stock" namespace-map="orderNamespaceMap" />
    <si-xml:mapping value="true" channel="warehouseDispatchChannel"/>
    <si-xml:mapping value="false" channel="outOfStockChannel"/>
</si-xml:xpath-router>

当订单项目没有库存时,将使用 XSLT 将消息转换为适合发送给供应商的格式。 下面的清单配置了 XSLT 转换器:spring-doc.cn

<si-xml:xslt-transformer input-channel="outOfStockChannel"
  output-channel="resupplyOrderChannel"
  xsl-resource="classpath:org/springframework/integration/samples/xml/bigBooksSupplierTransformer.xsl"/>