Spring Integration Samples

从 Spring Integration 2.0 开始,Spring Integration 分发不再包括示例。相反,我们已经切换到一个更简单的协作模型,该模型应促进更多的社区参与,理想情况下也应促进更多的贡献。示例现在有一个专门的 GitHub 存储库。示例开发也有自己的生命周期,该生命周期不依赖于框架版本的生命周期,但出于兼容性原因,存储库仍然标记有每个主要版本。

As of Spring Integration 2.0, the Spring Integration distribution no longer includes the samples. Instead, we have switched to a much simpler collaborative model that should promote better community participation and, ideally, more contributions. Samples now have a dedicated GitHub repository. Sample development also has its own lifecycle, which is not dependent on the lifecycle of the framework releases, although the repository is still tagged with each major release for compatibility reasons.

社区的巨大好处是,我们现在可以添加更多示例,并立即向你提供这些示例,而无需等待下一个版本。拥有一个独立于实际框架的 GitHub 存储库也是一大好处。你现在拥有一个专门的地方来建议示例以及报告现有示例的问题。你还可以将示例作为拉取请求提交给我们。如果我们认为你的示例很有价值,我们将非常乐意将其添加到“示例”存储库中,并正确注明你是作者。

The great benefit to the community is that we can now add more samples and make them available to you right away without waiting for the next release. Having its own GitHub repository that is not tied to the actual framework is also a great benefit. You now have a dedicated place to suggest samples as well as report issues with existing samples. You can also submit a sample to us as a Pull Request. If we believe your sample adds value, we would be more than glad to add it to the 'samples' repository, properly crediting you as the author.

Where to Get Samples

Spring Integration 样本项目托管在 GitHub。为了签出或克隆样本,您必须在您的系统上安装一个 Git 客户端。许多平台上有好几个基于 GUI 的产品可供使用(例如 Eclipse IDE 的 EGit)。Google 上进行简单搜索可以帮助您找到它们。您还可以使用 Git的命令行界面。

The Spring Integration Samples project is hosted on GitHub. In order to check out or clone the samples, you must have a Git client installed on your system. There are several GUI-based products available for many platforms (such as EGit for the Eclipse IDE). A simple Google search can help you find them. You can also use the command line interface for Git.

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

If you need more information on how to install or use Git, visit: https://git-scm.com/.

要使用 Git 命令行工具克隆(检出)Spring Integration 样例存储库,请发出以下命令:

To clone (check out) the Spring Integration samples repository by using the Git command line tool, issue the following command:

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

前面的命令将整个示例存储库克隆到在发出该 git 命令时所在的目录中名为 spring-integration-samples 的目录中。由于示例存储库是动态存储库,因此你可能希望定期进行拉取(更新)以获取新示例和对现有示例的更新。要执行此操作,请发出以下 git pull 命令:

The preceding command clones the entire samples repository into a directory named spring-integration-samples within the working directory where you issued that git command. Since the samples repository is a live repository, you might want to perform periodic pulls (updates) to get new samples and updates to the existing samples. To do so, issue the following git pull command:

$ git pull

Submitting Samples or Sample Requests

你可以提交新示例和示例请求。我们非常感谢各位对增强示例所做的努力,包括分享好点子。

You can submit both new samples and requests for samples. We greatly appreciate any effort toward improving the samples, including the sharing of good ideas.

How Can I Contribute My Own Samples?

GitHub 可用于社交编码:如果您希望向 Spring Integration Samples 项目提交自己的代码样例,我们鼓励通过提交至该存储库的 pull requests做出贡献。如果您希望通过这种方式贡献代码,请尽可能参考 GutHub issue,以提供有关您样例的一些详细信息。

GitHub is for social coding: if you want to submit your own code examples to the Spring Integration Samples project, we encourage contributions through pull requests from forks of this repository. If you want to contribute code this way, please reference, if possible, a GutHub issue that provides some details regarding your sample.

Example 1. Sign the contributor license agreement

