Spring 集成示例

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

对社区的巨大好处是,我们现在可以添加更多示例并立即提供给您,而无需等待下一个版本。拥有自己的与实际框架无关的 GitHub 存储库也是一大好处。您现在有一个专门的地方来建议样品以及报告现有样品的问题。您也可以向我们提交样本作为拉取请求。如果我们相信您的示例增加了价值,我们会更乐意将其添加到“示例”存储库中,并正确地将您视为作者。

从哪里获得样品

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

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

要使用 Git 命令行工具克隆(签出)Spring Integration 示例存储库,请发出以下命令:

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

前面的命令将整个示例存储库克隆到spring-integration-samples您发出该git命令的工作目录中指定的目录中。由于示例存储库是实时存储库,因此您可能希望执行定期提取(更新)以获取新示例并更新现有示例。为此,请发出以下git pull命令:

$ git pull

提交样品或样品请求

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

我如何提供自己的样品?

Github 用于社交编码:如果您想将自己的代码示例提交到 Spring Integration Samples 项目,我们鼓励通过来自该存储库的分支的拉取请求进行贡献。如果您想以这种方式贡献代码,请尽可能参考GutHub 问题,该问题提供有关您的示例的一些详细信息。

签署贡献者许可协议

非常重要:在我们接受您的 Spring Integration 示例之前,我们需要您签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议并不授予任何人对主存储库的提交权限,但这确实意味着我们可以接受您的贡献,如果我们这样做,您将获得作者信用。要阅读并签署 CLA,请访问:

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

代码贡献过程

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

此过程可确保每个提交都得到同行评审。事实上,核心提交者遵循完全相同的规则。我们非常期待您的 Spring Integration 示例!

样品请求

如前所述 Spring Integration Samples 项目使用 GitHub 问题作为错误跟踪系统。要提交新的示例请求,请访问https://github.com/spring-projects/spring-integration-samples/issues

样本结构

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

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

基本 ( samples/basic)

这是一个开始的好地方。这里的示例具有技术动机,并展示了关于配置和代码的最低限度。这些将通过向您介绍 Spring 集成以及企业集成模式 (EIP) 的基本概念、API 和配置来帮助您快速入门。例如,如果您正在寻找有关如何实现服务激活器并将其连接到消息通道、如何使用消息网关作为消息交换的门面或如何开始使用 MAIL、TCP/UDP 或其他模块,这是找到好的示例的正确位置。底线samples/basic是一个开始的好地方。

中级 ( samples/intermediate)

此类别面向已经熟悉 Spring Integration 框架(尚未入门)但在解决切换到消息传递架构后可能遇到的更高级技术问题时需要更多指导的开发人员。例如,如果您正在寻找有关如何处理各种消息交换场景中的错误或如何正确配置聚合器以解决某些消息永远不会到达聚合的情况的答案,或者超出基本实现的任何其他问题的答案和特定组件的配置并暴露“还有什么”类型的问题,这是找到这些类型示例的正确位置。

高级 ( samples/advanced)

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

应用 ( samples/applications)

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

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

样品

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

贷款经纪人

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

下图展示了整个过程:

贷款经纪人eip
图 1. 贷款经纪人示例

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

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

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

EIP 书的第 9 章很好地描述了这个用例的细节和变化,但这里是一个简短的总结:在购买最佳贷款报价时,消费者订阅贷款经纪人的服务,该经纪人处理以下细节:

  • 消费者预筛选(例如,获取和审查消费者的信用记录)

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

  • 向每个选定的银行发送贷款报价请求

  • 收集每家银行的回复

  • 根据消费者的要求过滤响应并确定最佳报价。

  • 将贷款报价传回给消费者。

获得贷款报价的实际过程通常要复杂一些。但是,由于我们的目标是演示如何在 Spring Integration 中实现和实现企业集成模式,因此已将用例简化为仅关注流程的集成方面。这不是试图为您提供消费金融方面的建议。

通过聘请贷款经纪人,消费者与贷款经纪人的操作细节隔离开来,每个贷款经纪人的操作可能会相互延迟以保持竞争优势,因此我们组装和实施的任何东西都必须灵活,以便可以引入任何变化快速无痛。

贷款经纪人样本实际上并没有与任何“想象中的”银行或信用局交谈。这些服务被淘汰了。

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

设计

当您分析前面列出的六个需求时,您可以看到它们都是集成问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者和消费者需求的额外信息,并使用额外的元信息来丰富贷款请求。然后我们必须过滤这些信息以选择最合适的银行列表等。丰富、过滤和选择都是 EIP 以模式形式定义解决方案的集成问题。Spring Integration 提供了这些模式的实现。

下图显示了消息传递网关的表示:

网关
图 2. 消息传递网关

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

<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>

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

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

组合消息处理器

组合消息处理器模式描述了围绕构建端点的规则,这些端点维护对消息流的控制,该消息流由多个消息处理器组成。在 Spring Integration 中,组合消息处理器模式由<chain>元素实现。

下图显示了链模式:

链
图 3. 链

