Spring Integration Samples
从 Spring Integration 2.0 开始,Spring Integration 分发不再包括示例。相反,我们已经切换到一个更简单的协作模型,该模型应促进更多的社区参与,理想情况下也应促进更多的贡献。示例现在有一个专门的 GitHub 存储库。示例开发也有自己的生命周期,该生命周期不依赖于框架版本的生命周期,但出于兼容性原因,存储库仍然标记有每个主要版本。 社区的巨大好处是,我们现在可以添加更多示例,并立即向你提供这些示例,而无需等待下一个版本。拥有一个独立于实际框架的 GitHub 存储库也是一大好处。你现在拥有一个专门的地方来建议示例以及报告现有示例的问题。你还可以将示例作为拉取请求提交给我们。如果我们认为你的示例很有价值,我们将非常乐意将其添加到“示例”存储库中,并正确注明你是作者。
Where to Get Samples
Spring Integration 样本项目托管在 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
Submitting Samples or Sample Requests
你可以提交新示例和示例请求。我们非常感谢各位对增强示例所做的努力,包括分享好点子。
How Can I Contribute My Own Samples?
GitHub 可用于社交编码:如果您希望向 Spring Integration Samples 项目提交自己的代码样例,我们鼓励通过提交至该存储库的 pull requests做出贡献。如果您希望通过这种方式贡献代码,请尽可能参考 GutHub issue,以提供有关您样例的一些详细信息。
非常重要:在我们接受你的 Spring Integration 样例前,你需要签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议不会向任何人授予对主存储库的提交权限,但意味着我们可以接受你的贡献,如果你做出了贡献,你将获得创作者荣誉。要阅读并签署 CLA,请转到: [role="bare"][role="bare"]https://support.springsource.com/spring_committer_signup 在 项目 下拉菜单中,选择 Spring Integration。项目负责人是 Artem Bilan。
Code Contribution Process
如需了解实际的代码贡献流程,请阅读 Spring Integration 的贡献者指南。它们也适用于样例项目。您可以在 [role="bare"][role="bare"]https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md中找到它们。
此流程可确保对每次提交进行同行评审。事实上,核心提交者遵循完全相同的规则。我们非常期待收到你的 Spring Integration 样例!
Sample Requests
正如 mentioned earlier 所述,春季集成样本项目将 GitHub 问题用作 Bug 跟踪系统。若要提交新的样本请求,请访问 [role="bare"][role="bare"]https://github.com/spring-projects/spring-integration-samples/issues.
Samples Structure
从 Spring Integration 2.0 开始,示例结构已发生更改。随着更多示例的规划,我们意识到并非所有示例都具有相同的目标。它们共同的目标是向你展示如何应用和使用 Spring Integration 框架。但是,它们之间的区别在于,一些示例注重技术用例,而另一些则注重业务用例。此外,一些示例用于展示可用于解决某些场景(技术和业务)的各种技术。示例的新分类使我们能够根据每个示例所解决的问题更好地组织示例,同时为你提供更简单的方法来查找适合你需求的正确示例。
目前有四类。在示例存储库中,每类都有自己的目录,其名称以类名命名:
- Basic (
samples/basic
) -
This is a good place to get started. The samples here are technically motivated and demonstrate the bare minimum with regard to configuration and code. These should help you to get started quickly by introducing you to the basic concepts, API, and configuration of Spring Integration as well as Enterprise Integration Patterns (EIP). For example, if you are looking for an answer on how to implement and wire a service activator to a message channel, how to use a messaging gateway as a facade to your message exchange, or how to get started with MAIL, TCP/UDP or other modules, this is the right place to find a good sample. The bottom line is
samples/basic
is a good place to get started. - Intermediate (
samples/intermediate
) -
This category targets developers who are already familiar with the Spring Integration framework (beyond getting started) but need some more guidance while resolving the more advanced technical problems they might encounter after switching to a messaging architecture. For example, if you are looking for an answer on how to handle errors in various message exchange scenarios or how to properly configure the aggregator for a situation where some messages do not ever arrive for aggregation, or any other issue that goes beyond a basic implementation and configuration of a particular component and exposes “what else” types of problems, this is the right place to find these type of samples.
- Advanced (
samples/advanced
) -
This category targets developers who are very familiar with the Spring Integration framework but are looking to extend it to address a specific custom need by using Spring Integration’s public API. For example, if you are looking for samples showing you how to implement a custom channel or consumer (event-based or polling-based) or you are trying to figure out the most appropriate way to implement a custom bean parser on top of the Spring Integration bean parser hierarchy (perhaps when implementing your own namespace and schema for a custom component), this is the right place to look. Here you can also find samples that will help you with adapter development. Spring Integration comes with an extensive library of adapters to let you connect remote systems with the Spring Integration messaging framework. However, you might need to integrate with a system for which the core framework does not provide an adapter. If so, you might decide to implement your own (please consider contributing it). This category would include samples showing you how.
- Applications (
samples/applications
) -
This category targets developers and architects who have a good understanding of message-driven architecture and EIP and an above-average understanding of Spring and Spring Integration who are looking for samples that address a particular business problem. In other words, the emphasis of the samples in this category is business use cases and how they can be solved with a message-driven architecture and Spring Integration in particular. For example, if you want to see how a loan broker or travel agent process could be implemented and automated with Spring Integration, this is the right place to find these types of samples.
Spring Integration 是一个社区驱动框架。因此,社区参与非常重要。这包括样例。如果您找不到所需内容,请告诉我们。
Samples
目前,Spring Integration 提供了许多示例,你只能期待更多。为了帮助你更好地浏览它们,每个示例都带有一个自己的 readme.txt
文件,其中涵盖了有关该示例的几个详细信息(例如,它涉及哪些 EIP 模式、它试图解决什么问题、如何运行该示例以及其他详细信息)。但是,某些示例需要更详细且有时需要图形化的解释。在本节中,你可以找到我们认为需要特别关注的示例的详细信息。
Loan Broker
本部分涵盖了包含在 Spring Integration 样例中的贷款经纪样例应用程序。此样例受到 Gregor Hohpe 和 Bobby Woolf 的书籍 Enterprise Integration Patterns中展示的一个样例的启发。
下图显示了整个过程:
EIP 体系结构的核心是管道、过滤器以及当然的消息等非常简单但强大的概念。端点(过滤器)通过通道(管道)相互连接。生成端点将消息发送到通道,并且消耗端点检索消息。此体系结构用于定义各种机制,描述如何在端点之间交换信息,而无需了解这些端点是什么或它们交换什么信息。因此,它提供了一种非常松散耦合且灵活的协作模型,同时还将集成问题与业务问题解耦。EIP 通过进一步定义来扩展此体系结构:
-
管道类型(点对点通道、发布订阅通道、通道适配器以及其他)
-
有关过滤器如何与管道协作的核心过滤器和模式(消息路由器、拆分器和聚合器、各种消息转换模式以及其他)
EIP 书籍的第 9 章很好地描述了此用例的详细信息和变化,但以下是简要摘要:在购买最佳贷款报价时,消费者订阅贷款经纪人的服务,贷款经纪人负责处理诸如以下详细信息:
-
消费者预筛选(例如,获取并审查消费者的信用记录)
-
确定最合适的银行(例如,基于消费者的信用记录或信用评分)
-
向每个选定的银行发送贷款报价请求
-
收集来自每个银行的答复
-
根据消费者的要求过滤应答并确定最佳报价。
-
将贷款报价传递回消费者。
获得贷款报价的实际过程通常会更复杂一些。但是,由于我们的目标是展示如何在 Spring Integration 中实现企业集成模式,因此该用例已简化为仅专注于该过程的集成方面。这不是企图为你提供消费者金融方面的建议。
通过聘请贷款经纪人,消费者与贷款经纪人运营的详细信息隔离开来,并且每个贷款经纪人的运营可能彼此不同以保持竞争优势,因此我们组装和实施的任何内容都必须灵活,以便可以快速而轻松地引入任何更改。
贷款经济人样例没有真正与任何“虚拟”银行或信用局交流。这些服务已经被存根。 |
我们的目标是组装、编排和测试整个流程的集成方面。只有这样,我们才能开始考虑将此类流程连接至实际服务。那时,组装的流程及其配置不会因特定的贷款经纪人处理的银行数量或与这些银行通信所使用的通信媒体(或协议)(JMS、WS、TCP 等)而改变。
Design
当你分析前面列出的 six requirements 时,你可以看出这些都是集成问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者的附加信息以及消费者的愿望,并使用附加元信息丰富贷款申请。然后,我们必须筛选此类信息,以选择最合适的银行列表,依此类推。丰富、筛选和选择都是 EIP 定义为模式而解决的集成问题。Spring 集成提供了这些模式的实现。
下图显示了消息网关的表示:
消息网关模式提供了一个简单的机制来访问消息系统,包括我们的贷款经纪人。在 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
。对于其他方法,我们不添加任何头。现在,我们可以在下游检查此头的存在。基于它的存在及其值,我们可以确定调用者想要哪种类型的答复。
根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些高级银行只接受满足最低信用评分要求的消费者的报价请求。所以,在将消息转发到银行之前,用此类信息丰富消息将很好。同样,如果需要完成多个流程来提供此类元信息,那么最好将这些流程分组到一个单元中。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则选择一个消息通道列表(银行通道),以便向其发送报价请求。
Composed Message Processor
复合消息处理器模式描述了在构建端点时的规则,这些处理程序维护对由多个消息处理器组成的消息流的控制。在 Spring Integration 中,<chain>
元素实现了复合消息处理器模式。
下图显示了链模式:
上图显示我们有一个链,其中有一个内部标头富集器元素,该元素使用 CREDIT_SCORE
标头和值(由对信用服务—由“creditBureau”名称标识的一个简单的 POJO spring bean 的调用确定)进一步丰富消息的内容。然后它委托给消息路由器。
下图显示了消息路由器模式:
Spring Integration 提供了消息路由模式的多个实现。在这种情况下,我们使用一个路由器,该路由器根据评估一个表达式(在 Spring 表达式语言中)来确定渠道列表,该表达式查看信用评分(在上一步中确定的)并选择来自 Map
Bean 的渠道列表,该 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`标头关联消息(参见 the correlation identifier pattern in the EIP book)。请注意,我们从未提供此标头的值。路由器在为每个银行通道生成单独消息时,会自动提前设置此值。
一旦消息关联起来,它们就会被释放到实际的聚合器实现中。尽管 Spring Integration 提供了一个默认聚合器,但其策略(从所有消息中收集有效负载列表并使用此列表作为其有效负载构建一个新消息)不满足我们的要求。在消息中拥有所有结果是一个问题,因为我们的消费者可能需要一个最好的报价或所有报价。为了传达消费者的意图,我们在流程的前面设置了 RESPONSE_TYPE
头。现在,我们必须评估此头并返回所有报价(默认聚合策略可行)或最佳报价(默认聚合策略不可行,因为我们必须确定哪个贷款报价是最好的)。
在更实际的应用程序中,选择最佳报价可能基于复杂的条件,这些条件可能会影响聚合器实现和配置的复杂性。不过,现在我们把它简单化。如果消费者想要最佳报价,我们将选择利率最低的报价。为了实现这一点,LoanQuoteAggregator
类根据利率对所有报价进行排序并返回第一个报价。LoanQuote
类实现了 Comparable
,以根据速率属性对报价进行比较。创建响应消息后,它将被发送到启动该过程的消息网关的默认答复通道(因此,发送给消费者)。我们的消费者拿到了贷款报价!
总之,组装了一个相当复杂的流程,该流程基于 POJO(即现有或旧版)逻辑和一个轻量级的可嵌入消息框架(Spring Integration),该框架采用松散耦合的编程模型,旨在简化异构系统的集成,而无需重量级 ESB 类似引擎或专有开发和部署环境。作为一名开发人员,你不需要将你的 Swing 或基于控制台的应用程序移植到 ESB 类似服务器或实现专有接口,仅仅是因为你有一个集成问题。
本示例和本节中的其他示例基于企业集成模式构建。可以将它们视为解决方案的“构建基块”。它们并非完整解决方案。所有类型应用程序(无论是基于服务器的还是非基于服务器的)都存在集成问题。我们的目标是让应用程序集成不需要在设计、测试和部署策略中进行更改。
The Cafe Sample
本部分涵盖了包含在 Spring 集成示例中的咖啡店示例应用程序。这个示例的灵感来自于 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>
每个消息端点连接到输入通道、输出通道或两者。每个端点管理其自己的生命周期(默认情况下,端点在初始化时自动启动,为防止这种情况,添加值为“false”的 auto-startup
属性)。最重要的是,请注意对象是具有强类型方法参数的简单 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 毫秒),可以看到它偶尔会通过强制任务调度程序(调用程序)调用操作来限制输入。
除了测试轮询的并发设置外,您还可以添加“事务性”子元素,然后在上下文中引用任何 |
The XML Messaging Sample
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"/>