非常重要:在我们接受你的 Spring Integration 样例前,你需要签署 SpringSource 贡献者许可协议 (CLA)。签署贡献者协议不会向任何人授予对主存储库的提交权限,但意味着我们可以接受你的贡献,如果你做出了贡献,你将获得创作者荣誉。要阅读并签署 CLA,请转到:

Very important: Before we can accept your Spring Integration sample, we need you to sign the SpringSource contributor license agreement (CLA). Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. In order to read and sign the CLA, go to:

项目 下拉菜单中,选择 Spring Integration。项目负责人是 Artem Bilan。

From the Project drop down, select Spring Integration. The Project Lead is Artem Bilan.

Code Contribution Process

如需了解实际的代码贡献流程,请阅读 Spring Integration 的贡献者指南。它们也适用于样例项目。您可以在 [role="bare"][role="bare"]https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md中找到它们。

For the actual code contribution process, read the Contributor Guidelines for Spring Integration. They apply for the samples project as well. You can find them at [role="bare"]https://github.com/spring-projects/spring-integration/blob/main/CONTRIBUTING.md

此流程可确保对每次提交进行同行评审。事实上,核心提交者遵循完全相同的规则。我们非常期待收到你的 Spring Integration 样例!

This process ensures that every commit gets peer-reviewed. As a matter of fact, the core committers follow the exact same rules. We gratefully look forward to your Spring Integration samples!

Sample Requests

正如 mentioned earlier 所述,春季集成样本项目将 GitHub 问题用作 Bug 跟踪系统。若要提交新的样本请求,请访问 [role="bare"][role="bare"]https://github.com/spring-projects/spring-integration-samples/issues.

As mentioned earlier, the Spring Integration Samples project uses GitHub issue as bug tracking system. To submit new sample requests, visit [role="bare"]https://github.com/spring-projects/spring-integration-samples/issues.

Samples Structure

从 Spring Integration 2.0 开始,示例结构已发生更改。随着更多示例的规划,我们意识到并非所有示例都具有相同的目标。它们共同的目标是向你展示如何应用和使用 Spring Integration 框架。但是,它们之间的区别在于,一些示例注重技术用例,而另一些则注重业务用例。此外,一些示例用于展示可用于解决某些场景(技术和业务)的各种技术。示例的新分类使我们能够根据每个示例所解决的问题更好地组织示例,同时为你提供更简单的方法来查找适合你需求的正确示例。

Starting with Spring Integration 2.0, the structure of the samples has changed. With plans for more samples, we realized that not all samples have the same goals. They all share the common goal of showing you how to apply and work with the Spring Integration framework. However, they differ in that some samples concentrate on a technical use case, while others focus on a business use case. Also, some samples are about showcasing various techniques that could be applied to address certain scenarios (both technical and business). The new categorization of samples lets us better organize them based on the problem each sample addresses while giving you a simpler way of finding the right sample for your needs.

目前有四类。在示例存储库中,每类都有自己的目录,其名称以类名命名:

Currently, there are four categories. Within the samples repository, each category has its own directory, which is named after the category name:

Basic (samples/basic)

This is a good place to get started. The samples here are technically motivated and demonstrate the bare minimum with regard to configuration and code. These should help you to get started quickly by introducing you to the basic concepts, API, and configuration of Spring Integration as well as Enterprise Integration Patterns (EIP). For example, if you are looking for an answer on how to implement and wire a service activator to a message channel, how to use a messaging gateway as a facade to your message exchange, or how to get started with MAIL, TCP/UDP or other modules, this is the right place to find a good sample. The bottom line is samples/basic is a good place to get started.

Intermediate (samples/intermediate)

This category targets developers who are already familiar with the Spring Integration framework (beyond getting started) but need some more guidance while resolving the more advanced technical problems they might encounter after switching to a messaging architecture. For example, if you are looking for an answer on how to handle errors in various message exchange scenarios or how to properly configure the aggregator for a situation where some messages do not ever arrive for aggregation, or any other issue that goes beyond a basic implementation and configuration of a particular component and exposes “what else” types of problems, this is the right place to find these type of samples.

Advanced (samples/advanced)

