在这篇文章中,我们将介绍消息传递的核心概念,以及 Spring 框架及其姊妹项目提供的对各种消息传递类型的丰富支持。
什么是消息传递?为了最好地解释这一点,我将改写 Gregor Hohpe 和 Bobby Woolf(Addison Wesley,2004 年)的开创性著作《企业集成模式》中提供的示例。当您拨打电话时,您试图将信息传递给另一方。只有当您拨打电话时对方可用时,此操作才能成功。由于无法始终接听电话,因此我们使用语音邮件来排队消息。呼叫者在语音邮件中留言,然后呼叫接收者可以在稍后异步地检索该消息(或许多消息)。
在该示例中,语音邮件位于双方之间。它存储消息,然后在被叫方——接收方——检索时传递消息。在企业消息传递的世界中,事情的工作原理非常相似:一方将消息发送到消息代理(也称为面向消息的中间件——MOM),另一方——当该方能够时——接收或显式查询消息代理中的任何消息。
这就是类比停止发挥作用的地方。与语音邮件相比,消息代理有很多选项。消息代理处于理想的位置,可以提供额外的服务,如路由,并对消息传递做出保证。可以针对不同的用例优化消息代理,例如,您可以用速度换取持久性。消息代理可以将消息持久化到外部存储以确保持久性,尽管这通常是在速度的名义下可以切换的配置。
在语音邮件示例中,一条消息由一方发送,然后传递到另一方——通信是点对点的。消息代理支持此功能,以及另一种称为发布-订阅的通信类型,其中消息传递给多个客户端。
消息代理的一个常见用途是解决两个不同系统之间的集成问题。发送到消息代理的数据通常采用发送方和接收方都通用的格式。两个系统仅需要就数据契约达成一致才能使用消息代理。消息通常包含消息体,其中存储消息本身的内容,以及消息头,这些消息头是键/值对,提供有关消息体的元数据,可用于帮助消息的使用者处理消息。消息头可以是您想要的任何内容,但它们通常与消息本身或消息的处理器相关。
Java 消息服务
Java 消息服务 (JMS) API 指定了与消息代理交互的客户端接口。每个消息代理都提供自己的 API 实现,非常类似于 JDBC 驱动程序对 JDBC API 的操作。这意味着 JMS 客户端通常应使用与服务器相同的客户端版本。有许多优秀的 JMS 代理实现可供选择。原因之一是消息传递一直是应用程序开发的重要组成部分,并且今天变得越来越重要。JMS 自 1.1 版起就成为 J2EE(现为 Java EE)规范的一部分。在过去十年的大部分时间里,JMS 规范一直保持在 1.1 版。
在 JMS 中,客户端使用javax.jms.ConnectionFactory
创建javax.jms.Connection
。然后,Connection
可用于创建javax.jms.Session
。Session
表示客户端与代理的交互,并允许发送和接收消息以及其他不太明显的操作。
接口上最有用的方法涉及创建消息生产者和消息使用者,这些生产者和使用者向javax.jms.Destination
发送和接收消息。Destination
将 JMS 中消息代理上的“地址”概念映射出来。它还映射了代理存储消息的位置的概念。在 JMS 中,消息发送到、存储在和从同一位置使用,所有这些都由javax.jms.Destination
实例表示。
[caption id="attachment_7506" align="alignnone" width="573" caption="上图中,蓝色元素表示生产者和使用者。橙色元素表示代理中缓冲消息的目标。在 JMS 中,这些是主题或队列。"]
[/caption]
Destination
是一个接口,并且有两个更具体的子接口,javax.jms.Queue
和javax.jms.Topic.
Queue
表示标准队列,如前所述,它是一个点对点结构。Topic
提供发布-订阅消息传递,并且可以将单个消息传递给多个接收方。
要将消息发送到Destination
,必须创建javax.jms.MessageProducer
。然后,MessageProducer
可用于发送javax.jms.Message
。
JMS 支持两种不同的接收消息机制。第一种方式是使用 javax.jmx.MessageConsumer#receive()
方法请求消息,该方法以同步方式从 Destination
返回单个消息;默认情况下,该方法会阻塞,直到收到消息。客户端可以使用 javax.jms.MessageListener
而不是 MessageConsumer
,方法是调用 javax.jms.Session#setMessageListener(MessageListener)
。MessageListener
是一个接口,只有一个方法 public void onMessage(javax.jms.Message)
,每当 Destination
上有 javax.jms.Message
可供使用时,就会调用此方法。MessageListener
提供异步消息处理:消息到达时,会立即进行处理。
JMS API 中还有很多内容需要学习,但这些类和概念将最有助于我们讨论 Spring 对 JMS 消息的支持。第一级支持是 org.springframework.jms.core.JmsTemplate
,它提供简化的方法,将我们刚刚讨论的内容简化为单行代码。JmsTemplate
需要一个 javax.jms.ConnectionFactory
实例来完成其工作。JmsTemplate
可以为您完成很多工作。例如,要发送消息,JmsTemplate
会建立一个 javax.jms.Session
,设置 javax.jms.MessageConsumer
或 javax.jms.MessageProducer
,设置所有事务机制,并为您提供对当前 javax.jms.Session
的引用,以便您可以创建您选择的消息并发送它。通过所有错误处理和构造逻辑,这很容易节省数十行代码。消息发送后,它会销毁或关闭大多数这些对象。这是应用程序服务器(如 Java EE 服务器)中的标准做法,因为 ConnectionFactory
实例由服务器创建、服务器管理并进行池化。它们在使用后会缓存实例。在这些环境中关闭资源只是将它们返回到池中。因此,JmsTemplate
在标准情况下会执行正确的操作,假设 ConnectionFactory
缓存或池化实例。
在像应用程序服务器这样的托管环境中,您通常需要从 JNDI 获取 javax.jms.ConnectionFactory
。您可以使用 Spring 为您查找该引用并配置 JmsTemplate
。在我们的示例中,我们希望更松散地操作,因此我们将使用独立的 ActiveMQ 消息代理。ActiveMQ 是一款流行的 开源消息代理。要使用它,请下载它,然后运行 bin 文件夹中适合您操作系统的启动脚本。在您的应用程序中,您需要客户端库才能连接相应的 ActiveMQ 版本。在撰写本文时,ActiveMQ 的最新版本为 5.4.2。如果您使用 Maven,请将以下依赖项添加到您的 Maven pom 文件中
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-optional</artifactId>
<version>${activemq.version}</version>
</dependency>
请务必为 ${activemq.version}
创建一个 Maven 属性,或手动将字符串替换为相应的版本。还有一个 activemq-all
依赖项,但它会下载许多可能不必要的 jar 包。对于我们的应用程序,以上两个依赖项就足够了。
使用 Spring 与 JMS
让我们检查一下基本 JMS 应用程序的配置。首先,让我们检查一下基本的 Spring XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/200…