Java 消息服务(Java Message Service,JMS)API 是 J2EE 平台的构成元素。JMS 1.0.2 定义了两种类型的消息传递域(它们是相互独立的),即点对点和发布/订阅。JMS 的最新版本,即版本 1.1,将成为 J2EE 1.4 的一部分,EJB 2.1 也将会需要它,JMS 统一了这两个域。有了 JMS 1.1,客户机就不再必须专门针对这个或那个域进行实现了。相反,JMS 客户机可以实现为能与来自任一域的目标一起工作。这极大地简化了 JMS API,并为开发者创建更通用、重用性更好的消息传递代码提供了机会。一旦有实现 JMS 1.1 的 JMS 提供程序和 J2EE 容器可供使用,JMS 客户机代码的开发者就应该可以开始在他们的新代码中使用版本 1.1 的 API,并且也可以从将他们现有的 JMS 1.0.2 代码移植到新的 API 中受益。
我们将直接进入对新规范的讨论,所以,如果您要温习一下 JMS,请参阅 JMS 快捷介绍。
在 JMS 1.0.2 中,三个通道接口 ―
Destination 、
Queue 和
Topic ― 不幸地造成了 API 中的接口数量增加到三倍的后果。为了发送或接收消息,客户机使用
连接生成器来获得一个
连接,客户机用这个连接来创建
会话并获得所期望的
队列和
主题上的
消息生产者和
消费者。唯一的问题是每种目标都有它自己的生成器、连接、会话、生产者和消费者接口(基本上,就是它自己的接口域),表 1(出自 JMS Javadocs)总结了这一情况。
表 1. 点对点和发布/订阅接口的关系
| JMS 公共 | PTP 域 | Pub/Sub 域 |
| ConnectionFactory | QueueConnectionFactory | TopicConnectionFactory |
| Connection | QueueConnection | TopicConnection |
| Destination | Queue | Topic |
| Session | QueueSession | TopicSession |
| MessageProducer | QueueSender | TopicPublisher |
| MessageConsumer | QueueReceiver | TopicSubscriber |
此外,为了与 XA(分布式)事务兼容,JMS 还提供了另一组接口。这意味着除了上表所示的 18 个接口之外,JMS 还有另外九个接口 ― 生成器、连接和会话类型的 XA 版本 ― 类型的数量达实际所需的三倍之多。
在上述 27 个接口中,只有六个公共接口(在第一列中的那些)是发送和接收消息所真正需要的。
Queue 和
Topic 有时对于区别点对点方式和发布/订阅方式而言很有用。表中的其它十个接口和九个 XA 接口中的至少六个实际上是不需要的,至少对于编写客户机代码来说是不需要的。
举例来说,使用 JMS 1.0.2,如果开发者要访问一个
Queue ,他将使用
QueueConnectionFactory 来获取一个
QueueConnection ,以创建一个
QueueSession ,这个
QueueSession 用来创建一个
QueueSender 或
QueueReceiver 。但这样的代码是特定于队列的,将不能与
Topic 一起工作。这种办法在 JMS 1.1 中也行得通,但直接使用
ConnectionFactory 、
Connection 、
Session 和
MessageProducer 或
MessageConsumer ,会简单些,这后一种办法能与
Topic 和
Queue 一起工作。因此,开发者应使用公共接口并避免使用特定于域的接口,因为公共接口使得代码更简单、更通用,重用性也更好。
在 JMS 1.0.2 中,客户机代码必须使用特定于域的队列和主题接口。公共接口在很大程度上仅仅是一个形式,并不声明客户机所需要的许多方法。例如:
Destination 压根不实现任何方法;
MessageProducer 不实现
send 方法;
MessageConsumer 的确实现
receive ,但
Session 客户机无法创建
MessageConsumer ;如此等等。
这样,对于一个要发送和接收
Queue 中的消息的客户机,它必须使用表 1 的第二列中的类,而使用
Topic 的类似客户机则必须使用表 1 的第三列中的类来实现很相似的代码。公共接口中缺乏这种多态性使得不可能通过主题重用队列客户机,反过来重用也不可能,这导致了如
清单 1所示的那种重复性代码。
JMS 1.1 中的主要变化是添加了新的 API,以支持能同时与点对点或发布/订阅域一起工作的客户机代码。最新的发行版在公共接口中添加了一些方法,从而使主题和主题扩展具有多态性。
例如,
MessageProducer 如今实现了
send ,所以,客户机可以给目标发送消息而不必知悉该目标是一个队列还是一个主题。类似地,
MessageConsumer 声明了
receive 方法,所以,相同的客户机代码不论是使用队列接收程序还是主题订阅程序都能工作。
清单 2显示了如何使用这个统一接口获取生产者和消费者。 清单 1所示的 JMS 1.0.2 代码需要四个方法(一对用于队列,另一对用于主题),而 JMS 1.1 代码则只需要两个方法(一对用于目标)。
这些新的 API 使得开发者能够编写重用性好得多的 JMS 客户机代码。JMS 客户机代码只是访问并使用目标而无须知道哪些目标是队列,哪些目标是主题。被编写用来访问队列的代码还可以不加修改就以相同方式被重用来访问主题,反之亦然。这对于 JMS 1.0.2 来说是不可能的。
因为公共接口现在几乎能够执行它们的特定于域的扩展所能做的所有相同任务(例如,
MessageProducer 几乎可以做
QueueSender 和
TopicPublisher 能做的所有任务),所以,特定于域的子接口实际上已不再需要。这些接口在 JMS 的未来版本中很可能会被去掉。
将消息传递域统一起来的微妙但重要的结果是,队列和主题现在可以通过同一个会话(从而在同一个事务中)进行访问。在 JMS 1.0.2 中,
QueueSession 可以访问不止一个
Queue ,
TopicSession 可以访问多个
Topic ,但单个会话不能既访问队列又访问主题。因为会话管理着事务,所以单个事务不能既用来控制队列,又用来控制主题。
在 JMS 1.1 中,不但单个
Session 可以访问
Queue 或
Topic (任一类型的
Destination ),而且单个会话实例可以用来操纵一个或多个队列以及一个或多个主题,一切都在单个事务中进行。这意味着单个会话可以(例如)创建队列和主题中的生产者,然后使用单个事务来同时发送队列和主题中的消息。因为单个事务跨越两个目标,所以,要么队列和主题的消息都得到发送,要么都未得到发送。类似地,单个事务可以用来接收队列中的消息并将消息发送到主题上,反过来也可以,如
清单 3所示。
考虑一个客户跟踪应用程序,它给要使用客户信息的其它应用程序(其中之一是送货应用程序)提供信息。跟踪应用程序可能需要告知其它应用程序某个特定客户的地址已经变更。如果使用消息传递,则该应用程序将把这项变更发布到主题
CustomerChanges 上,所有要使用客户信息的应用程序将订阅这个主题。客户跟踪应用程序可能要求每个订阅者回报该订阅者是否能够成功处理该项变更。客户跟踪应用程序将指定一个队列
ChangeProcessed ,每个订阅者应用程序将使用这个队列来发送应答。
订阅者应用程序,如发货应用程序将接收
CustomerChanges 主题上的变更消息,处理这个消息,然后发送一个应答到
CustomerChanges 队列上。为了确保没有任何变更被丢失,这三个步骤(接收请求、处理请求和发送应答)应该在单个原子事务中进行。这意味着发货应用程序必须使用同一个会话来访问
CustomerChanges 主题和
ChangeProcessed 队列。在 JMS 1.0.2 中,会话不能既访问主题又访问队列,但在 JMS 1.1 中则可以这样做。
JMS 1.0.2 受害于子类型的爆炸式增长;因为对于每一个类型,都有该类型本身的一个接口、一个队列扩展和一个主题扩展。三倍的接口意味着开发者要学习三倍的 API。客户机代码必须使用这个域或那个域,要么使用队列那组扩展,要么使用主题那组扩展,所以针对一个而编写的代码不能用于另一个。
JMS 1.1 统一了域,从而每个公共接口可以用来取代其特定于域的扩展。这意味着开发者要学习的接口更少了,并且同样的客户机代码既可以用于队列,又可以用于主题。此外,JMS 事务现在可以更容易地跨越主题和队列,这在域被统一起来之前是不可能的。
JMS 1.1 中统一的 API 使得 JMS 客户机代码更简单、更易于维护,重用性也更好。一旦 JMS 提供程序和 J2EE 容器的供应商实现了 JMS 1.1,编写新的客户机代码的开发者无疑应该使用统一的 API。虽然针对 JMS 1.0.2 编写的以前的客户机代码在 JMS 1.1 依然将很好地运转,但将这些代码移植成统一的 API 以使其更简单也是颇有价值的。
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
- 下载本文所讨论的
示例类。
- 可以从
JMS 主页学到关于 Java 消息服务 API(Java Message Service API,JMS)的更多知识。
- 阅读
JMS 1.1 中的变化(2002 年 3 月 4 日最后更新)。
- 此处列出了提供
实现了 JMS 1.0.2 的产品的供应商。
- Richard Monson-Haefel 和 David A. Chappell 所著的
Java Message Service
(O'Reilly,2001 年)对 JMS(版本 1.0.2)作了很好的总体介绍。
- 电子商务设计师 Willy Farrell 在他很受欢迎的教程“
Introducing the Java Message Service”(
developerWorks,2001 年 8 月)很精彩地概述了 JMS,教程还提供开发使用 JMS 的程序方面的基础知识。
-
Java 理论和实践专栏作家 Brian Goetz 仔细研讨了 JMS 如何可以
提高您的企业应用程序的灵活性和可伸缩性(
developerWorks,2002 年 2 月)。
- Java 设计师 Nicholas Whitehead 在“
Implementing vendor-independent JMS solutions”(
developerWorks,2002 年 2 月)中向您展示了如何组合使用 JMS、Java 命名和目录接口(Java Naming and Directory Interface)和精心编写的属性文件以构建与供应商无关的 JMS 解决方案。
- “
Get the message? Using JMS technology as a data replication solution”(
developerWorks,2002 年 2 月)概略讲了如何使用 JMS 进行大规模的文件复制。
- 要获取关于 J2EE 1.4(它包含 JMS 1.1)的更多信息,请下载
平台规范并阅读
JSR。
- 要获取关于 EJB 2.1(它需要 JMS 1.1)的更多信息,请下载
规范和文档并阅读
JSR。
- 来自 Ashnasoft 的
AshnaMQ 2.1添加了对 JMS 1.1 API 的支持。
- 来自 ObjectWeb 的
JORAM 3.1.0是第一个实现了 JMS 1.1 的开放源代码的消息传递系统。
- 在
developerWorks
Java 技术专区上可以找到成百上千与 Java 有关的参考资料。

Bobby Woolf 是 J2EE 设计师和开发者、作家和演讲人、导师和学生,他在北卡罗莱纳州的 Raleigh 做独立顾问。他几乎使用过 JMS 客户机 API 的各个部分:队列和主题、同步或异步传递的消息、事务会话和非事务会话。他甚至一度试验过临时队列,但他并不非常喜欢临时队列。请通过 woolf@acm.org 与 Bobby 联系。