在本篇文章中,我们将介绍消息的核心概念,以及 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)
,每当javax.jms.Message
可用于在Destination
上使用时,都会调用该方法。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…