This category targets developers who are very familiar with the Spring Integration framework but are looking to extend it to address a specific custom need by using Spring Integration’s public API. For example, if you are looking for samples showing you how to implement a custom channel or consumer (event-based or polling-based) or you are trying to figure out the most appropriate way to implement a custom bean parser on top of the Spring Integration bean parser hierarchy (perhaps when implementing your own namespace and schema for a custom component), this is the right place to look. Here you can also find samples that will help you with adapter development. Spring Integration comes with an extensive library of adapters to let you connect remote systems with the Spring Integration messaging framework. However, you might need to integrate with a system for which the core framework does not provide an adapter. If so, you might decide to implement your own (please consider contributing it). This category would include samples showing you how.

Applications (samples/applications)

This category targets developers and architects who have a good understanding of message-driven architecture and EIP and an above-average understanding of Spring and Spring Integration who are looking for samples that address a particular business problem. In other words, the emphasis of the samples in this category is business use cases and how they can be solved with a message-driven architecture and Spring Integration in particular. For example, if you want to see how a loan broker or travel agent process could be implemented and automated with Spring Integration, this is the right place to find these types of samples.

Spring Integration 是一个社区驱动框架。因此,社区参与非常重要。这包括样例。如果您找不到所需内容,请告诉我们。

Spring Integration is a community-driven framework. Therefore, community participation is IMPORTANT. That includes samples. If you cannot find what you are looking for, let us know!

Samples

目前,Spring Integration 提供了许多示例,你只能期待更多。为了帮助你更好地浏览它们,每个示例都带有一个自己的 readme.txt 文件,其中涵盖了有关该示例的几个详细信息(例如,它涉及哪些 EIP 模式、它试图解决什么问题、如何运行该示例以及其他详细信息)。但是,某些示例需要更详细且有时需要图形化的解释。在本节中,你可以找到我们认为需要特别关注的示例的详细信息。

Currently, Spring Integration comes with quite a few samples, and you can only expect more. To help you better navigate through them, each sample comes with its own readme.txt file which covers several details about the sample (for example, what EIP patterns it addresses, what problem it is trying to solve, how to run the sample, and other details). However, certain samples require a more detailed and sometimes graphical explanation. In this section, you can find details on samples that we believe require special attention.

Loan Broker

本部分涵盖了包含在 Spring Integration 样例中的贷款经纪样例应用程序。此样例受到 Gregor Hohpe 和 Bobby Woolf 的书籍 Enterprise Integration Patterns中展示的一个样例的启发。

This section covers the loan broker sample application that is included in the Spring Integration samples. This sample is inspired by one of the samples featured in Gregor Hohpe and Bobby Woolf’s book, Enterprise Integration Patterns.

下图显示了整个过程:

The following diagram shows the entire process:

loan broker eip
Figure 1. Loan Broker Sample

EIP 体系结构的核心是管道、过滤器以及当然的消息等非常简单但强大的概念。端点(过滤器)通过通道(管道)相互连接。生成端点将消息发送到通道,并且消耗端点检索消息。此体系结构用于定义各种机制,描述如何在端点之间交换信息,而无需了解这些端点是什么或它们交换什么信息。因此,它提供了一种非常松散耦合且灵活的协作模型,同时还将集成问题与业务问题解耦。EIP 通过进一步定义来扩展此体系结构:

At the core of an EIP architecture are the very simple yet powerful concepts of pipes, filters, and, of course: messages. Endpoints (filters) are connected with one another via channels (pipes). Producing endpoints send messages to the channel, and the consuming endpoint retrieves the messages. This architecture is meant to define various mechanisms that describe how information is exchanged between the endpoints, without any awareness of what those endpoints are or what information they are exchanging. Thus, it provides for a very loosely coupled and flexible collaboration model while also decoupling integration concerns from business concerns. EIP extends this architecture by further defining:

  • The types of pipes (point-to-point channel, publish-subscribe channel, channel adapter, and others)

  • The core filters and patterns around how filters collaborate with pipes (Message router, splitters and aggregators, various message transformation patterns, and others)

EIP 书籍的第 9 章很好地描述了此用例的详细信息和变化,但以下是简要摘要:在购买最佳贷款报价时,消费者订阅贷款经纪人的服务,贷款经纪人负责处理诸如以下详细信息:

