跳到主要内容
版本:7.0.2

Spring Integration 示例

DeepSeek V3 中英对照 Spring Integration Samples

自 Spring Integration 2.0 起,Spring Integration 发行版不再包含示例。我们转而采用了一种更简单的协作模式,旨在促进更好的社区参与,并期望获得更多贡献。示例现在拥有一个专门的 GitHub 仓库。示例开发也拥有自己的生命周期,虽然出于兼容性考虑,仓库仍会为每个主要版本打上标签,但其生命周期已不再依赖于框架的发布周期。

对社区而言,最大的好处在于我们现在可以添加更多示例,并立即提供给您,无需等待下一个版本发布。拥有一个独立于实际框架的GitHub仓库也是一大优势。您现在有一个专门的地方来建议示例,以及报告现有示例的问题。您还可以通过提交Pull Request的方式向我们提供示例。如果我们认为您的示例有价值,我们将非常乐意将其添加到'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

提交样本或样本请求

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

如何贡献我自己的样本?

GitHub 是一个社交化编码平台:如果您希望向 Spring Integration 示例项目提交自己的代码示例,我们鼓励您通过 fork 本仓库后发起拉取请求的方式进行贡献。若您希望通过这种方式贡献代码,请尽可能关联一个GitHub issue,该 issue 应提供关于您提交示例的相关详细信息。

代码贡献流程

关于实际的代码贡献流程,请阅读 Spring Integration 的贡献者指南。这些指南同样适用于示例项目。您可以在 github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.adoc 找到它们。

这个过程确保了每一次提交都经过同行评审。事实上,核心提交者也遵循完全相同的规则。我们热切期待您的 Spring Integration 示例!

示例请求

前文所述,Spring Integration Samples 项目使用 GitHub issue 作为缺陷跟踪系统。如需提交新的示例请求,请访问 github.com/spring-projects/spring-integration-samples/issues

示例结构

自 Spring Integration 2.0 起,示例项目的结构已发生变化。随着更多示例的规划,我们意识到并非所有示例都具有相同目标。它们都共享一个共同目标:向您展示如何应用和使用 Spring Integration 框架。然而,它们的区别在于:部分示例专注于技术用例,而其他示例则侧重于业务用例。此外,有些示例旨在展示可用于解决特定场景(包括技术和业务场景)的各种技术。新的示例分类方式让我们能够根据每个示例所解决的问题更好地组织它们,同时为您提供更简便的途径来找到符合需求的示例。

目前,共有四个类别。在示例代码仓库中,每个类别都有其对应的目录,目录名称即为类别名称:

基础示例 (samples/basic)

这是一个很好的入门起点。这里的示例在技术上有针对性,展示了配置和代码方面的最基本实现。这些示例通过介绍Spring Integration以及企业集成模式(EIP)的基本概念、API和配置,将帮助您快速入门。例如,如果您正在寻找如何实现服务激活器并将其连接到消息通道、如何使用消息网关作为消息交换的门面,或者如何开始使用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)

本类别面向那些对消息驱动架构和企业集成模式有良好理解,并对 Spring 和 Spring Integration 有较高认知的开发者和架构师,他们希望找到解决特定业务问题的示例。换言之,本类别示例的重点在于业务用例,以及如何通过消息驱动架构,特别是 Spring Integration 来解决这些用例。例如,如果您想了解如何使用 Spring Integration 实现和自动化贷款经纪人或旅行社流程,这里正是寻找此类示例的正确地方。

important

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

示例实现

目前,Spring Integration 提供了相当多的示例,并且未来还会不断增加。为了帮助您更好地浏览这些示例,每个示例都附有自己的 readme.txt 文件,其中涵盖了关于该示例的若干细节(例如,它涉及哪些 EIP 模式、试图解决什么问题、如何运行示例以及其他细节)。然而,某些示例需要更详细且有时是图形化的解释。在本节中,您可以找到我们认为需要特别关注的示例的详细信息。

贷款经纪人

本节介绍包含在 Spring Integration 示例中的贷款经纪人示例应用程序。该示例灵感来源于 Gregor Hohpe 和 Bobby Woolf 合著的《企业集成模式》一书中的示例。

下图展示了整个流程:

loan broker eip

图 1. 贷款经纪人示例

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

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

  • 核心过滤器及过滤器与管道协作的模式(消息路由器、拆分器与聚合器、多种消息转换模式等)

