Spring Integration 示例
从 Spring Integration 2.0 开始,Spring Integration 发行版不再包含示例。相反,我们切换到了一种更简单的协作模式,这应该能促进更好的社区参与,并且理想情况下会有更多的贡献。示例现在有一个专门的 GitHub 仓库。示例开发也有自己的生命周期,这个生命周期不依赖于框架发布的生命周期,尽管出于兼容性考虑,该仓库仍然会在每个主要版本发布时被打上标签。
对社区的最大好处是我们现在可以添加更多示例,并立即提供给您,而无需等待下一个版本。 拥有自己独立于实际框架的 GitHub 仓库也是一个很大的优势。 现在您有了一个专门的地方来建议新的示例,以及报告现有示例的问题。 您还可以通过 Pull Request 向我们提交示例。 如果我们认为您的示例具有附加值,我们将非常乐意将其添加到 'samples' 仓库中,并正确地将您署名为作者。
在哪里获取示例
如果您需要更多关于如何安装或使用 Git 的信息,请访问:https://git-scm.com/。
要使用 Git 命令行工具克隆(检出)Spring Integration 示例仓库,请发出以下命令:
$ git clone https://github.com/spring-projects/spring-integration-samples.git
前面的命令会将整个 samples 仓库克隆到你在其中执行该 git
命令的工作目录下的一个名为 spring-integration-samples
的目录中。由于 samples 仓库是一个活跃的仓库,你可能需要定期拉取(更新)以获取新的示例和现有示例的更新。为此,请执行以下 git pull
命令:
$ git pull
提交样本或样本请求
你可以提交新的样本和样本请求。我们非常感谢任何改进样本的努力,包括分享好的想法。
我如何贡献自己的示例?
GitHub 用于社交编程:如果你想向 Spring Integration Samples 项目提交自己的代码示例,我们鼓励通过从这个仓库的 forks 提交 pull requests 的方式来贡献。如果你想以这种方式贡献代码,请尽可能引用一个提供有关你示例详细信息的 GutHub issue。
签署贡献者许可协议
非常重要:在我们接受您的 Spring Integration 示例之前,您需要签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议并不会授予任何人对主仓库的提交权限,但这意味着我们可以接受您的贡献,并且如果接受了您的贡献,您将获得作者署名。为了阅读和签署 CLA,请访问:
support.springsource.com/spring_committer_signup
从 Project 下拉菜单中,选择 Spring Integration。项目负责人是 Artem Bilan。
代码贡献流程
对于实际的代码贡献过程,请阅读 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 的基本概念、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 的书籍 Enterprise Integration Patterns 中介绍的一个示例的启发。
下图显示了整个过程:
图 1. 贷款经纪人示例
在一个 EIP 架构的核心是管道、过滤器和当然还有:消息这些非常简单但强大的概念。端点(过滤器)通过渠道(管道)相互连接。生产端点将消息发送到渠道,消费端点则获取消息。这种架构旨在定义各种描述端点之间信息交换机制,而不必了解这些端点是什么或它们在交换什么信息。因此,它提供了一种非常松散耦合且灵活的合作模型,同时将集成关注点从业务关注点中解耦。EIP 通过进一步定义以下内容扩展了此架构:
-
管道的类型(点对点通道、发布订阅通道、通道适配器等)
-
核心过滤器以及过滤器与管道协作的方式(消息路由器、拆分器和聚合器、各种消息转换模式等)
《EIP》这本书的第 9 章很好地描述了这个用例的细节和变化,但这里是一个简要总结:在寻找最佳贷款报价时,消费者订阅了一家贷款经纪人的服务,该经纪人处理诸如以下的细节:
请注意,原文中未列出具体细节内容,如果需要更详细的信息,建议查阅《EIP》书籍的第 9 章。
-
消费者预筛选(例如,获取和审查消费者的 信用记录)
-
确定最合适的银行(例如,基于消费者的 信用记录 或评分)
-
向每个选定的银行发送贷款报价请求
-
收集每个银行的回复
-
过滤回复并确定最佳报价,基于消费者的要求。
-
将贷款报价传递给消费者。
获得贷款报价的实际过程通常要复杂一些。然而,由于我们的目标是演示Enterprise Integration Patterns 在 Spring Integration 中是如何实现和实施的,所以用例被简化了,只专注于过程的集成方面。这并不是试图给你提供消费者金融方面的建议。
通过聘请贷款经纪人,消费者被隔离于贷款经纪人的操作细节之外,并且每个贷款经纪人的操作可能各不相同,以保持竞争优势。因此,我们组装和实施的任何系统都必须具有灵活性,以便能够快速且轻松地引入任何变更。
贷款经纪人示例实际上并不与任何“想象中的”银行或信用机构进行通信。这些服务是存根。
我们的目标是组装、协调和测试整个过程的集成方面。只有这样,我们才能开始考虑将这些流程连接到实际的服务。在那时,组装的过程及其配置不会改变,无论是一个特定的贷款经纪人与多少家银行打交道,或者使用何种通信媒体(或协议)(JMS、WS、TCP 等)与这些银行进行通信。
设计
当你分析前面列出的six requirements时,可以看到它们都是集成方面的问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者的更多信息以及消费者的愿望,并用额外的元信息丰富贷款请求。然后我们必须过滤这些信息以选择最合适的银行列表,等等。丰富、过滤和选择都是EIP以模式形式定义解决方案的集成问题。Spring Integration 提供了这些模式的实现。
下图显示了消息网关的表示:
图 2. 消息网关
消息网关模式提供了一种访问消息系统的简单机制,包括我们的贷款经纪人。在 Spring Integration 中,你可以将网关定义为一个普通的 Java 接口(你不需要提供实现),使用 XML <gateway>
元素或用 Java 中的注解进行配置,并像使用其他任何 Spring bean 一样使用它。Spring Integration 负责通过生成消息(有效负载映射到方法的输入参数)并将其发送到指定的通道来委托和映射方法调用到消息基础结构。以下示例展示了如何用 XML 定义这样的网关:
<int:gateway id="loanBrokerGateway" service-interface="org.example.LoanBrokerGateway">
<int:method name="submitLoanRequest" request-channel="loanRequests"/>
</int:gateway>
请注意,上面的代码片段是 XML 配置的一个例子,它定义了一个名为 loanBrokerGateway
的网关,该网关实现了 org.example.LoanBrokerGateway
接口,并指定了 submitLoanRequest
方法的请求通道为 loanRequests
。
<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>
我们当前的网关提供了两种可以调用的方法。一种是返回最佳单一报价,另一种是返回所有报价。 somehow,下游需要知道调用者需要什么类型的回复。在消息传递架构中实现这一点的最佳方式是通过一些描述您意图的元数据来丰富消息的内容。内容丰富器是解决此问题的模式之一。Spring Integration 为了方便起见,提供了一个单独的配置元素,用于使用任意数据丰富消息头(稍后描述)。但是,由于 gateway
元素负责构建初始消息,它包括能够使用任意消息头丰富新创建的消息。在我们的示例中,每当调用 getBestQuote()
方法时,我们添加一个值为 BEST
的 RESPONSE_TYPE
头。对于其他方法,我们不添加任何头。现在我们可以在下游检查此头是否存在。根据其存在及其值,我们可以确定调用者想要什么类型的回复。
基于用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些 首席 银行只接受符合最低信用评分要求的消费者的报价请求。因此,在消息转发给银行之前,如果能用此类信息丰富消息会很好。另外,如果当需要完成多个流程以提供此类元信息时,可以将这些流程组合成一个单元,那也会很好。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则选择要发送报价请求的消息渠道(银行渠道)列表。
组合消息处理器
组合消息处理器模式描述了构建端点的规则,这些端点保持对消息流的控制,而消息流由多个消息处理器组成。在 Spring Integration 中,组合消息处理器模式由 <chain>
元素实现。
下图显示了链式模式:
图 3. 链
前面的图像显示我们有一个链,其中包含一个内部的 header-enricher 元素,该元素通过 CREDIT_SCORE
头和值(该值由调用信用服务确定——一个简单的名为 'creditBureau' 的 POJO Spring bean)进一步丰富消息的内容。然后它委托给消息路由器。
下图显示了消息路由模式:
图 4. 消息路由器
Spring Integration 提供了多种消息路由模式的实现。在本例中,我们使用一个路由器,它根据评估一个表达式(用 Spring 表达式语言编写)来确定一个渠道列表,该表达式查看信用评分(在上一步中确定),并从 id
为 banks
的 Map
bean 中选择值为 premier
或 secondary
的渠道列表,这取决于信用评分的值。一旦选择了渠道列表,消息就会被路由到这些渠道。
现在,贷款经纪人需要做的最后一件事是接收银行的贷款报价,根据消费者进行汇总(我们不希望将一个消费者的报价显示给另一个消费者),根据消费者的 selection criteria (单个最佳报价或所有报价)组装响应,并将回复发送给消费者。
下图显示了消息聚合器模式:
图 5. 消息聚合器
聚合器模式描述了一个将相关消息组合成单个消息的终点。可以提供标准和规则来确定聚合和关联策略。Spring Integration 提供了聚合器模式的多种实现以及基于命名空间的便捷配置。
以下示例展示了如何定义一个聚合器:
<int:aggregator id="quotesAggregator"
input-channel="quotesAggregationChannel"
method="aggregateQuotes">
<beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>
我们的 Loan Broker 使用 <aggregator>
元素定义了一个 'quotesAggregator' bean,它提供了一种默认的聚合和关联策略。默认的关联策略根据 correlationId
头来关联消息(参见 EIP 书籍中的关联标识符模式)。请注意,我们从未为此头提供值。它是在路由器生成每个银行渠道的单独消息时自动设置的。
一旦消息相关联,它们就会被释放到实际的聚合器实现中。虽然 Spring Integration 提供了一个默认的聚合器,但其策略(收集所有消息的有效载荷列表并构造一个以该列表作为有效载荷的新消息)并不能满足我们的需求。将所有结果放在一条消息中是一个问题,因为我们的消费者可能只需要一个最优报价或全部报价。为了传达消费者的意图,在此过程的早期我们设置了 RESPONSE_TYPE
头。现在我们必须评估这个头,并返回所有的报价(默认的聚合策略适用)或最优报价(默认的聚合策略不适用,因为我们必须确定哪个贷款报价是最优的)。
在更现实的应用中,选择最佳报价可能基于复杂的条件,这可能会影响聚合器实现和配置的复杂性。不过,目前我们保持简单。如果消费者想要最佳报价,我们就选择利率最低的报价。为此,LoanQuoteAggregator
类按利率对所有报价进行排序,并返回第一个。LoanQuote
类实现了 Comparable
接口,以根据 rate 属性比较报价。一旦响应消息创建完成,它就会被发送到启动此过程的消息网关的默认回复通道(因此也是发送给消费者)。我们的消费者获得了贷款报价!
总之,基于 POJO (即现有或遗留)逻辑和轻量级、可嵌入的消息传递框架(Spring Integration),并采用松耦合的编程模型,组装了一个相当复杂的过程,旨在简化异构系统的集成,而无需重型 ESB 类似引擎或专有开发和部署环境。作为开发人员,您不应该因为有集成方面的需求,而需要将 Swing 或控制台应用程序移植到 ESB 类似的服务器或实现专有接口。
本示例和本节中的其他示例都是基于 Enterprise Integration Patterns 构建的。你可以将它们视为你解决方案的“构建块”。它们并不是完整的解决方案。集成问题存在于所有类型的应用程序中(无论是基于服务器的还是其他)。我们的目标是使应用程序的集成不需要对设计、测试和部署策略进行更改。
Cafe 示例
本节介绍了包含在 Spring Integration 示例中的咖啡馆示例应用程序。此示例受到 Gregor Hohpe 的 Ramblings 中介绍的另一个示例的启发。
领域是咖啡馆,下图描绘了基本流程:
图 6. Cafe 示例
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。以下示例展示了 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
实例,但使用不同的方法(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"/>
另外,请注意每次调用都会显示工作线程名称。你可以看到,热饮是由 task-executor 线程准备的。如果你提供一个更短的轮询间隔(例如 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"/>