# Spring 积分样本 ## Spring 积分样本 从 Spring 积分 2.0 开始, Spring 积分分布不再包括样本。相反,我们转向了一种简单得多的协作模式,这种模式应该促进更好的社区参与,并在理想情况下促进更多的贡献。样例现在有了一个专门的 GitHub 存储库。样例开发也有自己的生命周期,它不依赖于框架版本的生命周期,尽管出于兼容性的原因,存储库仍然被标记为每个主要版本。 对社区的最大好处是,我们现在可以添加更多的示例,并使它们可以立即提供给你,而无需等待下一个版本。拥有自己的 GitHub 存储库,而不是与实际框架绑定,也是一个很大的好处。你现在有一个专门的地方建议样品以及报告问题的现有样品。你也可以提交一个样品给我们作为一个拉的要求。如果我们相信你的样例增加了价值,那么我们将比 GLAD 更多地将其添加到“样例”存储库中,正确地将你视为作者。 ### 哪里可以得到样品 Spring Integration Samples 项目托管在[GitHub](https://github.com/spring-projects/spring-integration-samples/)上。为了签出或复制示例,你必须在系统上安装一个 Git 客户机。有几种基于 GUI 的产品可用于许多平台(例如[EGit](https://eclipse.org/egit/)用于 Eclipse IDE)。一个简单的谷歌搜索就能帮你找到它们。你也可以使用[Git](https://git-scm.com/)的命令行接口。 | |如果你需要更多关于如何安装或使用 Git 的信息,请访问:[https://git-scm.com/](https://git-scm.com/)。| |---|---------------------------------------------------------------------------------------------------------------| 要使用 Git 命令行工具复制(签出) Spring 集成样例存储库,请发出以下命令: ``` $ git clone https://github.com/spring-projects/spring-integration-samples.git ``` 前面的命令将整个样例存储库复制到一个名为`spring-integration-samples`的目录中,该目录位于发出`git`命令的工作目录中。由于样例存储库是一个活动的存储库,因此你可能希望执行周期性的拉动(更新)来获取新的样例和对现有样例的更新。为此,发出以下`git pull`命令: ``` $ git pull ``` ### 提交样品或样品请求 你既可以提交新的样品,也可以要求提供样品。我们非常感谢为改进样本所做的任何努力,包括分享好的想法。 #### 我如何贡献我自己的样本? GitHub 是用于社交编码的:如果你想向 Spring 集成示例项目提交自己的代码示例,我们鼓励你通过该存储库的[拉请求](https://help.github.com/en/articles/creating-a-pull-request/)来贡献自己的代码。如果你想以这种方式贡献代码,请尽可能参考提供有关示例的一些详细信息的[GutHub 问题](https://github.com/spring-projects/spring-integration-samples/issues)。 | |签署贡献者许可协议

非常重要:在我们可以接受你的 Spring 集成示例之前,我们需要你签署 SpringSource 贡献者许可协议,
签署贡献者协议并不授予任何人对主存储库的提交权利,但这确实意味着我们可以接受你的贡献,如果我们接受的话,你将获得一个作者学分。
为了阅读并签署 CLA,请转到:



从**项目**下拉,选择**Spring Integration**。
项目负责人是 Artem Bilan。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| #### 代码贡献过程 对于实际的代码贡献过程,请阅读 Spring 集成的贡献者指南。他们也申请样品项目。你可以在[https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md](https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md)找到它们。 这一过程确保了每一次提交都得到同行评审。事实上,核心提交者遵循完全相同的规则。我们非常感激地期待你的 Spring 集成样品! #### 请求示例 由于[前面提到过](#samples-how-can-i-contribute), Spring Integration Samples 项目使用 GitHub Issue 作为缺陷跟踪系统。要提交新的示例请求,请访问[https://github.com/spring-projects/spring-integration-samples/issues](https://github.com/spring-projects/spring-integration-samples/issues)。 ### 样本结构 从 Spring Integration2.0 开始,样本的结构发生了变化。有了更多样品的计划,我们意识到并不是所有的样品都有相同的目标。它们都有一个共同的目标,那就是向你展示如何应用和使用 Spring 集成框架。然而,它们的不同之处在于,一些示例专注于技术用例,而另一些则专注于业务用例。此外,一些示例展示了可以应用于解决某些场景(技术和业务)的各种技术。新的样本分类使我们能够更好地根据每个样本所解决的问题来组织它们,同时为你提供了一种更简单的方法来为你的需求找到合适的样本。 目前有四个类别。在样例存储库中,每个类别都有自己的目录,该目录以类别名称命名: 基本(`samples/basic`) 这是一个开始的好地方。这里的示例是出于技术上的考虑,并演示了有关配置和代码的最小值。通过向你介绍 Spring 集成的基本概念、API 和配置以及 Enterprise 集成模式,这些将帮助你快速入门。例如,如果你正在寻找关于如何实现服务激活器并将其连接到消息通道的答案,那么如何使用消息网关作为消息交换的门面,或者如何开始使用 Mail、TCP/UDP 或其他模块,这是找到好样品的好地方。底线是`samples/basic`是一个开始的好地方。 中间值(`samples/intermediate`) 这一类别的目标开发人员已经熟悉 Spring 集成框架(除了入门),但在解决切换到消息传递体系结构后可能遇到的更高级技术问题时需要一些更多的指导。例如,如果你正在寻找关于如何处理各种消息交换场景中的错误的答案,或者在某些消息永远不会到达聚合的情况下如何正确配置聚合器,或者超出了特定组件的基本实现和配置的任何其他问题,并暴露了“还有什么”类型的问题,这是找到这些类型的示例的正确位置。 高级(`samples/advanced`) 这一类别的开发人员非常熟悉 Spring 集成框架,但希望通过 Spring 集成的公共 API 对其进行扩展,以满足特定的自定义需求。例如,如果你正在寻找对于向你展示如何实现自定义通道或使用者(基于事件或基于轮询)的示例,或者你正在尝试找出在 Spring 集成 Bean 解析器层次结构之上实现自定义 Bean 解析器的最合适方法(可能在实现你自己的名称空间时)和自定义组件的模式),这是正确的位置。在这里,你还可以找到一些示例,这些示例将帮助你进行适配器开发。 Spring 集成附带了一个广泛的适配器库,以使你能够将远程系统与 Spring 集成消息传递框架连接起来。但是,你可能需要与核心框架不提供适配器的系统集成。如果是这样,你可能会决定实现你自己的(请考虑贡献它)。这个类别将包括向你展示如何使用的示例。 应用程序(`samples/applications`) 这个类别的目标是那些对消息驱动体系结构和 EIP 有很好理解的开发人员和架构师,以及对 Spring 和 Spring 集成有高于平均水平的理解的开发人员和架构师,他们正在寻找解决特定业务问题的示例。换句话说,这个类别中的示例的重点是业务用例,以及如何使用消息驱动的体系结构,特别是 Spring 集成来解决它们。例如,如果你想了解如何通过 Spring 集成来实现贷款经纪人或旅行代理人流程并使其自动化,那么在这里就可以找到这些类型的示例。 | |Spring 集成是一个社区驱动的框架。
因此社区参与是很重要的。
这包括了样本。
如果你找不到你想要的东西,请告诉我们!| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ### 样本 目前, Spring 集成有相当多的示例,你只能期待更多的示例。为了帮助你更好地浏览它们,每个示例都有自己的`readme.txt`文件,其中包含有关示例的几个详细信息(例如,它处理的 EIP 模式,它试图解决的问题,如何运行示例,以及其他详细信息)。然而,某些示例需要更详细的、有时是图形的解释。在本节中,你可以找到我们认为需要特别注意的样品的详细信息。 #### 贷款经纪人 本节涵盖了 Spring 集成示例中包含的 Loan Broker 示例应用程序。这个样本的灵感来自 Gregor Hohpe 和 Bobby Woolf 的书[*Enterprise 整合模式 *](https://www.enterpriseintegrationpatterns.com/)中的一个样本。 下图显示了整个过程: ![贷款经纪人 EIP](images/loan-broker-eip.png) 图 1。贷款经纪人样本 EIP 架构的核心是非常简单但功能强大的管道、过滤器概念,当然还有:消息。端点(过滤器)通过通道(管道)相互连接。产生端点将消息发送到通道,并且使用端点检索消息。该体系结构旨在定义各种机制,这些机制描述了端点之间如何交换信息,而不需要了解这些端点是什么或它们正在交换什么信息。因此,它提供了一个非常松散耦合和灵活的协作模型,同时还将集成关注点与业务关注点分离开来。EIP 通过进一步定义以下内容扩展了该体系结构: * 管道的类型(点对点通道、发布订阅通道、通道适配器和其他) * 围绕过滤器如何与管道协作的核心过滤器和模式(消息路由器、分配器和聚合器、各种消息转换模式,以及其他) EIP 书的第 9 章很好地描述了这个用例的细节和变化,但这里有一个简短的总结:在购买最佳贷款报价时,消费者订阅了贷款经纪人的服务,该经纪人处理以下细节: * 消费者预筛选(例如,获取和审查消费者的信用记录) * 确定最合适的银行(例如,基于消费者的信用记录或得分) * 向每个选定的银行发送贷款报价请求 * 收集每家银行的回复 * 根据消费者的需求过滤响应并确定最佳报价。 * 将贷款报价传回给消费者。 获得贷款报价的实际过程通常要复杂一些。然而,由于我们的目标是演示如何在 Spring 集成中实现和实现 Enterprise 集成模式,因此用例已被简化为仅关注过程的集成方面。这并不是试图在消费理财方面给你建议。 通过聘请贷款经纪人,消费者与贷款经纪人的操作细节隔离,并且每个贷款经纪人的操作可能会相互延迟以保持竞争优势,因此,无论我们组装和实现什么,都必须具有灵活性,以便可以快速而轻松地引入任何更改。 | |贷款经纪人的样本实际上并不会与任何“想象中的”银行或征信机构进行对话。
那些服务都会被剔除。| |---|-----------------------------------------------------------------------------------------------------------------------------| 我们的目标是将流程的集成方面作为一个整体进行组装、编排和测试。只有到那时,我们才能开始考虑将这样的过程连接到真正的服务上。那时,无论与特定贷款经纪人进行交易的银行的数量,或者与这些银行进行通信所使用的通信媒体(或协议)的类型(JMS、WS、TCP 等),组装的过程及其配置都不会发生变化。 ##### 设计 在分析前面列出的[六项要求](#samples-loan-broker-requirements)时,可以看到它们都是集成问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者和消费者愿望的额外信息,并用额外的元信息丰富贷款请求。然后我们必须过滤这些信息,以选择最合适的银行名单等等。Enrich、Filter 和 Select 都是集成关注点,EIP 以模式的形式为其定义了解决方案。 Spring 集成提供了这些模式的实现方式。 下图显示了消息传递网关的表示: ![gateway](images/gateway.jpg) 图 2。消息传递网关 消息传递网关模式提供了一种访问消息传递系统(包括我们的贷款代理)的简单机制。在 Spring 集成中,可以将网关定义为普通的旧 Java 接口(不需要提供实现),使用 XML``元素或 Java 注释对其进行配置,并像使用其他任何方法一样使用它 Spring Bean。 Spring 集成通过生成消息(将有效负载映射到方法的输入参数)并将其发送到指定的信道,负责将方法调用委派和映射到消息传递基础设施。下面的示例展示了如何使用 XML 定义这样的网关: ``` ``` 我们当前的网关提供了两个可以调用的方法。一个是返回最好的单个报价,另一个是返回所有报价。不知何故,在下游,我们需要知道呼叫者需要哪种类型的答复。在消息传递体系结构中实现这一点的最佳方法是使用描述意图的一些元数据来丰富消息的内容。Content Enricher 是解决这一问题的模式之一。 Spring 集成确实,作为一种便利,提供了一个单独的配置元素来用任意数据(在后面描述)丰富消息头,但是,由于`gateway`元素负责构造初始消息,因此它包括用任意消息头丰富新创建的消息的能力。在我们的示例中,每当调用`getBestQuote()`方法时,我们都会添加一个`RESPONSE_TYPE`头,其值为`BEST`。对于其他方法,我们不添加任何标头。现在,我们可以检查下游是否存在此标头。根据它的存在和它的值,我们可以确定呼叫者想要哪种类型的答复。 根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些一流的银行只接受满足最低信用评分要求的消费者的报价请求。因此,如果在将信息转发给银行之前,能够用这些信息丰富信息,那就太好了。如果在需要完成几个过程以提供这样的元信息时,可以将这些过程组合在一个单元中,那也是很好的。在我们的用例中,我们需要确定信用评分,并根据信用评分和一些规则,选择要向其发送报价请求的消息通道(银行通道)列表。 ##### 组合消息处理器 组合消息处理器模式描述了围绕构建端点的规则,这些端点维护对消息流的控制,消息流由多个消息处理器组成。在 Spring 集成中,组合消息处理器模式是由``元素实现的。 下图显示了链状图案: ![chain](images/chain.png) 图 3。链条 前面的图片显示,我们有一个带有内部 header-enricher 元素的链,该元素通过`CREDIT_SCORE`header 和值(由对信用服务的调用决定)进一步丰富了消息的内容——一个简单的 POJO Spring Bean 由“CreditBureau”名称标识)。然后,它将委托给消息路由器。 下图显示了消息路由器模式: ![银行路由器](images/bank-router.jpg) 图 4。消息路由器 Spring 集成提供了消息路由模式的几种实现方式。在这种情况下,我们使用一个路由器,该路由器根据表达式的求值来确定一个通道列表(在 Spring 表达式语言中)它查看信用评分(在上一步中确定),并根据信用评分的值从`Map` Bean 中选择具有`id`的`banks`的通道列表,其值为`premier`或`secondary`。一旦通道列表被选中,消息将被路由到这些通道。 现在,贷款经纪人需要做的最后一件事是从银行收到贷款报价,并按客户汇总(我们不想显示从一个客户到另一个客户的报价),根据消费者的选择标准(单个最佳报价或所有报价)组装响应,并将响应发送给消费者。 下图显示了消息聚合器模式: ![报价聚合器](images/quotes-aggregator.jpg) 图 5。消息聚合器 聚合器模式描述将相关消息分组为单个消息的端点。可以提供标准和规则来确定聚合和相关策略。 Spring 集成提供了聚合器模式的几种实现方式以及方便的基于名称空间的配置。 下面的示例展示了如何定义聚合器: ``` ``` 我们的贷款经纪人使用``元素定义了一个’quotesaggregator’ Bean,该元素提供了一个默认的聚合和关联策略。默认的相关策略基于`correlationId`头来关联消息(参见[EIP 书中的相关标识符模式](https://www.enterpriseintegrationpatterns.com/patterns/messaging/CorrelationIdentifier.html))。请注意,我们从未提供此标头的值。当路由器为每个银行通道生成一条单独的消息时,它会在更早的时候自动设置。 一旦消息相互关联,它们就会被释放到实际的聚合器实现中。 Spring 尽管集成提供了一个默认的聚合器,但其策略(从所有消息中收集有效负载的列表,并以该列表作为其有效负载构建一个新的消息)不满足我们的需求。在消息中包含所有结果是一个问题,因为我们的消费者可能需要一个最佳报价或所有报价。为了传达消费者的意图,在此过程的早期,我们设置了`RESPONSE_TYPE`标头。现在,我们必须评估这个标头,并返回所有报价(默认聚合策略将起作用)或最佳报价(默认聚合策略不起作用,因为我们必须确定哪个贷款报价是最好的)。 在更现实的应用程序中,选择最佳报价可能基于复杂的标准,这些标准可能会影响聚合器实现和配置的复杂性。不过,就目前而言,我们正在简化这一过程。如果消费者想要最好的报价,我们就选择利率最低的报价。为了实现这一点,`LoanQuoteAggregator`类按利率对所有报价进行排序,并返回第一个报价。`LoanQuote`类实现`Comparable`以基于 rate 属性比较引号。一旦创建了响应消息,就会将其发送到启动该过程的消息传递网关的默认响应通道(因此也发送给消费者)。我们的消费者得到了贷款报价! 总之,基于 POJO(即现有的或遗留的)逻辑和一个轻量级的流程组装了一个相当复杂的流程,具有松耦合编程模型的可嵌入消息框架( Spring Integration)旨在简化异构系统的集成,而不需要重的类 ESB 引擎或专有的开发和部署环境。作为开发人员,你不应该仅仅因为担心集成问题就需要将 Swing 或基于控制台的应用程序移植到类似 ESB 的服务器或实现专有接口。 此示例和本节中的其他示例是在 Enterprise 集成模式的基础上构建的。你可以将它们视为解决方案的“基石”。它们并不是完整的解决方案。集成问题存在于所有类型的应用程序中(无论是否基于服务器)。我们的目标是使集成应用程序不需要更改设计、测试和部署策略。 #### 咖啡馆样本 本节涵盖 Spring 集成示例中包含的 CAFE 示例应用程序。这个样本的灵感来自 Gregor Hohpe 的[Ramblings](https://www.enterpriseintegrationpatterns.com/ramblings.html)中的另一个样本。 这个域是一个咖啡馆的域,下图描述了基本流程: ![cafe eip](images/cafe-eip.png) 图 6。咖啡厅样本 `Order`对象可以包含多个`OrderItems`。一旦下了订单,拆分器就会将复合订单消息分解为针对每种饮料的单个消息。然后,每一种都由路由器处理,该路由器确定饮料是热的还是冷的(通过检查`OrderItem`对象的“isiced”属性)。`Barista`为每一种饮料做准备,但冷热饮料的准备工作有两种不同的方法:“准备好的饮料”和“准备好的冷饮”。然后将准备好的饮料发送到`Waiter`,在那里它们被聚集到`Delivery`对象中。 下面的清单显示了 XML 配置: ``` ``` 每个消息端点连接到输入通道、输出通道或两者。每个端点管理其自己的生命周期(默认情况下,端点在初始化时自动启动,为了防止这种情况,请添加值为`auto-startup`的`false`属性)。最重要的是,请注意,这些对象是带有强类型方法参数的简单 POJO。下面的示例展示了拆分器: ``` public class OrderSplitter { public List split(Order order) { return order.getItems(); } } ``` 在路由器的情况下,返回值不一定是`MessageChannel`实例(尽管可以是)。在这个示例中,返回了一个保存通道名的`String`值,如下面的清单所示。 ``` public class DrinkRouter { public String resolveOrderItemChannel(OrderItem orderItem) { return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks"; } } ``` 现在,回到 XML,你可以看到有两个``元素。其中每一个都是委托给相同的`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`注释中提供,如下接口定义所示: ``` 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`(如[这一章的开头](#samples)中所述)。| |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 当你运行`cafeDemo`时,你可以看到冷饮的初始准备速度比热饮更快。因为存在聚合器,所以冷饮被有效地限制在热饮准备的速率中。这是可以预期的,基于它们各自的 1000 和 5000 毫秒的延迟。然而,通过配置具有并发任务执行器的 Poller,你可以显著地更改结果。例如,你可以使用一个线程池执行器,其中有五名工作人员负责热饮咖啡师,同时保持冷饮咖啡师的原样。下面的列表配置了这样的安排: ``` ``` 另外,请注意,每次调用都会显示 worker 线程名。你可以看到热饮是由任务执行器线程准备的。如果你提供的 Poller 间隔要短得多(例如 100 毫秒),那么你可以看到它偶尔会通过强制任务调度器(调用者)调用操作来限制输入。 | |除了尝试 Poller 的并发设置外,你还可以添加“事务”子元素,然后在上下文中引用任何`PlatformTransactionManager`实例。| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| #### XML 消息传递示例 `basic/xml`中的 XML 消息传递示例展示了如何使用处理 XML 有效负载的一些提供的组件。该示例使用了处理以 XML 表示的图书订单的思想。 | |这个示例显示了名称空间前缀可以是任何你想要的。
虽然我们通常使用`int-xml`来表示集成 XML 组件,但是示例使用`si-xml`。
(`int`是“集成”的缩写,`si`是“ Spring 集成”的缩写。)| |---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 首先,将订单拆分为许多消息,每个消息代表来自 XPath 拆分器组件的单个订单项。下面的清单显示了分割器的配置: ``` ``` 然后,服务激活器将消息传递到股票检查器 POJO。订单项目文档中添加了来自库存检查器的有关订单项目库存级别的信息。然后使用这个经过增强的订单项消息来路由该消息。在订单项有库存的情况下,消息被路由到仓库。下面的清单配置了发送消息的`xpath-router`: ``` ``` 当订单项不在库存中时,将使用 XSLT 将消息转换为适合发送给供应商的格式。下面的清单配置了 XSLT 转换器: ``` ```