上图显示我们有一个带有内部 header-enricher 元素的链,该元素进一步丰富了消息内容的CREDIT_SCOREheader 和 value(由对 credit 服务的调用确定 - 一个简单的 POJO spring bean,由 '信用局的名称)。然后它委托给消息路由器。

下图显示了消息路由器模式:

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

Spring Integration 提供了消息路由模式的多种实现。在这种情况下,我们使用一个路由器,它基于评估一个表达式(在 Spring 表达式语言中)来确定通道列表,该表达式查看信用评分(在上一步中确定)并从Mapbeanid中选择通道列表banks其值为premiersecondary,基于信用评分的值。一旦选择了频道列表,消息就会被路由到这些频道。

现在,贷款经纪人需要从银行接收贷款报价的最后一件事,按消费者汇总它们(我们不想显示从一个消费者到另一个消费者的报价),根据消费者的选择标准(单一最佳报价或所有报价)并将回复发送给消费者。

下图显示了消息聚合器模式:

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

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

以下示例显示了如何定义聚合器:

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

我们的贷款经纪人定义了一个带有元素的 'quotesAggregator' bean <aggregator>,它提供了默认的聚合和关联策略。默认关联策略基于correlationId标头关联消息(请参阅EIP book 中的关联标识符模式)。请注意,我们从未提供此标头的值。它是由路由器早先自动设置的,当时它为每个银行通道生成单独的消息。

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

在更实际的应用程序中,选择最佳报价可能基于可能影响聚合器实现和配置复杂性的复杂标准。不过,就目前而言,我们正在让它变得简单。如果消费者想要最好的报价,我们会选择利率最低的报价。为此,LoanQuoteAggregator该类按利率对所有报价进行排序并返回第一个报价。该类LoanQuote实现Comparable基于 rate 属性比较报价。创建响应消息后,它会被发送到启动进程的消息传递网关的默认回复通道(因此也发送给消费者)。我们的消费者得到了贷款报价!

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

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

咖啡厅样品

本节介绍 Spring Integration 示例中包含的 cafe 示例应用程序。此示例的灵感来自 Gregor Hohpe 的Ramblings中的另一个示例。

域是咖啡馆的域,下图描述了基本流程:

咖啡厅
图 6. 咖啡馆示例

Order对象可能包含多个OrderItems. 下订单后,拆分器将复合订单消息分解为每种饮料的单个消息。然后每一个都由一个路由器处理,该路由器确定饮料是热的还是冷的(通过检查OrderItem对象的“isIced”属性)。准备每种饮料,Barista但热饮和冷饮准备通过两种不同的方法处理:“prepareHotDrink”和“prepareColdDrink”。然后将准备好的饮料发送到Waiter它们被聚合成一个Delivery对象的地方。

以下清单显示了 XML 配置:

<?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>

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

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

在路由器的情况下,返回值不必是一个MessageChannel实例(尽管它可以是)。在此示例中,将String返回一个包含通道名称的值,如以下清单所示。

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

现在,回到 XML,您可以看到有两个<service-activator>元素。这些中的每一个都委派给同一个Barista实例,但使用不同的方法(prepareHotDrinkprepareColdDrink),每个对应于已路由订单项的两个通道之一。以下清单显示了 Barista 类(其中包含prepareHotDrinkprepareColdDrink方法)

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;
        }
    }
}

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

public interface Cafe {

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

}

最后看一下自身的main()方法CafeDemo

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);
    }
}
要运行这个示例以及其他八个示例,请参阅主分发目录中README.txtsamples目录(如本章开头所述)。

运行时cafeDemo,您可以看到冷饮最初的制备速度比热饮快。因为有一个聚合器,冷饮受到热饮制备速率的有效限制。这是可以预料的,基于它们各自的 1000 和 5000 毫秒的延迟。但是,通过使用并发任务执行器配置轮询器,您可以显着改变结果。例如,您可以为热饮咖啡师使用具有五个工作人员的线程池执行器,同时保持冷饮咖啡师保持原样。以下清单配置了这种安排:

<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"/>

此外,请注意,每次调用都会显示工作线程名称。您可以看到热饮是由任务执行器线程准备的。如果您提供更短的轮询间隔(例如 100 毫秒),您可以看到它偶尔会通过强制任务调度程序(调用方)调用操作来限制输入。

除了试验轮询器的并发设置外,您还可以添加“事务性”子元素,然后引用PlatformTransactionManager上下文中的任何实例。

XML 消息传递示例

中的 XML 消息传递示例basic/xml展示了如何使用一些提供的处理 XML 有效负载的组件。该示例使用处理以 XML 表示的书籍订单的想法。

此示例显示名称空间前缀可以是任何您想要的。虽然我们通常使用int-xml集成 XML 组件,但示例使用si-xml. (int“Integration”si的缩写,“Spring Integration”的缩写。)

首先,订单被拆分为多个消息,每个消息代表来自 XPath 拆分器组件的单个订单项。以下清单显示了拆分器的配置:

<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-router路由消息的方法:

<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 转换器:

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

1. see XML Configuration