Spring Integration 示例

自 Spring Integration 2.0 起,Spring Integration 发行版不再包含示例。相反,我们转而采用一种更简单的协作模型,这种模型应能促进更好的社区参与,并且理想情况下能带来更多贡献。示例现在有专门的 GitHub 仓库。示例开发也有自己的生命周期,它不依赖于框架版本的生命周期,尽管出于兼容性考虑,该仓库仍会针对每个主要版本进行标记。

社区的巨大好处是,我们现在可以添加更多示例并立即提供给您,而无需等待下一个版本。拥有一个不依赖于实际框架的独立 GitHub 仓库也是一个巨大好处。您现在有了一个专门的地方来建议示例以及报告现有示例的问题。您还可以通过 Pull Request 向我们提交示例。如果我们认为您的示例有价值,我们将非常乐意将其添加到 'samples' 仓库中,并妥善署名您为作者。

何处获取示例

Spring Integration Samples 项目托管在 GitHub 上。要签出 (check out) 或克隆 (clone) 示例,您的系统必须安装 Git 客户端。许多平台都有基于 GUI 的产品可用(例如,Eclipse IDE 的 EGit)。简单的 Google 搜索可以帮助您找到它们。您也可以使用 Git 的命令行界面。

如果您需要关于如何安装或使用 Git 的更多信息,请访问:https://git-scm.cn/

要使用 Git 命令行工具克隆 (check out) Spring Integration 示例仓库,请执行以下命令:

$ git clone https://github.com/spring-projects/spring-integration-samples.git

前面的命令将整个示例仓库克隆到您执行该 `git` 命令的工作目录下的一个名为 `spring-integration-samples` 的目录中。由于示例仓库是一个活跃的仓库,您可能希望定期执行拉取 (pull) 操作(更新)以获取新示例和现有示例的更新。为此,请执行以下 `git pull` 命令:

$ git pull

提交示例或示例请求

您可以提交新示例和示例请求。我们非常感谢任何改进示例的努力,包括分享好的想法。

我如何贡献自己的示例?

GitHub 适用于社交编码:如果您想将自己的代码示例提交到 Spring Integration Samples 项目,我们鼓励通过从此仓库的分叉创建拉取请求 (pull requests) 来贡献。如果您想通过这种方式贡献代码,请尽可能引用一个GitHub issue,其中提供有关您示例的一些详细信息。

签署贡献者许可协议

非常重要:在我们接受您的 Spring Integration 示例之前,您需要签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议并不赋予主仓库的提交权限,但这意味着我们可以接受您的贡献,并且如果接受,您将获得作者署名。要阅读并签署 CLA,请访问:

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 的书 企业集成模式 中介绍的一个示例。

下图展示了整个流程:

loan broker eip
图 1. 贷款经纪人示例

EIP 架构的核心是非常简单但功能强大的概念:管道、过滤器,当然还有消息。端点(过滤器)通过通道(管道)相互连接。生产者端点将消息发送到通道,消费者端点检索消息。这种架构旨在定义各种机制来描述信息如何在端点之间交换,而不关心这些端点是什么或它们交换什么信息。因此,它提供了一个非常松散耦合和灵活的协作模型,同时也解耦了集成关注点和业务关注点。EIP 通过进一步定义以下内容来扩展这种架构:

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

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

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

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

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

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

  • 收集来自每个银行的响应

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

  • 将贷款报价返还给消费者。

获取贷款报价的实际流程通常更为复杂。然而,由于我们的目标是展示如何在 Spring Integration 中实现企业集成模式,用例已简化,仅关注流程的集成方面。这并不是提供消费者金融方面的建议。

通过与贷款经纪人合作,消费者与贷款经纪人操作的细节隔离开来,并且每个贷款经纪人的操作可能不同,以保持竞争优势,因此我们组装和实现的内容必须灵活,以便能够快速无痛地引入任何更改。

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

我们的目标是组装、编排和测试整个流程的集成方面。只有这样,我们才能开始考虑将此类流程连接到实际服务。那时,无论特定贷款经纪人与多少银行打交道,也无论用于与这些银行通信的通信媒介(或协议)(JMS、WS、TCP 等)是什么类型,组装好的流程及其配置都不会改变。

设计

当您分析前面列出的六个要求时,您可以看到它们都是集成关注点。例如,在消费者预筛查步骤中,我们需要收集关于消费者的更多信息以及消费者的愿望,并用额外的元信息丰富贷款请求。然后,我们必须筛选这些信息以选择最合适的银行列表等。丰富 (enrich)、筛选 (filter) 和选择 (select) 都是集成关注点,EIP 为此定义了模式形式的解决方案。Spring Integration 提供了这些模式的实现。

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

gateway
图 2. 消息网关

