|
如需使用最新稳定版本,请使用 Spring Integration 7.0.4! |
Spring Integration 示例
自 Spring Integration 2.0 起,Spring Integration 发行版不再包含示例代码。 取而代之的是,我们采用了一种更简单的协作模式,旨在促进更好的社区参与,并理想地带来更多贡献。 示例代码现在拥有独立的 GitHub 仓库。 示例开发也有其自身的生命周期,不依赖于框架版本的生命周期,尽管出于兼容性考虑,该仓库仍会为每个主要版本打上标签。
社区的一大好处是,我们现在可以添加更多示例并立即向您提供,而无需等待下一个版本发布。 拥有一个独立于实际框架本身的 GitHub 仓库也是一个巨大的优势。 您现在有了一个专门的场所来建议新示例以及报告现有示例的问题。 您也可以作为拉取请求(Pull Request)向我们提交您的示例。 如果我们认为您的示例具有价值,我们将非常乐意将其添加到 '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
上述命令将完整的示例仓库克隆到工作目录中名为spring-integration-samples的目录下,该工作目录是你发出git命令的位置。
由于示例仓库是一个实时更新的仓库,你可能希望定期执行拉取(更新)操作,以获取新的示例并更新现有示例。
为此,请执行以下git pull命令:
$ git pull
提交样本或样本请求
您可以提交新的示例代码或请求获取示例代码。 我们非常感谢您为改进示例代码所付出的任何努力,包括分享好的想法。
我如何贡献自己的示例?
GitHub 用于社会化编程:如果您希望向 Spring Integration Samples 项目提交自己的代码示例,我们鼓励通过从该仓库的 分支(forks) 发起 拉取请求(pull requests) 来贡献。 如果您希望通过这种方式贡献代码,请尽可能引用一个 GitHub 问题(issue),其中包含关于您示例的详细信息。
|
签署贡献者许可协议
非常重要:在我们接受您的 Spring Integration 示例之前,需要您签署 SpringSource 贡献者许可协议(CLA)。 签署贡献者协议并不会授予任何人主仓库的提交权限,但这意味着我们可以接受您的贡献,如果您被采纳,您将获得作者署名。 若要阅读并签署 CLA,请访问: 从Project下拉菜单中选择Spring Integration。 项目负责人是 Artem Bilan。 |
代码贡献流程
对于实际的代码贡献流程,请阅读 Spring Integration 的贡献者指南。 它们同样适用于 samples 项目。 您可以在以下地址找到它们: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 和配置。 例如,如果您正在寻找如何实现并将服务激活器连接到消息通道、如何使用消息网关作为消息交换的 facade,或者如何开始使用 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 中实现和落地企业集成模式(Enterprise Integration Patterns),该用例已被简化,仅专注于该流程的集成方面。 这并非旨在为您提供消费金融方面的建议。
通过聘请贷款经纪人,消费者与贷款经纪人的运营细节隔离开来,且每个贷款经纪人的运营可能各不相同以维持竞争优势,因此我们组装和实施的任何内容都必须灵活,以便能够快速无痛地引入任何更改。
| 贷款中介示例实际上并不与任何“虚拟”银行或信用局进行通信。 这些服务已被桩化模拟。 |
我们的目标在此是组装、编排并测试整个流程的集成方面。 只有这样,我们才能开始思考如何将此类流程连接到实际服务。 届时,无论特定贷款经纪人处理的银行数量多少,也无论用于与这些银行通信的通信媒介(或协议)类型如何(如 JMS、WS、TCP 等),所组装的流程及其配置都不会改变。
设计
当您分析前面列出的六个需求时,可以看到它们都是集成关注点。 例如,在消费者预筛选步骤中,我们需要收集关于消费者及其需求的额外信息,并用额外的元信息丰富贷款请求。 然后,我们必须过滤这些信息以选择最合适的银行列表等。 丰富、过滤和选择都是集成关注点,EIP 为此定义了以模式形式提供的解决方案。 Spring Integration 提供了这些模式的实现。
以下图像显示了一个消息网关的表示:<br/>
消息网关模式提供了一种简单的机制来访问消息系统,包括我们的贷款经纪系统。
在 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 Expression Language)来确定通道列表。该表达式会查看信用评分(在上一步中确定),并从 Map Bean 中选择值为 premier 或 secondary、id 为 banks 的通道列表,具体选择取决于信用评分的值。
一旦选定了通道列表,消息就会被路由到这些通道。
现在,贷款经纪人还需要做最后一件事:从银行接收贷款报价,按消费者进行聚合(我们不想将一个消费者的报价展示给另一个消费者),根据消费者的选择标准组装响应(单条最佳报价或所有报价),然后将回复发送给消费者。
下面的图像展示了消息聚合器模式:
聚合器模式描述了一个将相关消息组合成单条消息的端点。 可以提供标准和规则来确定聚合和相关策略。 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 的 随笔 中介绍的另一个示例。
该领域属于咖啡馆范畴,下图展示了基本流程:
The 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);
}
最后,查看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);
}
}
要运行此示例以及其他八个示例,请参考主发行版中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"/>