EIP 书籍的第 9 章详细描述了此用例的细节和变体,以下是简要总结:在寻找最佳贷款报价时,消费者订阅了贷款经纪人的服务,该服务处理以下细节:

  • 消费者预筛选(例如,获取并审查消费者的信用历史)

  • 确定最合适的银行(例如,基于消费者的信用历史或信用评分)

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

  • 收集每家银行的回复

  • 根据消费者的要求筛选回复并确定最佳报价

  • 将贷款报价返回给消费者

获取贷款报价的实际过程通常更为复杂。然而,由于我们的目标是展示企业集成模式在Spring Integration中是如何实现和实施的,因此这个用例被简化了,仅专注于流程中的集成方面。这并非试图在消费者财务方面为您提供建议。

通过聘请贷款经纪人,消费者无需了解贷款经纪人运营的具体细节,并且每位贷款经纪人的运营方式可能各不相同以保持竞争优势。因此,我们组装和实施的系统必须具备灵活性,以便能够快速且无痛地引入任何变更。

备注

贷款经纪人示例实际上并未与任何“虚构的”银行或信用机构进行通信。这些服务已被模拟替代。

我们的目标在于整合、编排并测试整个流程的集成环节。唯有如此,我们才能开始考虑将这些流程与真实服务对接。届时,无论特定贷款经纪商与多少家银行合作,或使用何种通信媒介(或协议)(如JMS、WS、TCP等)与这些银行通信,已组装的流程及其配置都将保持不变。

设计

在分析前面列出的六个需求时,你会发现它们都属于集成关注点。例如,在消费者预筛选步骤中,我们需要收集关于消费者及其需求的额外信息,并用额外的元数据来丰富贷款请求。接着,我们必须筛选这些信息以选择最合适的银行列表等等。丰富、筛选和选择都属于集成关注点,而 EIP 以模式的形式为这些问题定义了解决方案。Spring Integration 则提供了这些模式的实现。

下图展示了消息网关的表示形式:

gateway

图 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 元素负责构建初始消息,它本身就具备用任意消息头来增强新创建消息的能力。在我们的示例中,每当调用 getBestQuote() 方法时,我们会添加一个值为 BESTRESPONSE_TYPE 消息头;而对于其他方法,则不添加任何消息头。现在,我们可以在下游检查该消息头是否存在,并根据其存在与否及其值来确定调用者期望的回复类型。

根据用例,我们还知道需要执行一些预筛选步骤,例如获取并评估消费者的信用评分,因为一些顶级银行只接受满足最低信用评分要求的消费者的报价请求。因此,如果在将消息转发给银行之前,能够用此类信息丰富消息内容,那将非常有益。此外,如果需要完成多个流程来提供此类元信息,将这些流程分组到一个单元中也会很有帮助。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则,选择要发送报价请求的消息通道(银行通道)列表。

组合消息处理器

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

下图展示了链式模式:

chain

图 3. 链

上图显示我们有一个包含内部头部增强器元素的链,该元素进一步用 CREDIT_SCORE 头部及其值(通过调用信用服务确定——这是一个由 'creditBureau' 名称标识的简单 POJO Spring Bean)来丰富消息内容。然后,它将消息委托给消息路由器。

下图展示了消息路由模式:

银行路由器

图 4. 消息路由器

Spring Integration 提供了消息路由模式的多种实现。在本例中,我们使用一个路由器,该路由器通过评估一个表达式(使用 Spring 表达式语言)来确定通道列表。该表达式会查看信用评分(在前一步骤中确定),并根据信用评分的值,从 idbanksMap Bean 中选择值为 premiersecondary 的通道列表。一旦选定通道列表,消息就会被路由到这些通道。

现在,贷款经纪人还需要从银行接收贷款报价,按消费者进行汇总(我们不希望将一个消费者的报价展示给另一个消费者),根据消费者的选择标准(单一最佳报价或所有报价)组装响应,并将回复发送给消费者。

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

quotes aggregator

图 5. 消息聚合器

聚合器模式描述了一个端点,它将相关消息分组为单个消息。可以提供标准和规则来确定聚合和关联策略。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 中展示的另一个示例。

领域是咖啡馆,下图描绘了基本流程:

cafe eip

图 6. Cafe 示例

Order 对象可能包含多个 OrderItem。订单一旦下达,拆分器会将复合订单消息分解为每个饮品的独立消息。随后,路由器通过检查 OrderItem 对象的 isIced 属性来判断饮品是热饮还是冷饮,并对每条消息进行相应处理。Barista 负责制作每杯饮品,但热饮和冷饮的制作由两个不同的方法处理:prepareHotDrinkprepareColdDrink。制作完成的饮品随后被送至 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>

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

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),分别对应订单项被路由到的两个通道。以下清单展示了包含 prepareHotDrinkprepareColdDrink 方法的 Barista 类:

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