Chapter 9 of the EIP book nicely describes the details and variations of this use case, but here is the brief summary: While shopping for the best loan quote, a consumer subscribes to the services of a loan broker, which handles such details as:

  • Consumer pre-screening (for example, obtaining and reviewing the consumer’s Credit history)

  • Determining the most appropriate banks (for example, based on the consumer’s credit history or score)

  • Sending a loan quote request to each selected bank

  • Collecting responses from each bank

  • Filtering responses and determining the best quotes, based on consumer’s requirements.

  • Pass the Loan quotes back to the consumer.

获得贷款报价的实际过程通常会更复杂一些。但是,由于我们的目标是展示如何在 Spring Integration 中实现企业集成模式,因此该用例已简化为仅专注于该过程的集成方面。这不是企图为你提供消费者金融方面的建议。

The real process of obtaining a loan quote is generally a bit more complex. However, since our goal is to demonstrate how Enterprise Integration Patterns are realized and implemented within Spring Integration, the use case has been simplified to concentrate only on the integration aspects of the process. It is not an attempt to give you advice in consumer finances.

通过聘请贷款经纪人,消费者与贷款经纪人运营的详细信息隔离开来,并且每个贷款经纪人的运营可能彼此不同以保持竞争优势,因此我们组装和实施的任何内容都必须灵活,以便可以快速而轻松地引入任何更改。

By engaging a loan broker, the consumer is isolated from the details of the loan broker’s operations, and each loan broker’s operations may defer from one another to maintain competitive advantage, so whatever we assemble and implement must be flexible so that any changes could be introduced quickly and painlessly.

贷款经济人样例没有真正与任何“虚拟”银行或信用局交流。这些服务已经被存根。

The loan broker sample does not actually talk to any 'imaginary' Banks or Credit bureaus. Those services are stubbed out.

我们的目标是组装、编排和测试整个流程的集成方面。只有这样,我们才能开始考虑将此类流程连接至实际服务。那时,组装的流程及其配置不会因特定的贷款经纪人处理的银行数量或与这些银行通信所使用的通信媒体(或协议)(JMS、WS、TCP 等)而改变。

Our goal here is to assemble, orchestrate, and test the integration aspects of the process as a whole. Only then can we start thinking about wiring such processes to the real services. At that time, the assembled process and its configuration do not change regardless of the number of banks with which a particular loan broker deals or the type of communication media (or protocols) used (JMS, WS, TCP, and so on) to communicate with these banks.

Design

当你分析前面列出的 six requirements 时,你可以看出这些都是集成问题。例如,在消费者预筛选步骤中,我们需要收集有关消费者的附加信息以及消费者的愿望,并使用附加元信息丰富贷款申请。然后,我们必须筛选此类信息,以选择最合适的银行列表,依此类推。丰富、筛选和选择都是 EIP 定义为模式而解决的集成问题。Spring 集成提供了这些模式的实现。

As you analyze the six requirements listed earlier, you can see that they are all integration concerns. For example, in the consumer pre-screening step, we need to gather additional information about the consumer and the consumer’s desires and enrich the loan request with additional meta-information. We then have to filter such information to select the most appropriate list of banks and so on. Enrich, filter, and select are all integration concerns for which EIP defines a solution in the form of patterns. Spring Integration provides an implementation of these patterns.

下图显示了消息网关的表示:

The following image shows a representation of a messaging gateway:

gateway
Figure 2. Messaging Gateway

消息网关模式提供了一个简单的机制来访问消息系统,包括我们的贷款经纪人。在 Spring Integration 中,你可以将网关定义为一个普通的旧 Java 接口(你不必提供实现),使用 XML <gateway> 元素或 Java 中的注释对其进行配置,并像使用任何其他 Spring bean 一样使用它。Spring Integration 通过生成消息(有效负载映射到方法的输入参数)并将其发送到指定的通道,来负责将方法调用委托和映射到消息基础设施。以下示例演示了如何使用 XML 定义此类网关:

