Java消息发送服务(Java Messaging Service,JMS)是提供商无关的一套API,用于在程序间进行可靠的消息发送。在客户端-服务器计算中,客户端程序与服务器与服务器建立联系并请求服务。相反,消息发送应用在相互协作的程序之间发送消息。有些程序(在所谓的“对等(peer-to-peer)”应用中)则相互之间直接交换信息(JXTA使用的就是这种模型)。
JMS提供了一个中间件消息代理(message broker),后者提供了程序间可靠的、事务性的消息发送。
虽然这些图中显示的消息提供者和消费者都是物理上的机器,实际上它们也可以是运行在一台或者多台机器上的相互协作的一些进程。
JMS提供者是一个程序,它实现了JMS公共接口中的JMS服务约定。J2EE平台规范要求平台实现包含一个JMS提供者。
大多数人都比较熟悉客户端-服务器模型的消息发送,在这种模型中,客户端程序与服务器建立联系,请求服务、数据,或者同时请求服务和数据。相反,JMS提供了一种更丰富的消息发送模型,这种模型具有以下高级特性:
可靠的消息发送。当消息发送者正在发送消息时,消息接受者无需处于运行状态。而是等等接受者下次做好准备时,再将消息发送到接受者手上。
点到点或者发布/订阅式的消息发送模型。消息的传送可能是一对一的,也可能是一对多的。
事务。消息发送可作为一个分布式事务的一部分。
同步的或异步的消息发送。消息生产者可能会等待接受者的确认,也可能不会。
面向对象的消息发送。JMS允许在客户端之间发送对象,而不是通过使用一些协议来发送结构化的数据。
遗留整合。JMS可以与底层的第三方消息发送系统进行整合。
本期解释了如何使用JMS消息队列来实现两个进程之间简单、可靠的点到点的消息发送。发布/订阅式的消息发送将在后面的期“Enterprise Java Technologies Tech Tips”中讲述。
JMS队列术语
JMS使用消息队列的概念来实现点到点的消息发送。在点到点的消息发送中,总是有一个明确的消息生产者和一个消息消费者。在点到点的消息发送中,与时间没有多大的关系,除非消息发送者为消息定义了一个期限。消息接受这可以接收由消息生产者在过去任何一个时候发送的消息,即使在该消息被编入队列时消息消费者没有处于运行状态。
JMS提供者是一个消息发送服务器,它负责处理消息的持久性、超时、重发、事务回滚以及由JMS提供的其他服务。对于J2EE SDK这种情况,JMS提供者是J2EE服务器程序的一部分。消息生产者发送对象到由JMS维护的一个队列中。消息消费者则从该队列接收消息,并发出确认,表示已经收到消息。
JMS规范定义了一些可用于在进程间发送消息的对象:
JMS管理的对象。有两种JMS管理的对象:目的地和连接工厂。这两种对象都是由系统管理员通过环境管理工具创建的。
目的地。这是一种服务器端的对象,通过这个对象进行消息的发送和接收。JMS队列就是一种目的地对象。
连接工厂。这是一种服务器端的对象,负责配置和创建到一个特定目的地的连接。JMS 队列的JMS连接工厂就是一种QueueConnectionFactory。
连接。这是一种到一个JMS提供者(而不是到一个目的地)的虚拟连接。它被用来创建会话。用于访问队列的一个连接就是一种QueueConnection。
会话。这是一个潜在的消息传送和接收的事务性的工作单元。会话用于创建消息、消息生产者和消息消费者。用于访问队列的一个会话就是一种QueueSession。
消息。这是一种可以从消息目的地发送到消息消费者的对象。消息的类型随要发送的对象类型的不同而不同。例如,为本期提供的示例代码就使用了TextMessage对象。
消息生产者。这是一种由JMS客户端程序使用的、用于发送消息到目的地的对象。JMS客户端程序从一个会话中获取消息生产者对象。程序可以使用QueueSender这种类型的消息生产者对象来发送消息到一个队列中。
消息消费者。这是一种由JMS客户端程序使用的、用于从一个目的地接收消息的对象。JMS客户端程序从一个会话中获取消息接受者对象。程序可以使用QueueReceiver这种类型的消息消费者对象来从一个队列中接收消息。
这里颇有几个新的术语。现在让我们看看如何发送消息。以下步骤展示了从与本期一起提供的示例代码中抽出的一些例子。(想知道如何下载和运行该示例代码,请参考运行示例代码一节。)不过,在运行示例代码之前,你需要配置一下服务器(参见 配置服务器一节)。
发送消息
J2EE参考实现预先配有一个队列连接工厂(名为QueueConnectionFactory)和一个队列(名为jms/Queue)。如果你使用的是JMS服务器,而不是参考实现,或者如果你想试着更改队列连接工厂和/或队列队列的名称,请参考配置服务器一节。
下面是通过一个JMS队列发送消息的步骤。从示例程序TestQueue中抽出的代码片段也穿插在这些步骤中。
通过按名字在JNDI中进行查找,获得一个指向QueueConnectionFactory 的引用:
protected static String qfactoryName =3.
"jms/queue/TechTipsQueueConnectionFactory";4.
...
try {
// 获得JNDI上下文
InitialContext ctx = new InitialContext();
// 获得连接工厂
QueueConnectionFactory qcf =
(QueueConnectionFactory)ctx.lookup(qfactoryName);
2.从 QueueConnectionFactory获得一个QueueConnection,再从这个连接获得一个QueueSession 。按名字在JNDI中查找:
// 获得一个到队列的连接
qc = qcf.createQueueConnection();
//从该连接获得一个会话
QueueSession qs = qc.createQueueSession(
false, Session.AUTO_ACKNOWLEDGE);
// 获得一个队列
Queue q = (Queue)ctx.lookup(queueName);
3. 使用QueueSession创建一个QueueSender,将该队列作为一个参数来传递。(QueueSender是QueueSession与某个特定队列之间的一个关联。):
// 使用这个会话创建一个QueueSender
// and a TextMessage.
QueueSender qsnd = qs.createSender(q);
现在可以用QueueSender来发送消息到队列。
4. 创建一个消息对象(Message的子类),然后使用QueueSender的发送方法将它们发送至目的地。示例程序从Session中获得一个TextMessage对象。接着示例程序将每个程序参数打包到TextMessage 中,然后使用QueueSender将其发送至队列。注意,同一个TextMessage 可以使用多次。
TextMessage tm = qs.createTextMessage();
// 为第一个参数之后的每个参数进行一次循环
// 以文本消息的形式发送参数字符串
for (int i = 2; i < args.length; i++) {
tm.setText(args[i]);
qsnd.send;
}
5.关闭QueueConnection。在try/finally程序块的最后一条语句中关闭连接是一个好习惯。这一步很重要:忘记关闭 QueueConnections 将可能导致服务器上的资源泄漏:
} finally {
if (qc != null) {
qc.close();
}
}
要发送消息到一个消息队列,可以使用TestQueue程序(在缺省的包中),加上一个参数“send”,例如:
$ java TestQueue send jms/queue/MyTestQueue a b c d
Java Message Service 1.0.2 Reference
Implementation (build b14)
Sent: 'a'
Sent: 'b'
Sent: 'c'
Sent: 'd'
接收消息
TestQueue程序按照以下步骤接收消息:
和2从一个消息队列接收消息的起先两步与发送消息是一样的:先是查找连接工厂,再获得一个QueueConnection,然后查找Queue。
从QueueSession 获得一个QueueReceiver :
QueueReceiver qrcv = qs.createReceiver(q);
3. 从Queue接收消息。示例程序进入一个循环,在这个循环中从队列获取消息并将它们打印到标准输出设备。
qc.start();
Message m = qrcv.receive(10000);
while (m != null) {
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage)m;
System.out.println("Received text: '" +
tm.getText() + "'");
} else {
System.out.println("Received a " +
m.getClass().getName());
}
m = qrcv.receive(100);
} finally {
if (qc != null) {
qc.close();
}
}
对QueueConnection的启动方法的调用将告诉连接开始接收消息。QueueReceiver接收方法带有一个参数,该参数表明了等待一条消息的毫秒数。如果消息没有在规定时间内到达,该方法将返回null值。直到消息队列在100毫秒的时间内都保持为空,程序才开始读取和打印收到的消息。在try/finally程序块的结尾有一个finally子句,该子句像前面例子中一样地关闭QueueConnection。
要接收消息队列中的消息,可以使用TestQueue程序,再带上一个参数“recy”,例如:
$ java TestQueue recv jms/queue/MyTestQueue
Java Message Service 1.0.2 Reference
Implementation (build b14)
Received text: 'a'
Received text: 'b'
Received text: 'c'
Received text: 'd'
配置服务器
如果你使用的不是参考引用,或者你想更改队列和/或队列连接工厂的名字,你就需要通过使用平台的管理工具配置JMS提供者。队列或者连接工厂被创建之后,便留在服务器中,直到服务器重新启动。有些平台实现可能会为客户端提供扩展API,以便程序化地创建目的地和连接工厂。
用来在J2EE SDK下创建受管理的对象的工具是。要配置服务器,需:
启动应用服务器。
Create a message queue, giving it a JNDI name. (Do this only once.) To create a
new message queue in the J2EE SDK, first decide on a name for your message
queue. It can be convenient for administration purposes to choose a name that
indicates that the queue is used by JMS, for example, jms/MyTestQueue. Then
execute the command:创建一个消息队列,并为它取一个JNDI名。(只做一次。)要用J2EE SDK创建一个新的消息队列,首先需要为消息队列决定一个名字。为了便于管理, 应该选择一个可以表明该队列是JMS使用的队列,例如jms/MyTestQueue。然后执行命令:
j2eeadmin -addJmsDestination <queuename> queue
用消息队列的实际名字替换<queuename> 。
由于连接工厂名QueueConnectionFactory被硬性地放在示例程序中,因此要告诉程序使用一个不同的连接工厂名,就必须修改源代码,并且重新编译。例如,在TestQueue.java中:
// Change this variable's value to change the
// connection factory name
protected static String qfactoryName =
"QueueConnectionFactory"
重新编译之后,使用j2eeadmin(或者你自己的服务器的工具)创建一个connection factory,给它取个JNDI名,如:
j2eeadmin -addJmsFactory jms/MyQueueConnectionFactory
要列出连接工厂,使用:
j2eeadmin -listJmsFactory
要列出目的地,使用:
j2eeadmin -listJmsDestination
对于不是J2EE SDK的平台,可以在该平台上使用J2EE产品的部署工具来创建所需的消息队列和连接工厂。
运行示例代码
下载这些期的示例存档。这个JAR文件包含了示例程序的完整源代码,包括Java程序文件和HTML格式的文件。你可以使用命令jar xvf ttmar2003.jar 将示例jar中的内容解压缩到你的工作目录下。在运行示例程序前,要确保J2EE服务器正在运行。
多次运行示例程序。试着发送一些成批的消息,但是不接收这些消息,检查一下输出的顺序。
这个示例程序为你试着使用JMS队列提供了切入点。探索一下JMS接口使用的API。例如,可以更改一下程序,试着使用三种不同的接收方式。探索一下事务性的消息发送,或者试着发送各种类型的消息对象。JMS有许多特性,熟练使用它可以提高你的应用设计水平。