消息网关模式提供了一种访问消息系统的简单机制,包括我们的贷款经纪人。在 Spring Integration 中,您可以将网关定义为一个普通的 Java 接口(无需提供实现),使用 XML 的 `` 元素或 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()` 方法,我们都会添加一个 `RESPONSE_TYPE` 头,其值为 `BEST`。对于其他方法,我们不添加任何头。现在,我们可以在下游检查此头的存在性。根据它的存在和它的值,我们可以确定调用者需要哪种类型的回复。

基于用例,我们还知道需要执行一些预筛查步骤,例如获取和评估消费者的信用评分,因为一些一流银行只接受满足最低信用评分要求的消费者的报价请求。因此,如果在消息被转发到银行之前,能够用此类信息丰富消息,那就太好了。如果需要完成几个流程来提供此类元信息,并且这些流程可以分组到单个单元中,那就更好了。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则,选择一个消息通道列表(银行通道)来发送报价请求。

复合消息处理器

复合消息处理器模式描述了关于构建端点的规则,这些端点保持对由多个消息处理器组成的消息流的控制。在 Spring Integration 中,复合消息处理器模式由 `` 元素实现。

下图展示了链模式:

chain
图 3. 链

上图显示,我们有一个链,其中包含一个内部 header-enricher 元素,它进一步用 `CREDIT_SCORE` 头和值(由调用信用服务确定——一个简单的 POJO spring bean,由 'creditBureau' 名称标识)丰富消息的内容。然后,它委派给消息路由器。

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

bank router
图 4. 消息路由器

Spring Integration 提供了消息路由模式的几种实现。在这种情况下,我们使用一个路由器,它根据评估一个表达式(在 Spring Expression Language 中)来确定通道列表,该表达式查看信用评分(在前一步中确定)并从 id 为 `banks` 的 `Map` bean 中选择通道列表,其值根据信用评分的值为 `premier` 或 `secondary`。一旦通道列表被选中,消息就会被路由到这些通道。

现在,贷款经纪人需要完成的最后一件事是接收来自银行的贷款报价,按消费者聚合它们(我们不希望向一个消费者展示来自另一个消费者的报价),根据消费者的选择标准(单一最佳报价或所有报价)组装响应,并将回复发送给启动流程的消息网关(从而发送给消费者)。我们的消费者获得了贷款报价!

下图显示了消息聚合器模式

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,该 bean 提供默认的聚合和关联策略。默认的关联策略基于 correlationId 头部关联消息(参见 EIP 书中的关联标识符模式)。请注意,我们从未为此头部提供值。它是在路由器为每个银行通道生成单独消息时自动设置的。

一旦消息关联完成,它们就会被释放到实际的聚合器实现中。尽管 Spring Integration 提供了一个默认聚合器,但其策略(收集所有消息的 payload 列表并构建一个以该列表作为 payload 的新消息)不满足我们的需求。将所有结果放在一个消息中是一个问题,因为我们的消费者可能只需要一个最佳报价或所有报价。为了传达消费者的意图,我们在流程的早期设置了 RESPONSE_TYPE 头部。现在我们必须评估此头部并返回所有报价(默认聚合策略可以工作)或最佳报价(默认聚合策略不工作,因为我们必须确定哪个贷款报价是最好的)。

在一个更真实的应用程序中,选择最佳报价可能基于复杂的标准,这可能会影响聚合器实现和配置的复杂性。但现在,我们将其简化。如果消费者想要最佳报价,我们选择利率最低的报价。为此,LoanQuoteAggregator 类按利率属性对所有报价进行排序并返回第一个。LoanQuote 类实现了 Comparable 接口,以便根据利率属性比较报价。响应消息创建后,它将被发送到启动该流程的消息网关的默认回复通道(从而发送给消费者)。我们的消费者获得了贷款报价!

总之,基于 POJO(即现有或遗留)逻辑和轻量级、可嵌入的消息框架(Spring Integration)组装了一个相当复杂的流程,它采用松散耦合的编程模型,旨在简化异构系统的集成,而无需重量级 ESB 类引擎或专有的开发和部署环境。作为开发者,您不应该仅仅因为存在集成关注点,就不得不将您的 Swing 或控制台应用移植到 ESB 类服务器或实现专有接口。

本示例和本节中的其他示例都是基于企业集成模式构建的。您可以将它们视为您解决方案的“构建块”。它们并非旨在成为完整的解决方案。集成关注点存在于所有类型的应用中(无论是否基于服务器)。我们的目标是使应用集成不要求在设计、测试和部署策略上进行更改。

咖啡馆示例

本节涵盖了包含在 Spring Integration 示例中的咖啡馆示例应用。此示例的灵感来自 Gregor Hohpe 的 Ramblings 中介绍的另一个示例。

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

cafe eip
图 6. 咖啡馆示例

Order 对象可能包含多个 OrderItem。订单下达后,一个 splitter 将复合订单消息分解成针对每种饮品的单个消息。然后,路由器会处理这些消息,确定饮品是热的还是冷的(通过检查 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,具有强类型的方法参数。以下示例显示了 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),每个方法对应于订单项被路由到的两个通道之一。以下列表显示了 Barista 类(包含 prepareHotDrinkprepareColdDrink 方法)

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 毫秒。但是,通过配置一个带有并发任务执行器的 poller,您可以显著改变结果。例如,您可以为热饮咖啡师使用一个带有五个工作线程的线程池执行器,而保持冷饮咖啡师不变。以下列表配置了这样的安排

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

此外,请注意,每次调用时都会显示工作线程名称。您可以看到热饮是由任务执行器线程准备的。如果您提供一个更短的 poller 间隔(例如 100 毫秒),您可以看到它偶尔会通过强制任务调度器(调用者)调用操作来限制输入。

除了试验 poller 的并发设置外,您还可以添加 'transactional' 子元素,然后引用上下文中的任何 PlatformTransactionManager 实例。

XML 消息示例

位于 basic/xml 中的 XML 消息示例展示了如何使用处理 XML payload 的一些提供的组件。该示例使用了处理表示为 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"/>