The messaging gateway pattern provides a simple mechanism to access messaging systems, including our loan broker. In Spring Integration, you can define the gateway as a plain old java interface (you need not provide an implementation), configure it with the XML <gateway> element or with an annotation in Java, and use it as you would any other Spring bean. Spring Integration takes care of delegating and mapping method invocations to the messaging infrastructure by generating a message (the payload is mapped to an input parameter of the method) and sending it to the designated channel. The following example shows how to define such a gateway with 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() 方法时,我们都会添加一个 RESPONSE_TYPE 头,其值为 BEST。对于其他方法,我们不添加任何头。现在,我们可以在下游检查此头的存在。基于它的存在及其值,我们可以确定调用者想要哪种类型的答复。

Our current gateway provides two methods that could be invoked. One that returns the best single quote and another one that returns all quotes. Somehow, downstream, we need to know what type of reply the caller needs. The best way to achieve this in messaging architecture is to enrich the content of the message with some metadata that describes your intentions. Content Enricher is one of the patterns that addresses this. Spring Integration does, as a convenience, provide a separate configuration element to enrich message headers with arbitrary data (described later) However, since the gateway element is responsible for constructing the initial message, it includes ability to enrich the newly created message with arbitrary message headers. In our example, we add a RESPONSE_TYPE header with a value of BEST whenever the getBestQuote() method is invoked. For other methods, we do not add any header. Now we can check downstream for the existence of this header. Based on its presence and its value, we can determine what type of reply the caller wants.

根据用例,我们还知道需要执行一些预筛选步骤,例如获取和评估消费者的信用评分,因为一些高级银行只接受满足最低信用评分要求的消费者的报价请求。所以,在将消息转发到银行之前,用此类信息丰富消息将很好。同样,如果需要完成多个流程来提供此类元信息,那么最好将这些流程分组到一个单元中。在我们的用例中,我们需要确定信用评分,并根据信用评分和某些规则选择一个消息通道列表(银行通道),以便向其发送报价请求。

Based on the use case, we also know that some pre-screening steps need to be performed, such as getting and evaluating the consumer’s credit score, because some premiere banks only accept quote requests from consumers that meet a minimum credit score requirement. So it would be nice if the message would be enriched with such information before it is forwarded to the banks. It would also be nice if, when several processes need to be completed to provide such meta-information, those processes could be grouped in a single unit. In our use case, we need to determine the credit score and, based on the credit score and some rule, select a list of message channels (bank channels) to which to send quote request.

Composed Message Processor

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

The composed message processor pattern describes rules around building endpoints that maintain control over message flow, which consists of multiple message processors. In Spring Integration, the composed message processor pattern is implemented by the <chain> element.

下图显示了链模式:

The following image shows the chain pattern:

chain
Figure 3. Chain

上图显示我们有一个链,其中有一个内部标头富集器元素,该元素使用 CREDIT_SCORE 标头和值(由对信用服务—由“creditBureau”名称标识的一个简单的 POJO spring bean 的调用确定)进一步丰富消息的内容。然后它委托给消息路由器。

The preceding image shows that we have a chain with an inner header-enricher element that further enriches the content of the message with the CREDIT_SCORE header and the value (which is determined by the call to a credit service — a simple POJO spring bean identified by 'creditBureau' name). Then it delegates to the message router.

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

The following image shows the message router pattern:

bank router
Figure 4. Message Router

Spring Integration 提供了消息路由模式的多个实现。在这种情况下,我们使用一个路由器,该路由器根据评估一个表达式(在 Spring 表达式语言中)来确定渠道列表,该表达式查看信用评分(在上一步中确定的)并选择来自 Map Bean 的渠道列表,该 Bean 的 idbanks,其值为 premiersecondary,基于信用评分。一旦选择了渠道列表,消息就会被路由到那些渠道。

Spring Integration offers several implementations of the message routing pattern. In this case, we use a router that determines a list of channels based on evaluating an expression (in Spring Expression Language) that looks at the credit score (determined in the previous step) and selects the list of channels from the Map bean with an id of banks whose values are premier or secondary, based on the value of credit score. Once the list of channels is selected, the message is routed to those channels.

