Spring Integration 示例
从 Spring Integration 2.0 开始,Spring Integration 发行版不再包含示例。相反,我们转向了一个更简单的协作模型,该模型应该促进更好的社区参与,理想情况下,会有更多贡献。示例现在拥有一个专门的 GitHub 存储库。示例开发也有自己的生命周期,它不依赖于框架发布的生命周期,尽管出于兼容性原因,该存储库仍然标记了每个主要版本。
对社区的巨大益处是,我们现在可以添加更多示例,并立即让您使用它们,而无需等待下一个版本。拥有一个不与实际框架绑定的独立 GitHub 存储库也是一个巨大的优势。您现在拥有一个专门的地方来建议示例,以及报告现有示例的问题。您也可以向我们提交示例作为拉取请求。如果我们认为您的示例具有价值,我们将很乐意将其添加到“示例”存储库中,并适当地将您列为作者。
从哪里获取示例
Spring Integration 示例项目托管在 GitHub 上。为了检出或克隆示例,您必须在系统上安装 Git 客户端。许多平台都提供了一些基于 GUI 的产品(例如,EGit 用于 Eclipse IDE)。简单的 Google 搜索可以帮助您找到它们。您也可以使用 Git 的命令行界面。
如果您需要有关如何安装或使用 Git 的更多信息,请访问:https://git.js.cn/. |
要使用 Git 命令行工具克隆(检出)Spring Integration 示例存储库,请发出以下命令
$ git clone https://github.com/spring-projects/spring-integration-samples.git
前面的命令将整个示例存储库克隆到您发出该 git
命令的工作目录中的名为 spring-integration-samples
的目录中。由于示例存储库是一个实时存储库,您可能希望定期执行拉取(更新)以获取新示例和对现有示例的更新。为此,请发出以下 git pull
命令
$ git pull
提交示例或示例请求
您可以提交新的示例和示例请求。我们非常感谢您为改进示例所做的任何努力,包括分享好主意。
我如何贡献自己的示例?
GitHub 用于社交编码:如果您想将自己的代码示例提交到 Spring Integration 示例项目,我们鼓励您通过 拉取请求 从 此存储库的分支 中进行贡献。如果您想以这种方式贡献代码,请尽可能参考 GutHub 问题,该问题提供了一些有关您的示例的详细信息。
签署贡献者许可协议
非常重要:在我们接受您的 Spring Integration 示例之前,您需要签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议不会授予任何人对主存储库的提交权限,但它确实意味着我们可以接受您的贡献,并且如果您这样做,您将获得作者署名。要阅读和签署 CLA,请访问 从**项目**下拉菜单中选择**Spring Integration**。项目负责人是 Artem Bilan。 |
代码贡献流程
有关实际代码贡献流程,请阅读 Spring Integration 的贡献者指南。它们也适用于示例项目。您可以在 github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc 找到它们
此流程确保每个提交都经过同行评审。事实上,核心提交者遵循完全相同的规则。我们热切期待您的 Spring Integration 示例!
示例请求
如 前面所述,Spring Integration Samples 项目使用 GitHub 问题作为错误跟踪系统。要提交新的示例请求,请访问 github.com/spring-projects/spring-integration-samples/issues。
示例结构
从 Spring Integration 2.0 开始,示例的结构发生了变化。随着更多示例的计划,我们意识到并非所有示例都具有相同的目标。它们都共享展示如何应用和使用 Spring Integration 框架的共同目标。但是,它们的不同之处在于一些示例侧重于技术用例,而另一些示例侧重于业务用例。此外,一些示例旨在展示可用于解决某些场景(技术和业务)的各种技术。新的示例分类使我们能够根据每个示例解决的问题更好地组织它们,同时为您提供更简单的方法来找到适合您需求的示例。
目前,有四个类别。在示例存储库中,每个类别都有自己的目录,以类别名称命名
- 基本 (
samples/basic
) -
这是一个很好的入门起点。这里的示例在技术上是驱动的,并且展示了关于配置和代码的最低限度。这些应该通过向您介绍 Spring Integration 的基本概念、API 和配置以及企业集成模式 (EIP) 来帮助您快速入门。例如,如果您正在寻找有关如何实现和连接服务激活器到消息通道、如何使用消息网关作为消息交换的 фасадом 以及如何开始使用 MAIL、TCP/UDP 或其他模块的答案,这里就是找到一个好示例的地方。最重要的是,
samples/basic
是一个很好的入门起点。 - 中级 (
samples/intermediate
) -
此类别针对已经熟悉 Spring Integration 框架(超出入门)但需要更多指导来解决他们在切换到消息架构后可能遇到的更高级技术问题的开发人员。例如,如果您正在寻找有关如何在各种消息交换场景中处理错误或如何为某些消息永远不会到达聚合的情况正确配置聚合器,或者任何超出特定组件的基本实现和配置并暴露“还有什么”类型的问题,这里就是找到这些类型示例的地方。
- 高级 (
samples/advanced
) -
此类别针对熟悉 Spring Integration 框架的开发人员,他们希望通过使用 Spring Integration 的公共 API 来扩展框架以解决特定的自定义需求。例如,如果您正在寻找展示如何实现自定义通道或消费者(基于事件或基于轮询)的示例,或者您正在尝试找出在 Spring Integration bean 解析器层次结构之上实现自定义 bean 解析器的最合适方法(可能是在实现您自己的命名空间和自定义组件的模式时),这里就是您应该查看的地方。您还可以在这里找到有助于您进行适配器开发的示例。Spring Integration 附带了一个广泛的适配器库,可让您将远程系统与 Spring Integration 消息传递框架连接起来。但是,您可能需要与核心框架未提供适配器的系统集成。如果是这样,您可能决定实现自己的适配器(请考虑贡献它)。此类别将包括展示如何实现的示例。
- 应用程序 (
samples/applications
) -
此类别针对对消息驱动架构和 EIP 有良好理解,并且对 Spring 和 Spring Integration 有高于平均水平的理解的开发人员和架构师,他们正在寻找解决特定业务问题的示例。换句话说,此类别中示例的重点是业务用例以及如何使用消息驱动架构,特别是 Spring Integration 来解决这些用例。例如,如果您想了解如何使用 Spring Integration 实现和自动化贷款经纪人或旅行社流程,这里就是您找到这些类型示例的地方。
Spring Integration 是一个社区驱动的框架。因此,社区参与非常重要。这包括示例。如果您找不到您要找的内容,请告诉我们! |
示例
目前,Spring Integration 附带了相当多的示例,而且您只能期望更多。为了帮助您更好地浏览它们,每个示例都附带自己的 readme.txt
文件,其中涵盖了有关示例的几个详细信息(例如,它解决了哪些 EIP 模式,它试图解决什么问题,如何运行示例以及其他详细信息)。但是,某些示例需要更详细,有时需要图形化的解释。在本节中,您可以找到我们认为需要特别注意的示例的详细信息。
贷款经纪人
本节介绍了 Spring Integration 示例中包含的贷款经纪人示例应用程序。此示例的灵感来自 Gregor Hohpe 和 Bobby Woolf 的著作《企业集成模式》中介绍的示例之一。
以下图表展示了整个流程
EIP 架构的核心是简单而强大的管道、过滤器和消息概念。端点(过滤器)通过通道(管道)相互连接。生产端点将消息发送到通道,而消费端点检索消息。这种架构旨在定义各种机制,描述信息如何在端点之间交换,而无需了解这些端点是什么或它们交换的信息是什么。因此,它提供了一个非常松散耦合且灵活的协作模型,同时将集成问题与业务问题分离。EIP 通过进一步定义扩展了这种架构
-
管道类型(点对点通道、发布订阅通道、通道适配器等)
-
过滤器核心和过滤器与管道协作方式的模式(消息路由器、拆分器和聚合器、各种消息转换模式等)
EIP 书籍的第 9 章很好地描述了此用例的细节和变体,但以下是简要总结:在寻找最佳贷款报价时,消费者会订阅贷款经纪人的服务,贷款经纪人会处理以下细节
-
消费者预筛选(例如,获取和审查消费者的信用历史)
-
确定最合适的银行(例如,根据消费者的信用历史或评分)
-
向每家选定的银行发送贷款报价请求
-
收集每家银行的回复
-
根据消费者的要求过滤回复并确定最佳报价。
-
将贷款报价传回给消费者。
获得贷款报价的实际流程通常会更复杂。但是,由于我们的目标是演示企业集成模式如何在 Spring Integration 中实现和实施,因此用例已被简化,只关注流程的集成方面。这不是要给你提供消费者财务方面的建议。
通过聘请贷款经纪人,消费者与贷款经纪人运营的细节隔离开来,每个贷款经纪人的运营方式可能有所不同,以保持竞争优势,因此我们组装和实施的任何东西都必须灵活,以便可以快速轻松地引入任何更改。
贷款经纪人示例实际上并没有与任何“虚构的”银行或信用局进行通信。这些服务是模拟的。 |
我们的目标是组装、编排和测试整个流程的集成方面。只有这样,我们才能开始考虑将此类流程连接到实际服务。那时,组装的流程及其配置不会改变,无论特定贷款经纪人与之合作的银行数量或用于与这些银行通信的通信媒体(或协议)类型(JMS、WS、TCP 等)如何。
设计
正如您分析前面列出的六个需求时所见,它们都是集成问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者及其愿望的更多信息,并用额外的元信息丰富贷款请求。然后,我们必须过滤这些信息以选择最合适的银行列表等等。丰富、过滤和选择都是集成问题,EIP 为这些问题定义了模式形式的解决方案。Spring Integration 提供了这些模式的实现。
下图显示了消息网关的表示
消息网关模式提供了一种简单的机制来访问消息系统,包括我们的贷款经纪人。在 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>
我们当前的网关提供了两种可以调用的方法。一种返回最佳报价,另一种返回所有报价。不知何故,在流程下游,我们需要知道调用者需要哪种类型的回复。在消息架构中实现此目的的最佳方法是使用一些元数据丰富消息的内容,这些元数据描述您的意图。内容丰富器是解决此问题的模式之一。为了方便起见,Spring Integration 提供了一个单独的配置元素来用任意数据丰富消息头(稍后描述)。但是,由于 gateway
元素负责构建初始消息,因此它包含了用任意消息头丰富新创建消息的功能。在我们的示例中,每当调用 getBestQuote()
方法时,我们都会添加一个值为 BEST
的 RESPONSE_TYPE
头。对于其他方法,我们不添加任何头。现在,我们可以在下游检查此头的存在。根据其存在及其值,我们可以确定调用者想要哪种类型的回复。
根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些顶级银行只接受符合最低信用评分要求的消费者的报价请求。因此,如果在将消息转发到银行之前,用这些信息丰富消息会很好。如果需要完成多个流程才能提供这些元信息,那么将这些流程分组到一个单元中也会很好。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则,选择一个消息通道列表(银行通道)来发送报价请求。
组合消息处理器
组合消息处理器模式描述了围绕构建端点的规则,这些端点维护对消息流的控制,消息流由多个消息处理器组成。在 Spring Integration 中,组合消息处理器模式由 <chain>
元素实现。
下图显示了链模式
上面的图片显示我们有一个链,其中包含一个内部 header-enricher 元素,该元素使用 `CREDIT_SCORE` 头部进一步丰富消息内容,以及值(由对信用服务的调用确定 - 一个由 'creditBureau' 名称标识的简单 POJO spring bean)。然后它委托给消息路由器。
下面的图片显示了消息路由器模式
Spring Integration 提供了消息路由模式的几种实现。在本例中,我们使用一个路由器,它根据评估表达式(在 Spring 表达式语言中)来确定通道列表,该表达式查看信用评分(在之前的步骤中确定),并从 `id` 为 `banks` 的 `Map` bean 中选择通道列表,其值为 `premier` 或 `secondary`,具体取决于信用评分的值。一旦选择通道列表,消息就会被路由到这些通道。
现在,贷款经纪人需要做的最后一件事是接收银行的贷款报价,按消费者进行汇总(我们不想向一个消费者展示另一个消费者的报价),根据消费者的选择标准(最佳报价或所有报价)组装响应,并将回复发送给消费者。
下面的图片显示了消息聚合器模式
聚合器模式描述了一个端点,它将相关消息分组到单个消息中。可以提供标准和规则来确定聚合和关联策略。Spring Integration 提供了聚合器模式的几种实现,以及方便的基于命名空间的配置。
以下示例显示了如何定义聚合器
<int:aggregator id="quotesAggregator"
input-channel="quotesAggregationChannel"
method="aggregateQuotes">
<beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>
我们的贷款经纪人使用 `<aggregator>` 元素定义了一个 'quotesAggregator' bean,它提供了一个默认的聚合和关联策略。默认的关联策略根据 `correlationId` 头部关联消息(参见 EIP 书籍中的关联标识符模式)。请注意,我们从未提供此头的值。它是在路由器之前自动设置的,当时它为每个银行通道生成了单独的消息。
一旦消息被关联,它们就会被释放到实际的聚合器实现中。虽然 Spring Integration 提供了一个默认的聚合器,但它的策略(从所有消息中收集有效负载列表,并使用此列表作为有效负载构建新消息)不满足我们的要求。在消息中拥有所有结果是一个问题,因为我们的消费者可能需要一个最佳报价或所有报价。为了传达消费者的意图,我们在流程的早期设置了 `RESPONSE_TYPE` 头部。现在我们必须评估此头部,并返回所有报价(默认聚合策略将起作用)或最佳报价(默认聚合策略不起作用,因为我们必须确定哪个贷款报价是最好的)。
在更现实的应用程序中,选择最佳报价可能基于复杂的标准,这些标准可能会影响聚合器实现和配置的复杂性。不过,现在我们把它简化了。如果消费者想要最佳报价,我们选择利率最低的报价。为了实现这一点,`LoanQuoteAggregator` 类按利率对所有报价进行排序,并返回第一个报价。`LoanQuote` 类实现了 `Comparable`,以便根据利率属性比较报价。一旦创建了响应消息,它就会被发送到启动该流程的消息网关的默认回复通道(因此,发送到消费者)。我们的消费者获得了贷款报价!
总之,基于 POJO(即现有或遗留)逻辑和轻量级、可嵌入的消息框架(Spring Integration)构建了一个相当复杂的流程,该流程采用松耦合的编程模型,旨在简化异构系统的集成,而无需依赖重量级的 ESB 式引擎或专有的开发和部署环境。作为开发人员,您无需将 Swing 或基于控制台的应用程序移植到 ESB 式服务器或实现专有接口,仅仅因为您遇到了集成问题。
本示例以及本节中的其他示例都建立在企业集成模式之上。您可以将它们视为解决方案的“构建块”。它们并非旨在成为完整的解决方案。集成问题存在于所有类型的应用程序中(无论是基于服务器的还是非基于服务器的)。我们的目标是使应用程序集成无需更改设计、测试和部署策略。
咖啡馆示例
本节介绍 Spring Integration 示例中包含的咖啡馆示例应用程序。该示例的灵感来自 Gregor Hohpe 的 Ramblings 中的另一个示例。
该领域是咖啡馆,以下图表描述了基本流程
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
实例,但使用不同的方法(prepareHotDrink
或prepareColdDrink
),分别对应于订单项被路由到的两个通道之一。以下清单显示了 Barista 类(包含prepareHotDrink
和prepareColdDrink
方法)
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>
元素的指定。这会触发创建代理,该代理实现给定的服务接口并将其连接到通道。通道名称在Cafe
接口的@Gateway
注释中提供,如下面的接口定义所示
public interface Cafe {
@Gateway(requestChannel="orders")
void placeOrder(Order order);
}
最后,看一下CafeDemo
本身的main()
方法
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);
}
}
要运行此示例以及其他八个示例,请参考主发行版samples 目录中的README.txt (如本章开头所述)。
|
运行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 毫秒),您会发现它偶尔会通过强制任务调度程序(调用者)调用操作来限制输入。
除了试验轮询器的并发设置外,您还可以添加“transactional”子元素,然后引用上下文中的任何PlatformTransactionManager 实例。
|
XML 消息示例
basic/xml
中的 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"/>