一、JMS基本概念
1.1 P2P通信
1.2 Pub/Sub通信
二、JMS消息
三、JMS P2P编程
3.1 使用JMS QueueConnection对象
3.2 处理回退事件
3.3 关闭JMS对象
3.4 接收消息
3.5 消息驱动的Bean
3.6 消息持久化
3.7 消息选择器
四、JMS Pub/Sub编程
五、二阶段提交的事务
━━━━━━━━━━━━━━━━━━━━━━━━━━
EJB 2.0和J2EE 1.3规范开始提供对Java消息服务(JMS)的支持。在J2EE 1.3加入JMS之前,J2EE环境中的组件通过RMI-IIOP协议通信,J2EE是一个完全同步的平台。由于在J2EE 1.3规范中引入了JMS,J2EE环境开始具备一项极其重要的功能--异步通信。
● 说明:RMI-IIOP是Java远程方法调用(RMI,Remote Method Invocation)的一个兼容CORBA的版本,CORBA是Common Object Request Broker Architecture的缩写,即公用对象请求代理(调度)体系结构。RMI-IIOP通过CORBA平台和语言中立的通信协议发送RMI消息。
为了支持异步通信,J2EE 1.3规范还引入了一种新的EJB类型:消息驱动的Bean,即Message Driven Bean,简称MDB。如前所述,在JMS之前,J2EE原来是一个建立在Java RMI-IIOP通信协议基础上的同步环境,但MDB却具有接收异步消息的能力。
异步通信使得企业应用能够建立在一种全新的通信机制之上,它具有如下重要优点:
■ 异步通信程序不直接交换信息。由于这个原因,通信的任意一方能够在对方停止响应时继续自己的操作,例如当接收消息的一方不能响应时,发送方仍能够发出消息。
■ 异步通信有助于提高可靠性。支持异步通信的中间件软件提供了有保证的(可靠的)消息递送机制,即使整个环境出现问题也能够保证消息传递到目的地。Internet是一种不可靠的通信媒体,对于通过Internet通信的应用来说,异步通信的这一特点非常富有吸引力。
■ 发送异步消息的程序不会在等待应答的时候被锁定,从而极大地有利于提高性能。
JMS本身不是一个通信软件,而是一个标准的应用编程接口(API),用来建立厂商中立的异步通信机制。从这个意义上说,JMS类似于JDBC和JNDI,例如就JDBC来说,它要求有一个底层的数据库提供者,JMS则要求有一个支持JMS标准的底层异步通信中间件提供者,一般称为面向消息的中间件(Message-Oriented Middleware,MOM)。
MOM是一种支持应用程序通过交换消息实现异步通信的技术。在某种程度上,异步通信有点象是人们通过email进行通信;类似地,同步通信的程序就象是人们通过电话进行通信。在异步通信过程中,程序的结合方式是宽松的,换句话说,异步通信的程序并不直接相互联系,而是通过称为队列(Queue)或主题(Topic)的虚拟通道联系。
同时,异步通信还意味着消息通过一个中转站,按照存储-转发的方式传递,即使通信的接收方当时并未运行,另一方(发送方)的程序也能够顺利发出消息,一旦接收方的程序后来开始运行,消息就会传递给它。
JMS通信主要的优点是提供了一个环境,在这个环境中各个程序通过标准的API进行通信,开发者不必再面对各种不同操作系统、数据表示方式、底层协议所带来的复杂性。
本文讨论了JMS编程的基本概念以及两种JMS通信方式:第一种是端对端通信(Point-to-Point,P2P)方式,第二种是出版/订阅(Publish/Subscribe,Pub/Sub)方式,另外还涵盖了JMS消息结构、JMS主要对象、MDB、JMS和WebSphere编程、消息持久化以及JMS事务支持方面的问题。
一、JMS基本概念
鉴于JMS是一种比较新的技术,所以本文将首先详细介绍JMS编程的一般概念,然后再探讨WSAD 5.0环境下的JMS开发。如前所示,JMS程序本身并不直接相互通信,消息被发送给一个临时中转站--队列或主题。队列和主题都是能够收集消息的中转对象,但两者支持的消息传递机制又有所不同,分别对应两种不同的通信方式--P2P和Pub/Sub。
1.1 P2P通信
P2P消息传递又可以按照推(Push)和拉(Pull)两种方式运作。在P2P拉方式中,程序通过称为队列的虚拟通道通信:在通信会话的发送方,发送程序把一个消息"放入"队列,在接收方,接收程序定期扫描队列,寻找它希望接收和处理的消息。和推方式相比,拉方式的消息传递效率较低,因为它需要周而复始地检查消息是否到达,这个过程会消耗一定的资源。另外必须注意的一点是,当接收方发现一个需要处理的消息时,它就会"提取"消息,从效果上看等于从队列删除了消息。
因此,即使有多个接收程序在处理同一个队列,对于某一特定的消息来说,总是只有一个接收者。JMS程序可以使用多个队列,每一个队列可以由多个程序处理,但是只有一个程序才会收到某个特定的消息。
在P2P推方式的消息传递中,发送程序的工作原理也一样,它把消息发送到队列,但接收程序的工作原理有所不同。接收程序实现了一个Listener接口,包括实现了该接口中的onMessage回调方法,在J2EE环境中监听队列接收消息的任务交给了容器,每当一个新的消息达到队列,容器就调用onMessage方法,将消息作为参数传递给onMessage方法。
P2P通信最重要的特点(不管是推还是拉)是:每一个消息总是只由一个程序接收。一般而言,P2P程序在通信过程中参与的活动较多,例如,发送程序可以向接收程序指出应答消息应当放入哪一个队列,它还可以要求返回一个确认或报告消息。
1.2 Pub/Sub通信
在Pub/Sub通信方式中,程序之间通过一个主题(Topic)实现通信,用主题作为通信媒介要求有Pub/Sub代理程序的支持(稍后详细介绍)。在消息发送方,生产消息的程序向主题发送消息;在接收方,消息的消费程序向感兴趣的主题订阅消息。当一个消息到达主题,所有向该主题订阅的消费程序都会通过onMessage方法的参数收到消息。
这是一种推式的消息传递机制。可以设想,会有一个以上的消费程序收到同一消息的副本。相比之下,程序在Pub/Sub通信过程中参与的活动较少,当生产者程序向某个特定的队列发送消息,它不知道到底会有多少程序接收到该消息(可能有多个,可能没有)。通过订阅主题实现的通信是一种比较灵活的通信方式,主题的订阅者可以动态地改变,却不需要改动底层的通信机制,而且它对整个通信过程完全透明。
Pub/Sub方式的通信要求有Pub/Sub代理的支持。Pub/Sub代理是一种协调、控制消息传递过程,保证消息传递到接收方的程序。P2P通信方式中程序利用一个队列作为通信的中转站,相比之下,Pub/Sub通信方式中程序直接交互的是特殊的代理队列,这就是我们要在WebSphere MQ常规安装方式之上再装一个MA0C的原因(只有用WebSphere MQ作为JMS提供者时才必须如此。要求使用MQ 5.3.1或更高的版本),MA0C就是一个支持Pub/Sub方式通信的代理软件。
QueueConnectionFactory和TopicConnectionFactory对象是创建相应的QueueConnection对象和TopicConnection对象的JMS工厂类对象,JMS程序正是通过QueueConnection对象和TopicConnection对象连接到底层的MOM技术。
二、JMS消息
基于JMS的程序通过交换JMS消息实现通信,JMS消息由三部分构成:消息头,消息属性(可选的),消息正文。消息头有多个域构成,包含了消息递送信息和元数据。
消息属性部分包含一些标准的以及应用特定的域,消息选择器依据这些信息来过滤收到的消息。JMS定义了一组标准的属性,要求MOM提供者支持(如表1所示)。消息正文部分包含应用特定的数据(也就是要求递送到目标位置的内容)。
![](http://jinguo.iteye.com/col/attachment/2003/8/249804.gif)
表1 JMS标准消息属性
可选的属性包括JMSXUserID、JMSXAppID、JMSXProducerTXID、ConsumerTXID、JMSXRcvTimestamp、JMSXDeliveryCount以及JMSXState。消息头为JMS消息中间件提供了描述性信息,诸如消息递送的目标、消息的创建者、保留消息的时间,等等,如表2所示。
![](http://jinguo.iteye.com/col/attachment/2003/8/249805.gif)
表2 JMS消息头的域
*在所有这些消息类型中,TextMessage是最常用的,它不仅简单,而且能够用来传递XML数据。
JMS支持多种消息正文类型,包括:
■ textMessage:消息正文包含一个java.lang.String对象。这是最简单的消息格式,但可以用来传递XML文档。
■ ObjectMessage:消息正文中包含了一个串行化之后的Java对象,这类Java对象必须是可串行化的。
■ MapMessage:消息正文包含一系列"名字-值"形式的数据元素,通常用来传送一些按键-值形式编码的数据。设置数据元素可以用setInt、setFloat、setString等方法;在接收方,提取数据元素使用相应的getInt、getFloat、getString等方法。
■ BytesMessage:消息正文包含一个字节数组。如果需要发送应用生成的原始数据,通常采用这一消息类型。
■ StreamMessage:消息正文包含一个Java基本数据类型(int,char,double,等等)的流。接收方读取数据的次序跟发送方写入数据的次序一样,读写消息元素分别使用readInt和writeInt、readString和writeString之类的方法。
JMS消息对象可以用JMS会话对象(参见本文"使用JMS QueueConnection对象"部分)创建。下面的例子显示了如何创建各类消息对象:
TextMessage textMsg = session.createTextMessage(); MapMessage mapMsg = session.createMapMessage(); ObjectMessage objectMsg = session.createObjectMessage(); BytesMessage byteMsg = session.createBytesMessage(); |
消息对象提供了设置、提取各个消息头域的方法,下面是几个设置和提取JMS消息头域的例子:
// 获取和设置消息ID String messageID = testMsg.getJMSMessageID(); testMsg.setJMSCorrelationID(messageID); // 获取和设置消息优先级 int messagePriority = mapMsg.getJMSPriority(); mapMsg.setJMSPriority(1); |
另外,消息对象还为标准属性和应用特有的属性提供了类似的设置和提取方法。下面的例子显示了如何设置、提取JMS消息标准属性和应用特有属性域:
int groupSeq = objectMsg.getIntProperty("JMSGroupSeq"); objectMsg.setStringProperty("myName", "孙悟空"); |
JMS为读写消息正文内容提供了许多方法。下面略举数例,说明如何操作不同类型的消息正文:
// Text Message TextMessage textMsg = session.createTextMessage(); textMsg.setText("消息内容文本"); // Map Message MapMessage mapMsg = session.createMapMessage(); mapMsg.setInt(BookCatalogNumber, 100); mapMsg.setString(BookTitle, "书籍题目"); mapMsg.setLong(BookCost, 50.00); String bookTitle = mapMsg.getString("BookTitle"); // Object Message ObjectMessage objectMsg = session.createObjectMessage(); Book book = new Book("WinSocks 2.0"); objectMsg.setObject(book); |