现在,贷款经纪人需要接收银行的贷款报价的最后一件事,按消费者聚合它们(我们不想向一个消费者展示另一个消费者的报价),根据消费者的选择标准(单个最佳报价或所有报价)组装响应,并将答复发送给消费者。

Now, one last thing the loan broker needs to receive the loan quotes form the banks, aggregate them by consumer (we do not want to show quotes from one consumer to another), assemble the response based on the consumer’s selection criteria (single best quote or all quotes) and send the reply to the consumer.

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

The following image shows the message aggregator pattern:

quotes aggregator
Figure 5. Message Aggregator

聚合器模式描述了一个将相关消息分组到单个消息的端点。可以提供条件和规则来确定聚合和关联策略。Spring Integration 提供了聚合器模式的多种实现以及方便的基于命名空间的配置。

An aggregator pattern describes an endpoint that groups related messages into a single message. Criteria and rules can be provided to determine an aggregation and correlation strategy. Spring Integration provides several implementations of the aggregator pattern as well as a convenient namespace-based configuration.

以下示例演示如何定义聚合器:

The following example shows how to define an aggregator:

<int:aggregator id="quotesAggregator"
      input-channel="quotesAggregationChannel"
      method="aggregateQuotes">
  <beans:bean class="org.springframework.integration.samples.loanbroker.LoanQuoteAggregator"/>
</int:aggregator>

我们的贷款经纪人以 `<aggregator>`元素定义了“quotesAggregator”bean,该元素提供默认聚合和关联策略。默认关联策略基于 `correlationId`标头关联消息(参见 the correlation identifier pattern in the EIP book)。请注意,我们从未提供此标头的值。路由器在为每个银行通道生成单独消息时,会自动提前设置此值。

Our Loan Broker defines a 'quotesAggregator' bean with the <aggregator> element, which provides a default aggregation and correlation strategy. The default correlation strategy correlates messages based on the correlationId header (see the correlation identifier pattern in the EIP book). Note that we never provided the value for this header. It was automatically set earlier by the router, when it generated a separate message for each bank channel.

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

Once the messages are correlated, they are released to the actual aggregator implementation. Although Spring Integration provides a default aggregator, its strategy (gather the list of payloads from all messages and construct a new message with this list as its payload) does not satisfy our requirement. Having all the results in the message is a problem, because our consumer might require a single best quote or all quotes. To communicate the consumer’s intention, earlier in the process we set the RESPONSE_TYPE header. Now we have to evaluate this header and return either all the quotes (the default aggregation strategy would work) or the best quote (the default aggregation strategy does not work because we have to determine which loan quote is the best).

在更实际的应用程序中,选择最佳报价可能基于复杂的条件,这些条件可能会影响聚合器实现和配置的复杂性。不过,现在我们把它简单化。如果消费者想要最佳报价,我们将选择利率最低的报价。为了实现这一点,LoanQuoteAggregator 类根据利率对所有报价进行排序并返回第一个报价。LoanQuote 类实现了 Comparable,以根据速率属性对报价进行比较。创建响应消息后,它将被发送到启动该过程的消息网关的默认答复通道(因此,发送给消费者)。我们的消费者拿到了贷款报价!

In a more realistic application, selecting the best quote might be based on complex criteria that might influence the complexity of the aggregator implementation and configuration. For now, though, we are making it simple. If the consumer wants the best quote, we select a quote with the lowest interest rate. To accomplish that, the LoanQuoteAggregator class sorts all the quotes by interest rate and returns the first one. The LoanQuote class implements Comparable to compare quotes based on the rate attribute. Once the response message is created, it is sent to the default reply channel of the messaging gateway (and, thus, to the consumer) that started the process. Our consumer got the loan quote!

总之,组装了一个相当复杂的流程,该流程基于 POJO(即现有或旧版)逻辑和一个轻量级的可嵌入消息框架(Spring Integration),该框架采用松散耦合的编程模型,旨在简化异构系统的集成,而无需重量级 ESB 类似引擎或专有开发和部署环境。作为一名开发人员,你不需要将你的 Swing 或基于控制台的应用程序移植到 ESB 类似服务器或实现专有接口,仅仅是因为你有一个集成问题。

