Spring Integration 示例
从 Spring Integration 2.0 开始,Spring Integration 发行版不再包含示例。 相反,我们转向了一种更简单的协作模型,该模型应该能促进更好的社区参与,理想情况下,还能带来更多的贡献。 示例现在有了一个专门的 GitHub 仓库。 示例开发也有自己的生命周期,它不依赖于框架发布的生命周期,尽管出于兼容性原因,该仓库仍会用每个主要版本进行标记。 这对社区来说是一个巨大的好处,因为我们现在可以添加更多示例并立即提供给您,而无需等待下一个版本。 拥有一个不与实际框架绑定的专用 GitHub 仓库也是一个巨大的好处。 您现在有一个专门的地方来建议示例以及报告现有示例的问题。 您还可以以拉取请求的形式向我们提交示例。 如果我们认为您的示例增加了价值,我们将非常乐意将其添加到“samples”仓库中,并适当地将您列为作者。
如何获取示例
Spring Integration Samples 项目托管在 GitHub 上。 为了检出或克隆示例,您必须在系统上安装 Git 客户端。 有许多基于 GUI 的产品可用于许多平台(例如,Eclipse IDE 的 EGit)。 简单的 Google 搜索可以帮助您找到它们。 您还可以使用 Git 的命令行界面。
如果您需要有关如何安装或使用 Git 的更多信息,请访问:https://git-scm.com/。 |
要使用 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 Samples 项目,我们鼓励通过本仓库的 forks 中的 拉取请求 进行贡献。 如果您想以这种方式贡献代码,请尽可能引用一个 GutHub issue,其中提供有关您的示例的一些详细信息。
非常重要:在我们接受您的 Spring Integration 示例之前,我们需要您签署 SpringSource 贡献者许可协议 (CLA)。 签署贡献者协议不会授予任何人对主仓库的提交权限,但这意味着我们可以接受您的贡献,并且如果接受,您将获得作者署名。 要阅读和签署 CLA,请访问: [role="bare"][role="bare"][role="bare"]support.springsource.com/spring_committer_signup 从 Project 下拉列表中,选择 Spring Integration。 项目负责人是 Artem Bilan。
代码贡献流程
有关实际的代码贡献流程,请阅读 Spring Integration 的贡献者指南。 它们也适用于示例项目。 您可以在 [role="bare"][role="bare"][role="bare"]github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc 找到它们。
这个过程确保了每个提交都经过同行评审。 事实上,核心提交者遵循完全相同的规则。 我们衷心期待您的 Spring Integration 示例!
示例请求
如 samples-how-can-i-contribute,Spring Integration Samples 项目使用 GitHub issue 作为错误跟踪系统。 要提交新的示例请求,请访问 [role="bare"][role="bare"][role="bare"]github.com/spring-projects/spring-integration-samples/issues。
示例结构
从 Spring Integration 2.0 开始,示例的结构发生了变化。 随着更多示例的计划,我们意识到并非所有示例都具有相同的目标。 它们都共享展示如何应用和使用 Spring Integration 框架的共同目标。 但是,它们的不同之处在于,一些示例侧重于技术用例,而另一些则侧重于业务用例。 此外,一些示例是关于展示可用于解决某些场景(包括技术和业务)的各种技术。 新的示例分类使我们能够更好地根据每个示例解决的问题来组织它们,同时为您提供一种更简单的方法来找到满足您需求的正确示例。
目前,有四个类别。 在示例仓库中,每个类别都有自己的目录,以类别名称命名:
- Basic (
samples/basic
) -
这是一个很好的入门之处。 这里的示例是技术驱动的,并展示了配置和代码方面的最低要求。 这些示例应该通过向您介绍 Spring Integration 的基本概念、API 和配置以及企业集成模式 (EIP),帮助您快速入门。 例如,如果您正在寻找有关如何实现服务激活器并将其连接到消息通道、如何使用消息网关作为消息交换的门面,或者如何开始使用 MAIL、TCP/UDP 或其他模块的答案,那么这里是找到一个好示例的正确地方。 总而言之,
samples/basic
是一个很好的入门之处。 - Intermediate (
samples/intermediate
) -
此类别面向已经熟悉 Spring Integration 框架(不仅仅是入门)但需要更多指导来解决切换到消息传递架构后可能遇到的更高级技术问题的开发人员。 例如,如果您正在寻找有关如何在各种消息交换场景中处理错误,或者如何为某些消息永远不会到达聚合的情况正确配置聚合器,或者任何超出特定组件的基本实现和配置并暴露“还有什么”类型问题的问题的答案,那么这里是找到这类示例的正确地方。
- Advanced (
samples/advanced
) -
此类别面向非常熟悉 Spring Integration 框架但希望通过使用 Spring Integration 的公共 API 扩展它以解决特定自定义需求的开发人员。 例如,如果您正在寻找示例,向您展示如何实现自定义通道或消费者(基于事件或基于轮询),或者您正在尝试找出在 Spring Integration Bean 解析器层次结构之上实现自定义 Bean 解析器(例如,在实现您自己的自定义组件命名空间和模式时)的最合适方法,那么这里是寻找的正确地方。 在这里您还可以找到有助于适配器开发的示例。 Spring Integration 附带了一个广泛的适配器库,可让您将远程系统与 Spring Integration 消息传递框架连接起来。 但是,您可能需要与核心框架未提供适配器的系统集成。 如果是这样,您可能会决定实现自己的适配器(请考虑贡献它)。 此类别将包含向您展示如何操作的示例。
- Applications (
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 等),组装好的过程及其配置都不会改变。
设计
在分析前面列出的 samples-loan-broker-requirements 时,您可以看到它们都是集成关注点。 例如,在消费者预筛选步骤中,我们需要收集有关消费者和消费者意愿的附加信息,并用附加元信息丰富贷款请求。 然后,我们必须过滤此类信息以选择最合适的银行列表等等。 丰富、过滤和选择都是集成关注点,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()
方法时,我们都会添加一个 RESPONSE_TYPE
头,其值为 BEST
。
对于其他方法,我们不添加任何头。
现在我们可以在下游检查此头的存在。
根据其存在和值,我们可以确定调用者想要哪种类型的回复。
根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些顶级银行只接受符合最低信用评分要求的消费者的报价请求。 因此,如果消息在转发给银行之前能用此类信息丰富,那就太好了。 如果需要完成多个过程来提供此类元信息,并且这些过程可以分组为一个单元,那也很好。 在我们的用例中,我们需要确定信用评分,并根据信用评分和一些规则,选择要发送报价请求的消息通道(银行通道)列表。
组合消息处理器
组合消息处理器模式描述了围绕构建控制消息流(由多个消息处理器组成)的端点的规则。
在 Spring Integration 中,组合消息处理器模式由 <chain>
元素实现。
下图显示了链模式:

上图显示我们有一个链,其中包含一个内部 header-enricher 元素,该元素进一步用 CREDIT_SCORE
头和值(由对信用服务(一个简单的 POJO Spring Bean,由“creditBureau”名称标识)的调用确定)丰富消息内容。
然后它委托给消息路由器。
下图显示了消息路由器模式:

Spring Integration 提供了消息路由模式的几种实现。
在这种情况下,我们使用一个路由器,它通过评估一个表达式(用 Spring Expression Language)来确定通道列表,该表达式查看信用评分(在上一步中确定)并从 Map
Bean 中选择通道列表,该 Map
Bean 的 id
为 banks
,其值根据信用评分的值是 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
以根据 rate 属性比较报价。
一旦创建了响应消息,它就会发送到消息网关的默认回复通道(因此发送给消费者),该网关启动了该过程。
我们的消费者得到了贷款报价!
总而言之,一个相当复杂的过程是基于 POJO(即现有或遗留)逻辑和轻量级、可嵌入的消息传递框架(Spring Integration)以及松散耦合的编程模型组装而成的,旨在简化异构系统的集成,而无需重量级的 ESB 类引擎或专有的开发和部署环境。 作为开发人员,您不应该仅仅因为有集成问题而需要将您的 Swing 或基于控制台的应用程序移植到 ESB 类服务器或实现专有接口。
本示例和本节中的其他示例都构建在企业集成模式之上。 您可以将它们视为解决方案的“构建块”。 它们并非旨在成为完整的解决方案。 集成问题存在于所有类型的应用程序中(无论是基于服务器还是非基于服务器)。 我们的目标是让集成应用程序无需更改设计、测试和部署策略。
咖啡馆示例
本节涵盖 Spring Integration 示例中包含的咖啡馆示例应用程序。 此示例的灵感来自于 Gregor Hohpe 的 漫谈 中特色示例之一。
领域是一个咖啡馆,下图描绘了基本流程:

Order
对象可能包含多个 OrderItem
。
一旦下订单,拆分器会将复合订单消息拆分为每个饮料的单个消息。
然后,路由器处理这些消息,路由器通过检查 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);
}
}
要运行此示例以及其他八个示例,请参阅主发行版 |
当您运行 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”子元素,然后在上下文中引用任何 |
XML 消息传递示例
basic/xml
中的 XML 消息传递示例展示了如何使用一些处理 XML 有效负载的组件。
该示例使用处理表示为 XML 的图书订单的想法。
此示例表明命名空间前缀可以是您想要的任何内容。
虽然我们通常使用 |
首先,订单被拆分成多个消息,每个消息代表 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"/>