In conclusion, a rather complex process was assembled based on POJO (that is existing or legacy) logic and a light-weight, embeddable messaging framework (Spring Integration) with a loosely coupled programming model intended to simplify integration of heterogeneous systems without requiring a heavy-weight ESB-like engine or a proprietary development and deployment environment. As a developer, you should not need to port your Swing or console-based application to an ESB-like server or implement proprietary interfaces just because you have an integration concern.

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

This sample and the other samples in this section are built on top of Enterprise Integration Patterns. You can consider them to be “building blocks” for your solution. They are not intended to be complete solutions. Integration concerns exist in all types of application (whether server-based or not). Our goal is to make is so that integrating applications does not require changes in design, testing, and deployment strategy.

The Cafe Sample

本部分涵盖了包含在 Spring 集成示例中的咖啡店示例应用程序。这个示例的灵感来自于 Gregor Hohpe 的 Ramblings中的另一个示例。

This section covers the cafe sample application that is included in the Spring Integration samples. This sample is inspired by another sample featured in Gregor Hohpe’s Ramblings.

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

The domain is that of a cafe, and the following diagram depicts the basic flow:

cafe eip
Figure 6. Cafe Sample

Order 对象可能包含多个 OrderItems。一旦下订单,分隔器将复合订单消息分解为每种饮品的一条消息。然后由路由器处理每条消息,路由器确定饮品是热饮还是冷饮(通过检查 OrderItem 对象的“isIced”属性)。Barista 准备每种饮品,但热饮和冷饮准备由两种不同的方法处理:“prepareHotDrink”和“prepareColdDrink”。然后将准备好的饮品发送到 Waiter,在那里将它们聚合到 Delivery 对象中。

The Order object may contain multiple OrderItems. Once the order is placed, a splitter breaks the composite order message into a single message for each drink. Each of these is then processed by a router that determines whether the drink is hot or cold (by checking the OrderItem object’s 'isIced' property). The Barista prepares each drink, but hot and cold drink preparation are handled by two distinct methods: 'prepareHotDrink' and 'prepareColdDrink'. The prepared drinks are then sent to the Waiter where they are aggregated into a Delivery object.

以下列表显示 XML 配置:

The following listing shows the XML configuration:

<?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。以下示例显示分隔器:

Each message endpoint connects to input channels, output channels, or both. Each endpoint manages its own lifecycle (by default, endpoints start automatically upon initialization, to prevent that, add the auto-startup attribute with a value of false). Most importantly, notice that the objects are simple POJOs with strongly typed method arguments. The following example shows the Splitter:

public class OrderSplitter {
    public List<OrderItem> split(Order order) {
        return order.getItems();
    }
}

对于路由器,返回值不必是 MessageChannel 实例(虽然可以是)。在此示例中,取而代之的是返回包含通道名称的 String 值,如下表所示。

In the case of the router, the return value does not have to be a MessageChannel instance (although, it can be). In this example, a String value that holds the channel name is returned instead, as the following listing shows.

public class DrinkRouter {
    public String resolveOrderItemChannel(OrderItem orderItem) {
        return (orderItem.isIced()) ? "coldDrinks" : "hotDrinks";
    }
}

现在,返回 XML,可以看到有两个 <service-activator> 元素。每个元素都委托给相同的 Barista 实例,但采用不同的方法(prepareHotDrinkprepareColdDrink),每个方法对应于订单项已路由到的两个通道之一。以下列表显示 Barista 类(其中包含 prepareHotDrinkprepareColdDrink 方法)

Now, turning back to the XML, you can see that there are two <service-activator> elements. Each of these is delegating to the same Barista instance but with different methods (prepareHotDrink or prepareColdDrink), each corresponding to one of the two channels where order items have been routed. The following listing shows the Barista class (which contains the prepareHotDrink and prepareColdDrink methods)

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 注解上,如下接口定义所示:

As you can see from the preceding code excerpt, the Barista methods have different delays (the hot drinks take five times as long to prepare). This simulates work being completed at different rates. When the CafeDemo 'main' method runs, it loops 100 times and sends a single hot drink and a single cold drink each time. It actually sends the messages by invoking the 'placeOrder' method on the Cafe interface. In the earlier XML configuration, you can see that the <gateway> element is specified. This triggers the creation of a proxy that implements the given service interface and connects it to a channel. The channel name is provided on the @Gateway annotation of the Cafe interface, as the following interface definition shows:

public interface Cafe {

    @Gateway(requestChannel="orders")
    void placeOrder(Order order);

}

最后,看看 CafeDemo 本身的 main() 方法:

Finally, have a look at the main() method of the CafeDemo itself:

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(正如 the beginning of this chapter 中描述的)。

To run this sample as well as eight others, refer to the README.txt within the samples directory of the main distribution (as described at the beginning of this chapter).

运行 cafeDemo 时,可以看到冷饮最初准备得比热饮快。由于有一个聚合器,因此冷饮实际上受到热饮准备速率的限制。基于它们各自 1000 毫秒和 5000 毫秒的延迟,这是可以预料的。但是,通过使用并发任务执行器配置轮询器,可以极大地改变结果。例如,可以为热饮咖啡师使用具有五个工作线程的线程池执行器,同时保持冷饮咖啡师不变。以下清单配置了此类安排:

When you run cafeDemo, you can see that the cold drinks are initially prepared more quickly than the hot drinks. Because there is an aggregator, the cold drinks are effectively limited by the rate of the hot drink preparation. This is to be expected, based on their respective delays of 1000 and 5000 milliseconds. However, by configuring a poller with a concurrent task executor, you can dramatically change the results. For example, you could use a thread pool executor with five workers for the hot drink barista while keeping the cold drink barista as it is. The following listing configures such an arrangement:

<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 毫秒),可以看到它偶尔会通过强制任务调度程序(调用程序)调用操作来限制输入。

Also, notice that the worker thread name is displayed with each invocation. You can see that the hot drinks are prepared by the task-executor threads. If you provide a much shorter poller interval (such as 100 milliseconds), you can see that it occasionally throttles the input by forcing the task scheduler (the caller) to invoke the operation.

除了测试轮询的并发设置外,您还可以添加“事务性”子元素,然后在上下文中引用任何 PlatformTransactionManager 实例。

In addition to experimenting with the poller’s concurrency settings, you can also add the 'transactional' child element and then refer to any PlatformTransactionManager instance within the context.

The XML Messaging Sample

basic/xml 中的 XML 消息示例演示如何使用处理 XML 负载的一些提供的组件。该示例使用处理表示为 XML 的图书订单的想法。

The XML messaging sample in basic/xml shows how to use some of the provided components that deal with XML payloads. The sample uses the idea of processing an order for books represented as XML.

此示例表明命名空间前缀可以是您想要的任何内容。虽然通常会使用 int-xml 集成 XML 组件,但示例却使用了 si-xml。(int“Integration” 的简称,而 si“Spring Integration” 的简称。)

This sample shows that the namespace prefix can be whatever you want. While we usually use, int-xml for integration XML components, the sample uses si-xml. (int is short for “Integration”, and si is short for “Spring Integration”.)

首先,订单将拆分为多条消息,每条消息代表 XPath 分隔器组件中的单个订单项。以下列表显示分隔器的配置:

First, the order is split into a number of messages, each one representing a single order item from the XPath splitter component. The following listing shows the configuration of the splitter:

<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

A service activator then passes the message into a stock checker POJO. The order item document is enriched with information from the stock checker about the order item stock level. This enriched order item message is then used to route the message. In the case where the order item is in stock, the message is routed to the warehouse. The following listing configures the xpath-router that routes the messages:

<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 转换器:

When the order item is not in stock, the message is transformed with XSLT into a format suitable for sending to the supplier. The following listing configures the XSLT transformer:

<si-xml:xslt-transformer input-channel="outOfStockChannel"
  output-channel="resupplyOrderChannel"
  xsl-resource="classpath:org/springframework/integration/samples/xml/bigBooksSupplierTransformer.xsl"/>