8用JMS编程
在本章中,我们将讨论Java 消息发送服务(JMS)接口概念和MQSeries 实施,以及如
何使用JMS 编程。我们将在消息发送编程模式的上下文中探讨JMS 概念。
8.1 什么是JMS?
与JDBC API for databases 一样,Java Message Services(JMS)是消息发送的标准API。
JMS 规范(1.0.2)由Sun Microsystems 开发,IBM 和其他企业消息发送销售商、事务处
理销售商以及RDBMS 销售商都积极参与了开发过程。JMS 为Java 程序与对消息发送
系统对象进行各种操作的消息发送系统进行互动提供了一个常见的模型。程序对消息发
送系统对象进行的常见操作包括创建消息、发送消息、接收消息以及从企业消息发送系
统中读取消息。JMS 为那些用Java 开发的程序提供了一种访问这些消息发送系统操作
的常见方法。
JMS 具有两种消息发送风格,或者说它具有两个域:
.. 一对一或点到点模型;
.. 发布/预订模型。
JMS 仅仅是种规范。每个企业消息发送系统销售商都必须就其特定的消息发送系统提供
实施规范的类。
在本章中,我们将描述JMS API 的MQSeries 实施、讨论JMS API 概念和MQSeries 的
JMS 实施能力,并讲解在可以利用MQSeries JMS 实施的不同情境中如何利用MQSeries
JMS。
为什么要使用JMS?
JMS 标准非常重要,其原因在于:
.. 它是第一个获取广泛跨行业支持的企业消息发送API;
.. 它提供的标准消息发送概念和惯例适用于广泛的企业消息发送系统,因而简化了
企业应用程序的开发;
.. 它可以利用现有的、企业证明成功可行的消息发送系统;
.. 它添加了完全用现有非JMS 客户机解释的新JMS 客户机,从而允许您扩展现有的
基于消息的应用程序;
.. 它允许您可以编写便携性强的基于消息的商业应用程序。
8.2 概述
JMS 是定义JMS 客户机如何访问企业消息发送产品功能的一系列接口和相关语义。我
们这里所描述的消息是指企业应用程序所使用的异步请求、报表或事件。它们既包含协
同这些系统所需的重要信息;又包含着描述特定商业行为的精确格式化的数据。通过这
些消息的交流,每个应用程序都能跟踪企业的发展。
JMS 定义了一系列常见的企业消息发送概念和功能。它最大限度地减少了Java 语言程
序设计人员在使用企业消息发送产品前必须学会的概念集。也最大限度地加强了消息发
送应用程序的便携性。JMS 标准虽然提供了独立于不同销售商的编程接口,但是并不定
义出通讯协议。
JMS 模型
JMS 定义了消息传递服务的一般视图。理解该视图,并弄清它是如何映射到底层的
MQSeries 传输上的,相当重要。一般JMS 模型是建立在Sun 的javax.jms 包所定义的接
口上的,请见图8-1。
图8-1 JMS 模型
连接
连接提供了到底层传输的访问,并被用来创建会话。在MQSeries 上下文中,连接提供
了储存参数,如队列管理器名、远程主机名(在Java 客户连接性中)等的地方。换言之,
MQSeries JMS 连接一般都在Java 虚拟机之外分配MQSeries 资源。连接也支持同时使用。
连接可以提供以下好处:
.. 包括与JMS 供应方的开放式连接。它通常代表客户机和供应方服务端口之间的一
个开放的TCP/IP 槽;
.. 它的创建就是客户机认证发生之处;
.. 它可以指定唯一的客户机标识符;
.. 它提供ConnectionMetaData;
.. 它支持可选的ExceptionListener。
由于建立连接时完成了认证和通讯设置,因此连接相对来说是个重量级的JMS 对象。
大多数客户机都使用单一的连接进行消息发送。其他更先进的应用程序可能会使用几个
连接。JMS 并不为使用多个连接而设计原因;但是,这样做可能有操作上的原因。
JMS 客户机一般创建一个连接、一个或多个会话以及许多消息生成器和使用者。当创建
连接时,它处在停止模式,这就是说没有消息再被送达。
重点:连接是在停止模式中创建的。
通常,直到设置完成前,连接都处在停止模式中。在完成时,将调用连接的start()方
法,而消息则开始到达连接的使用者。此设置惯例可以在客户机仍在进行设置过程中尽
可能减少异步消息送达所带来的任何客户机混乱。
连接可以立即启动,而设置可在随后进行。这样做的客户机必须在设置过程中就准备好
处理异步消息送达。
提示:消息生成器可以在停止连接时发送消息。
不是直接创建连接的,而是利用连接库建立的。库对象可以储存在JNDI 名称空间中,
从而将JMS 应用程序与供应方特定信息隔离开来。连接库对象是利用MQSeries JMS 管
理工具JMSAdmin 创建的。该工具使得管理员可以给JNDI 名称空间定义MQSeries JMS
对象的8 种类型。请参见第8.4.9 节《利用JMSAdmin 以VisualAge for Java 管理JMS JNDI
对象》(见本书第260 页)。
创建连接
客户机利用连接库来创建连接。要使用何种连接库类型取决于您想要哪种类型的连接:
.. 就PTP 连接而言,我们利用QueueConnectionFactory 或XAQueueConnectionFactory
来获取QueueConnection 或XAQueueConnection;
.. 就发布/ 预订消息发送模式而言, 我们利用TopicConnectionFactory 或
XATopicConnectionFactory 来获取TopicConnection 或XATopicConnection。
为了创建连接,我们应当进行以工作:
.. 从JNDI 名称空间接收库对象
JNDI API 向以Java 编写的应用程序提供了命名和目录功能。它是利用Java 的对象
模型专门为Java 设计的。利用JNDI,Java 应用程序可以储存并接收任何类型的命
名为Java 的对象。另外,JNDI 提供进行标准目录操作的方法,如将属性与对象相
关联并利用对象属性查找对象等。
在此常见的API 之后,可以无缝地插入不同的命名和目录服务供应方。这使得Java
应用程序可以通过各种各样的命名和目录服务来利用信息,如LDAP、NDS、DNS
和NIS(YP)等,也使得Java 应用程序可以与传统应用程序和系统并存。
在JNDI 中,所有的命名和目录操作都是根据上下文进行的。没有绝对的根。因此,
JNDI 定义了初始上下文,它是命名和目录操作名解析的起始点。一旦有了初始上
下文,您就可以利用它来查看其他上下文和对象。
.. 为了从JNDI 名称空间接收对象,必须按照下面这段代码所显示的那样设置初始上
下文:
import javax.jms.*
import javax.naming.*;
import javax.naming.directory.*;
java.util.Hashtable ;
Hashtable env =new Hashtable();
env.put(Context.INITIAL_CCONTEXT_FACTORY,icf);
env.put(Context.PROVIDER_URL,url);
Context ctx =new InitialDirContext(env);
Icf 为初始上下文定义了库类,url 则定义了随上下文而变的URL。
.. 一旦获取了初始上下文,我们就可以利用lookup()方法来从名称空间接收对象。
下面这段代码从基于LDAP 的名称空间接收QueueConnectionFactory 名firstQCF;
QueueConnectionFactory qFactory ;
queueFactory =(QueueConnectionFactory)ctx.lookup(“cn=firstQCF ”);
.. 利用库对象来获取连接。
241
利用库对象的createQueueConnection()方法来创建连接,请参见如下的例子:
QueueConnection connection ;
connection =qFactory.createQueueConnection();
.. 启动连接
JMS 规范定义指出,应当在“停止”状态下创建连接。在您可以利用连接发送消息
之前,必须显式启动连接。
我们利用start()方法启动连接,请参见如下的例子:
connection.start();
会话
为生成和使用消息提供上下文,包括用来创建消息生成器和消息使用者的方法。
JMS 会话是生成和使用消息的单线程上下文。尽管它可以在Java 虚拟机之外分配供应
方资源,但我们还是将其看作轻量级JMS 对象。
我们可以分别利用连接对象的createQueueSession()或createTopicSession()方法来创
建会话。
创建会话方法包括两个参数:
1. 可决定会话是否进行事务处理的boolean。
在事务处理的会话中,全部发送、或者全部接收作为一个单位的一组消息。
在非事务处理会话中,分别发送或接收消息。
2. 定义识别模式的参数。
请参见如下的例子:
session =connection.createQueueSession(false ,Session.AUTO_ACKNOWLEDGE);
这是用AUTO_ACKNOWLEDGE 来创建非事务处理会话的最简单的情况。连接是
线程安全的,但会话和由会话创建的对象不是线程安全的。我们建议多线程应用程
序应当对每个线程都使用不同的会话。
会话有以下几种用途:
.. 它是消息生成器和使用者的库;
.. 它提供了供应方优化的消息库;
.. 它既支持单一事务处理,又支持一系列将跨越生成器和使用者的库结合到原子单
位的事务处理;
.. 会话为它使用和生成的消息定义了连续序列;会话保存了它使用的消息,直到识
别该消息;
.. 会话串行化注册到消息使用者的消息侦听器。
会话可以创建并服务于多个消息生成器和使用者。
一种典型的用法就是在消息到达前一直在同步消息使用者上保持线程块。然后线程可以
使用一个或更多的会话消息生成器。
如果一个客户机希望在其他客户机使用消息时具备一个生成消息的线程,那么该客户机
就应当为生成线程使用另外的会话。
一旦启动了连接,任何具有侦听器或注册消息侦听器的会话就会被赋予送达消息的控制
线程。客户机代码从另一个控制线程利用会话或任何构成会话的对象都是错误的。唯一
的例外就是使用会话或连接关闭方法。
大多数客户机将它们的库自然地分成会话应当是比较容易的。该模型允许客户机简单启
动并随着并行操作需要的增加而逐渐增加消息线程的复杂度。
关闭方法是当另一个线程正在执行某个其他会话方法时唯一可以调用的会话方法。
我们可以选择地指定会话为事务处理会话。每个事务处理会话都支持单一系列的事务处
理。每个事务处理会将一系列消息发送和一系列消息接收归组到原子库单元中。事实上,
事务处理将会话的输入消息流和输出消息流组织到原子单位集中。当提交事务处理时,
会确认输入的原子单位,同时发送与其相关联的输出原子单位。如果已经进行了事务处
理回退,那么其发送的消息则被毁坏,会话的输入也被自动恢复。
事务处理输入和输出单位的内容就是会话的当前事务处理中所生成和使用的那些消息。
我们可以利用会话的提交或回退方法完成事务处理。会话的当前事务处理的完成将自动
开始下一个事务处理。其结果就是,事务处理会话总是具有一个当前事务处理,且其库
就在当前事务处理中完成。
消息生成器
JMS 客户机利用消息生成器发送消息到特定的目的地。我们通过传递目的地到会话对象
提供的创建消息生成器方法,从而创建消息生成器。
在点到点消息发送中,这将是利用QueueSession 对象上的createSender 方法所创建的
QueueSender。QueueSender 通常是为特定队列创建的,因此所有利用该发送器发送的消
息都会被发送到同样的目的地。我们利用队列对象指定目的地。队列对象即可以在运行
时间中创建,也可以在JNDI 名称空间中构造和储存。请参见如下的例子:
Queue ioQueue ;
ioQueue = (Queue).ctx.lookup(qLookUp) ;
sender = session.createSender(ioQueue);
在发布/预订消息发送中,这将是在TopicSession 对象上利用createPublisher 方法创建
的TopicPublisher。
通常,Topic 是在创建TopicPublisher 时指定的。在这种情况下,尝试去使用方法,该方
法为未确认的TopicPublisher,将生成UnsupportedOperationException。
请参见如下的例子:
Topic topic ;
topic = (Topic)ctx.lookup( “cn=first.topic”) ;
TopicPublisher pub = session.createPublisher(topic);
客户机也可以选择不提供目的地而创建消息生成器。在这种情况下,目的地必须输入每
个发送操作。这种风格的消息生成器一般用来根据请求的replyTo 目的地向请求发送回
复。
客户机可以通过消息生成器为发送的消息指定默认的送达模式、优先级和使用期限。它
也可以为每条消息指定送达模式、优先级和使用期限。
244
客户机可以为它发送的每条消息指定单位为毫秒的使用期限值。这个值定义了消息到期
时间,也就是消息使用期限的和以及消息发送的GMT 时间(对事务处理发送而言,这
是指客户机发送消息的时间而不是提交事务处理的时间)。
发送消息
消息是用消息生成器发送的。因此在点到点(PTP)模型中发送消息时,您应当使用
QueueSender 对象,而在发布/预订模型中,您则应当使用TopicPublisher 对象。
在PTP 模型中,应使用QueueSender 的send()方法发送消息。请参见如下的例子:
outmessage = session.createTextMessage();
outmessage.setText(“Sample Message “) ;
sender.send(outMessage);
在发布/预订模型中,应使用TopicPublisher 对象的发布方法来发布消息。请参见如下
的例子:
pub.publish(outMessage);
消息使用者
消息使用者是用来接收消息的。MessageConsumer 接口是所有消息使用者的父级接口。
在PTP 模型中,这将是QueueReceiver。在发布/预订模型中,这将是TopicSubscriber。
我们可以用消息选择器来创建消息使用者,消息选择器允许客户机根据消息选择器指定
的标准选择消息子集。欲了解详细信息,请参见第8.7 节《消息选择器》(见本书第289
页)。
消息使用者客户机可以同步接收消息,也可以让消息在到达时异步送达。客户机可以利
用其接收方法之一从消息使用者请求下一条消息。接收有几种不同的形式,允许客户机
登记或等待下一条消息。
客户机可以向消息使用者注册MessageListener 对象。当消息到达消息使用者时,它是通
过调用MessageListeners onMessage 方法来送达消息的。欲了解更多信息,请参见第8.6
节《异步处理》(见本书第285 页)。
245
在PTP 模型中,QueueReceiver 是利用QueueSession 对象上的createReceiver()方法创
建的。该方法采用的是从何处接收消息定义的Queue 参数。请参见如下的例子:
QueueReceiver queueReceiver = session.createReceiver(ioQueue) ;
在发布/预订模型中,TopicSubscriber 对象是利用TopicSession 对象的createSubscriber
()方法创建的。请参见如下的例子:
TopicSubscriber sub = session.createSubscriber(topic);
接收消息
消息是用消息使用者接收的。在PTP 消息发送中,应当用QueueReceiver 对象来接收消
息,而在发布/预订消息发送中,则应当用TopicSubscriber 对象来接收消息。
在PTP 模型中,您可以利用QueueReceiver 对象的接收方法来接收消息。请参见如下的
例子:
Message inMessage = queueReceiver.receive(800) ;
指定的参数是以毫秒为单位的超时。此方法发出调用,以接收指定超时间隔内到达的下
一条消息。
在发布/预订模型中,我们利用TopicSubscriber 的receive()方法来接收预订。请参见
如下的例子:
Message inMsg = sub.receive() ;
这段代码执行了带有等待的获取方法。
重点:请注意,连接是线程安全的,但会话、消息生成器和消息使用者则不是。我们建
议的做法是对每个应用程序线程使用一个会话。
在MQSeries 条款中,连接为临时队列提供了作用域。它也提供了地方,可以用来储存
参数,该参数控制如何连接到MQSeries。举例来说,这些参数就是队列管理器名和远
程主机名(如果您使用MQSeries Java 客户机连接的话)。会话包含HCONN,因此定义
了事务处理作用域。
消息生成器和消息使用者包含HOBJ,它定义了向其写入或从其读取的特定队列。请注
意,正常的MQSeries 规则是适用的。在任何给定时间上,每个HCONN 只能进行一个
单一的操作。因此,不能同时调用消息生成器或与会话相关的消息使用者。
这与JMS 每个会话只有单一线程的限制是一致的。放置方法可以使用远程队列,但获
取方法只能适用于本地队列管理器上的队列。一般JMS 接口是进一步划分为更具体的
版本子集,该子集为点到点以及发布/预订行为。点到点版本如下:
.. QueueConnection QueueSession QueueSender QueueReceiver
JMS 的关键理念就是,它编写的应用程序可以只在javax.jms 中接口引用。
我们强烈建议您这么做。所有各销售商特有的信息都包含在以下实施中:
.. QueueConnectionFactor TopicConnectionFactory
- Queue
- Topic
以上这些称作“管理对象”,即可以利用销售商提供的管理工具构造、且能被储存在JNDI
名称空间中的对象。JMS 应用程序可以从名称空间接收并使用这些对象,而不必知道由
哪个销售商进行实施。MQSeries classes for Java Message Service 由许多Java 类和接口构
成,这些类和接口是以Sun 的接口和类javax.jms 包为基础的。
应当用下面所列的Sun 接口和类编写客户机,我们将在下面各节中详细描述这些接口和
类。可实施Sun 接口和类MQSeries 对象的名称具有“MQ”作为前缀(如果对象描述中
没有做出其它规定的话)。描述包括有关MQSeries 对象与标准JMS 定义存在任何偏差
的细节。
事务处理
会话是在客户机和消息发送系统之间的连接上一系列被发送和接收的消息。当创建会话
时,它可以是非事务处理的(默认)也可以是事务处理的。事务处理会话保证一组消息
要么都被发送接收,要么都不被发送接收。非事务处理会话意味着消息被分别发送和接
收。在线购物就是事务处理会话的一个实例。客户打开订单(开始事务处理)。客户选
择的每项商品都是一条在订单中添加商品的消息。客户关闭订单(结束事务处理)。如
果发送器提交了事务处理,那么将发送组中所有的消息。如果发送器回退事务处理的话,
那么将不订购任何商品项目。
8.3 JMS消息
JMS 提供不同的消息类型。每种类型都包括有关其内容的某些信息。
JMS 中定义的消息类型如下:
.. BytesMessage
.. MapMessage
.. ObjectMessage
.. StreamMessage
.. TextMessage
JMS 消息由以下三部分构成:
.. 标题
标题字段包括供应方用于路由和确认消息的信息,以及客户机可以利用的信息。
.. 属性
除标题中的字段外,JMS 消息还为添加可选的标题字段到消息提供功能。这些属性
可以分为:
- 应用程序特定属性,客户机为处理消息用这些属性添加进程特定信息;
- 标准属性,即JMS 特定属性;
- 供应方特定属性,原始客户机要求这些属性。
.. 主体
这就是消息数据部分的所在。JMS 提供不同类型的消息主体,包括大部分当前正被
使用的消息发送风格。
8.3.1 映射JMS消息到MQSeries消息上
MQSeries 消息有三个组成部分:
.. MQSeries 消息描述器(MQMD);
.. MQSeries MQRFH2 标题;
.. 消息主体。
MQRFH2 标题是可选的。MQRFH2 标题是具有固定部分和可变标题部分的可扩展的标
题。固定部分以标准MQSeries 标题模式为模型。MQRFH2 标题既可以携带与消息内容
相关联的JMS 特定数据,又可以携带不直接与JMS 相关联的其他信息。
我们用两种方法将JMS 消息翻译或转化为MQSeries 消息。
.. 映射
映射是将JMS 消息标题和消息属性翻译到有相应MQMD 字段的相应MQMD 值。
.. 拷贝
如果JMS 消息标题和消息属性值不具有相应的MQMD 字段的话,JMS 标题字段或
属性将被传递,并可能作为MQRFH2 标题中的一个字段被转化。
是否包括MQRFH2 标题是可选的。在发送消息中,JMS 目的地类的标记决定是否包括
它。该标记可以在定义JMS 管理对象时用JMSAdmin 工具来设定。管理员可以设置
MQSeries 目的地的TargetClient 值为JMSC.MQJMS_CLIENT_NONJMS_MQ,从而显示
出JMS 客户机正与非JMS 客户机通讯。由于MQRFH2 标题携带着JMS 特定信息,因
此如果发送器知道接收客户机是JMS 客户的话,您就应当包括它。如果接收器不是JMS
客户机,那么就应当省略MQRFH2 标题。
处理JMS 消息类型
在本节中,我们将利用发货跟踪ID(它作为JMS 客户机的一条消息被发送)讲解可以
如何使用不同的JMS 消息类型。
使用TextMessage 类
消息中的信息可以作为人类可以阅读的文本字符串发送,客户机也可以读取并处理或者
显示该字符串。
我们可以如下将跟踪ID 作为TextMessage 对象中的字符串发送:
String trackingId ;
TextMessage message;
message =session.createTextMessage();
message.SetText(trackingId);
使用BytesMessage 类
在BytesMessage 中,信息是以二进制格式发送的。消息中的信息可以作为字节数组构件。
可以如下准备这样的消息:
byte [] trackingId ;;
BytesMessage message ;
message =session.createBytesMessage();
message.writeBytes(trackingId);
使用映射消息
在映射消息中,信息可以作为名值对发送。这样,消息本身可以包括消息所储存数据的
元数据。
在我们所用的例子中,发送消息请求trackingId 上的信息,名(元数据)可以是trackingId,
实际值(比如说AMX100000)将是名值对的值。
消息可以如下构造:
String attributeName =“trackingId ”;
String attributeValue =“AMX100000 ”
MapMessage message;
message =session.createMapMessage();
message.setString(attributeName,attributeValue);
提示:如果值部分是较长的数据类型的话,那么您可以使用消息setLong 方法。请根据
您处理的数据类型选择合适的方法。
使用流消息
在流消息中,与映射消息相似,消息可以由顺序写入的各种字段构成,每个字段都有其
自己原始的类型。在映射消息中,客户机可以设置映射中任意数量的字段,处理客户机
则可以读取特定的字段而不必处理整个映射。但是,在流消息中,即便处理客户机仅对
流消息中的字段子集感兴趣,客户机仍必须读取消息中的每个字段(再摒弃它不感兴趣
的字段)。此二者的另一个重要差别就是,在映射消息中,字段顺序并不重要,但在流
消息中,字段是以写入顺序读取的。
流消息可以如下构造:
String attributeName =“trackingId ” ;;
String attributeValue =“AMX100000 ” ;;
StreamMessage message;
session.createStreamMessage();
message.writeString(attributeName);
message.writeString(attributeValue);
使用对象消息
对象消息可被用来将Java 对象作为一条消息传递,该消息就是接收客户机可以在对象中
利用方法提取数据的消息。在下面的例子中,我们使用的是跟踪对象:
p
ublic class TrackingObject {
private String attributeName ;
private String attributeValue ;
public void setAttributeName (String name ){
attributeName =name ;
}
public void setAttributeValue(String value){
attributeValue =value ;
}
public String getAttributeName(){
return attributeName;
}
public String getAttributeValue(){
return attributeValue ;
}
}
Using the above tracking object we can sent the tracking information as a
tracking object.(利用以上跟踪对象,我们能发送作为跟踪对象的跟踪消息)
We can construct such a message as:(我们能构造如下的消息)
String attributeName =“trackingId ” ;;
String attributeValue =“ AMX100000” ;
TrackingObject trakingObject =new TrackingObject();
ObjectMessage =message ;
trackingObject.setAttributeName(attributeName);
trackingObject.setAttributeValue(attributeValue);
message =session.createObjectMessage();
message.setObject(trackingObject);
在接收方,客户机可以使用跟踪对象的获取方法来提取数据。
消息识别
如果客户机指定JMS 使用者的消息应被显式识别的话,那么JMS 消息也支持使用识别
方法。如果客户机使用自动识别的话,那么将忽略识别调用。
消息识别有三种类型。消息识别的类型是在会话创建时指定的。这些不同的类型如下:
.. AUTO_ACKNOWLEDGE
在AUTO_ACKNOWLEDGE 模式中,当消息成功从调用返回到接收器,或消息使
用者注册的处理消息的消息侦听器成功返回时,消息会话将自动识别消息。
Session session =queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
.. CLIENT_ACKNOWLEDGE
利用CLIENT_ACKNOWLEDGE 模式,客户机通过调用消息上的识别方法显式并
确认消息。
Session session =queueConnection.createQueueSession(false,
Session.CLIENT_ACKNOWLEDGE);
Then once the message is processed,the client can issue(然后一旦处理完消息,客户机就能发行)
message.acknowledge();
method to acknowledge the message.(方法以确认该消息)
当使用CLINET_ACKNOWLEDGE 模式时,我们在处理消息过程中必须注意避免
大量未识别消息的积累,未识别消息的积累可能导致资源耗尽,带来失败。
.. DUPS_OK_ACKNOWLEDGE
DUPS_OK_ACKNOWLEDGE 模式命令会话迟钝地确认消息送达。如果JMS 失败
的话,其结果就可能是重复消息发送。允许处理重复消息的使用者应当使用这种模
式。在客户机允许重复消息的情况下,利用这种模式可以获取一些效能改善,因为
会话在避免重复消息时的开销较小。
8.3.2 JMS补充特性
JMS 还包括其他一些特性:
.. 异步消息送达
- 使用消息侦听器概念;
- 使用基于事件的模型,它在事先设定的事件上触发指定的函数。
JMS 客户机可以注册侦听器对象,它可以实施具有消息使用者(消息接收器)的
MessageListener 接口。当注册使用者的消息到达时,我们可以调用listeners
onMessage 方法让消息对消息使用者可用。
.. 消息选择器
- 基于内容的特定消息接收
- 使用基于SQL92 的查询函数
JMS 消息具有向JMS 消息标题(在消息实际主体外)提供用户定义元数据的功能。JMS
程序可以利用这一功能根据选择标准选择消息子集,换言之,JMS 客户机可以仅选择那
些自己感兴趣的消息。
8.4 MQSeries JMS实施
用MQSeries 实施JMS 的重点如下:
.. 支持的平台:AIX, HP-UX, Windows NT, Solaris 和 Linux
.. 由包括关键功能的JAR 文件构成
- com.ibm.mq.jms.jar
- com.ibm.mq.jar
.. 定义管理对象到JNDI 名称空间的管理工具:
- JMSAdmin
.. 作为产品延伸可以通过网络下载获取
.. MQSeries classes for Java 和JMS with MQSeries V5.2
8.4.1 MQSeries JMS的安装
为了使JMS 支持MQSeries,您必须安装并实施JMS 接口的Java 类。JMS classes for
MQSeries 可以从下述IBM 网站的MA88 SupportPac 下载获取:
http://www-3.ibm.com/software/ts/mqseries/txppacs/
此SupportPac 是免费的。您可以根据支持的平台下载合适的版本,并按照随SupportPac
提供的安装说明进行安装。
重点:当您在安装JMS SupportPac 时,位于MQSeries 安装的Java 次级目录中的所有文
件都会在安装过程中备份,新文件拷贝自SupportPac。如果在您的上下文中已经安装了
AMI 支持,那么您可能需要重新安装AMI SupportPac。
一旦您已安装了JMS SupportPac,为了用JMS 运行发布/预订应用程序,您还需要运行
称作MQJMS_PSQ.mqsc 的MQSC 脚本,它位于MQSeries 安装目录下的java\bin 次级目
录中。为了在默认的队列管理器上运行脚本,请从操作系统命令行窗口中运行以下命令:
runmqsc <“C:\Program Files \IBM \MQSeries \java \bin \MQJMS_PSQ.mqsc ”
这里,我们假定C:\Program Files \IBM \MQSeries \就是MQSeries 的安装目录。
8.4.2 JMS管理对象——JNDI和JMSAdmin
目录服务包括命名功能,在消息发送语句中提供包括各种实体(机器名、服务、用户以
及MueueManagers, Queues, Topics 等供应方特定对象)安排和确认的抽象层次名称空间。
这就使得我们可以利用逻辑名称空间,简化了网络中的对象发现和识别。Java 命名和目
录接口(JNDI)API 实施向以Java 开发的程序提供了目录和命名功能。这是的Java 程
序可以从JNDI 名称空间发现并接收任何类型的对象。
JMS 管理对象是JMS 应用程序储存并从JNDI 名称空间接收的对象。JMS 管理对象通常
由管理员创建。JMS 管理对象包含底层消息发送服务供应方信息的配置信息。在这节中,
我们将就以MQSeries 开发和应用JMS 应用程序来讨论这些对象的定义、管理和使用。
重点:在程序样例中,我们将使用作为JNDI 服务器的WebSphere 应用程序服务器所提
供的持久名服务器。我们是利用VisualAge for Java 企业版和Websphere application Server
来开发程序样例的。
8.4.3 JMSAdmin工具
管理工具JMSAdmin 使得管理员可以定义MQSeries JMS 对象,并将其储存在JNDI 名
称空间中。JMS 客户机可以利用JNDI 接口从名称空间接收这些管理对象。该工具也允
许管理员操纵JNDI 中的目录名称空间子上下文。
您可以用JMSAdmin 工具管理以下8 个管理对象:
.. MQQueueConnectionFactory
.. MQTopicConnectionFactory
.. MQQueue
.. MQTopic
.. MQXAQueueConnectionFactory
.. MQXATopicConnectionFactory
.. JMSWrapXAQueueConnectionFactory
.. JMSWrapXATopicConnectionFactory
提示: JMSWrapXAQueueConnectionFactory 和JMSWrapTopicConnectionFactory 是
WebSphere 特有的类,包含在com.ibm.ejs.jms.mg 包中。
8.4.4 调用管理工具
管理工具具有命令行接口。您可以互动地利用此接口,也可以用它来启动批处理进程。
互动模式提供命令提示符,您可以在此输入管理命令。在批处理模式中,启动工具的命
令包括包含管理命令脚本的文件名。
为了在互动模式下启动该工具,请输入以下命令:
JMSAdmin [-t ] [-v ] [-cfg config_filename ]
在这里:
.. -t 启动跟踪(默认情况下为跟踪关闭)
.. -v 生成详细输出(默认情况下为简洁输出)
.. -cfg config_filename 为备用配置文件名。
8.4.5 JMSAdnub工具配置
JMSAdmin 工具在其中的以下参数使用配置文件:
.. INITIAL_CONTEXT_FACTORY
这显示出工具使用的服务供应方。现在该属性有3 个被支持的值:
- com.sun.jndi.ldap.LdapCtxFactory
当您使用LDAP 服务器作为JNDI 服务器时,您需要使用此类名。(在我们的测
试中,我们将使用IBM Secureway Server 3.2.1。)
- com.sun.jndi.fscontext.RefFSContextFactory
当您在使用文件系统储存JNDI 对象时,应当使用此类名。
- com.ibm.ejs.ns.jndi.CNInitialContextFactory
当您在使用Websphere 的持久名服务器时,您应当使用此类名。
.. PROVIDER_URL
这显示出会话初始上下文的URL,即该工具执行的所有JNDI 操作的根。现在支持
该属性的有3 种形式:
- ldap://hostname/contextname (针对LDAP)
- file:[drive:]/pathname (针对文件系统上下文)
该工具不会创建路径指定的目录。您应当在使用JMSAdmin 工具前确保目录存
在。
- iiop://hostname[:port] /[?TargetContext=ctx] (针对WebSphere Persistent
Name Server)
.. SECURITY_AUTHENTICATION
这显示出JNDI 是否传递安全证明到您的服务供应方。只有在使用LDAP 服务供应
方时才会用到该参数。该参数现在可以具有以下三个值之一:
- None(匿名识别)
- Simple(简单识别)
- CRAM-MD5(CRAM-MD5 识别机制)
以上的值指定在一个配置文件中,您可以在以-cfg 参数调用JMSAdmin 工具时指定配
置文件。称作jmsadmin.config 的配置文件样例位于MQSeriesInstalldirectory\java\bin,您
可以编辑它或使用它,以适应上下文的属性值来创建配置文件。
8.4.6 以持久名服务器使用JMSAdmin
编辑随安装带来的JMSAdmin.config 文件(您可以在MQSeriesInstalldirectory\java\bin 下
找到该文件),指示JMSAdmin 工具应当使用持久名服务器,并编辑PROVIDER_URL
属性值,以指向持久名服务器。而后在另一个文件夹中保存文件,例如C:\temp。
在JMSAdmin.config 文件中,我们提供如下值:
.. INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
该值对VisualAge for Java 企业版和WebSphere 应用程序服务器提供的持久名服务
器有效。对另一个JNDI 服务器而言,INITIAL_CONTEXT_FACTORY 变量必须随
配套文档提供。
.. PROVIDER_URL=iiop://hostname/
在此,hostname 即持久名服务器运行的机器名。当使用IIOP 协议时,默认端口为
900,不必提及。如果您改变持久名服务器的默认端口(默认端口为900)的话,那
么您必须在PROVIDER_URL 变量中指示端口,例如:iiop://hostname:901/(这
里假定您将默认端口900 改为901 端口)。
利用-cfg 参数调用JMSAdmin 工具,并给出您在编辑后保存JMSAdmin.config 文件的
路径。
8.4.7 以VisualAge for Java使用持久名服务器
持久名服务器的数据库应当可以利用JDBC 从VisualAge for Java 访问到。如果是第一次
设置持久名服务器的话,您将需要创建数据库(我们利用UDB7.1 作为数据库服务器)。
我们创建了称作jndi 的数据库。为了让持久名服务器利用JDBC 访问数据库,VisualAge
for Java 库空间类路径也应当包括Db2UdbInstallDirectory\java\db2java.zip。
从VisualAge for Java 启动持久名服务器
.. 从VisualAge for Java 菜单栏选择Workspace - > Tools - >Websphere Test
Environment(见图8-2)。
图8-2 从VisualAge for Java 步骤一启动持久名服务器
.. 在左手边的窗格中选择持久名服务器并输入参数以连接到持久名服务器的配置数
据库,请看图8-3;
图8-3 持久名服务器配置画面
重点:在默认情况下,持久名服务器使用端口号900。如果您已经指定了不同的端口的
话,那么为JNDI lookup 使用名服务器的程序就应当包括PROVIDER_URL 参数中的端
口号。
.. 点击Start Name Server 按钮启动持久名服务器。
8.4.8 为配合使用JMS配置VisualAge for Java
设置VisualAge for Java 与JMSAdmin 配合库或开发JMS 应用程序的相关步骤如下:
.. 校验IBM 企业版库和IBM WebSphere 监测上下文已加载入库空间;
.. 调入以下JAR 文件到项目中。可以在MQInstallationDirectory\Java 下找到这些JAR
文件。我们在VisualAge for Java 中已创建了名为JMSTest 的新项目,该项目将用
于以下的实例。
- com.ibm.mq.jar
- com.ibm.mqbind.jar
- com.ibm.mqjms.jar
- com.ibm.mq.iiop.jar
- jms.jar
- jndi.jar
- ldap.jar
- fscontext.jar
- providerutil.jar
.. 添加MQSeriesInstallDirectory\java\lib 目录到VisualAge for Java 库空间类路径。我
们可以通过选择Window -> Options->Resources -->Edit 和添加目录来实现
这一目的(请参见图8-4)。
图8-4 设置VisualAge for Java 库空间类路径
.. 如果您希望在访问队列管理器时使用绑定模式的话,那么路径系统环境变量就应
当包括MQSeriesInstallDirectory\Java\bin 目录。该目录包括mqjbnd02.dll,在使用
绑定模式时要求具有该文件。
8.4.9 用JMSAdmin配合VisualAge for Java管理JMS JNDI对象
在本节中,我们将讲解如何定义和管理用MQSeries JMS 管理工具JMSAdmin 配合
VisualAge for Java 实施JMS 时所需的JNDI 对象。
我们将在本章下面的编程样例中介绍如何使用工具处理对象,既包括点到点模式,又包
括发布/预订消息发送模式。
被使用的MQSeries 对象
表8-1 列出了程序样例中用到的MQSeries 对象。
表8-1 程序样例中用到的MQSeries 对象
对象名 描述
SAMPLE.QMGR1 程序样例中将要使用的队列管理器
PTP.QUEUE.LOCAL 点到点样例中使用的队列
PTP.REPLY.QUEUE.LOCAL 请求/回复发送回复消息所用的队列
JMS.SVR.CHNL 客户机连接所用的服务器连接通道
SYSTEM.JMS.ADMIN.QUEUE JMS 发布/预订管理队列
SYSTEM.JMS.PS.STATUS.QUEUE JMS 发布/预订状态队列
SYSTEM.JMS.REPORT.QUEUE JMS 发布/预订报表队列
SYSTEM.JMS.MODEL.QUEUE JMS 发布/预订预订器模型队列。(预订器使
用该模型队列为预订创建永久队列)
SYSTEM.JMS.ND.SUBSCRIBER.QUEUE JMS 发布/预订默认非可持续共享队列(非可
持续预订器使用的默认共享队列)
SYSTEM.JMS.ND.CC.SUBSCRIBER.QUEUE JMS 发布/预订ConnectionConsumer 功能的默
认非可持续共享队列
SYSTEM.JMS.D.SUBSCRIBER.QUEUE JMS 发布/预订默认可持续共享队列(可持续
预订器使用的默认共享队列)
SYSTEM.JMS.D.CC.SUBSCRIBER.QUEUE JMS 发布/预订ConnectionConsumer 功能的默
认可持续共享队列
261
8.4.10 定义JMS管理对象
首先,我们要实施点到点程序样例使用的管理对象。我们将用作为JNDI 名服务器的持
久名服务器库,并从VisualAge for Java 调用JMSAdmin 工具。您应当设置VisualAge 来
用JMS 库,持久名服务器应当以第8.4.7 节《以VisualAge for Java 使用持久名服务器》
(见本书第256 页)和第8.4.8 节《为配合使用JMS 配置VisualAge for Java》(见本书第
258 页)介绍的方法配置和启动。
从VisualAge for Java 调用JMSAdmin 工具
.. 更新JMSAdmin.config 文件指示JMSAdmin 将编辑初始上下文库属性值以使用持
久名服务器。
INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
提示:在LDAP 服务器中,它将是com.sun.indi.ldap.LdapCtxFactory,而在文件系
统上下文中,值将是com.sun.jndi.fscontext.RefFSContextFactory。
编辑PROVIDER_URL,以指向JNDI 名服务器。在这种情况下,我们是在名为ITSOG
的服务器上运行持久名服务器,因此它将是:
PROVIDER_URL=iiop://itsog/
.. 拷贝上述您正在处理项目(我们正在使用名为JMSTest 的项目)的项目资源中的
JMSAdmin.config 文件。我们可以通过以下方法从VisualAge for Java 实现此目的:
- 在资源标签上右键点击项目,再点击Add --> Resources,请看图8-5。
262
图8-5 向VisualAge 项目添加资源(第一步)
.. 选择JMSAdmin.config 文件所在的目录,点击OK 按钮(见图8-6)。
263
图8-6 选择资源目录
.. 在图8-7 所示的添加资源窗口选择JMSAdmin.config 文件并点击OK。
264
图8-7 添加JMSAdmin 配置文件资源
.. 扩展您的项目,并定位JMSAdmin 类(它在com.ibm.mq.jms.admin 包中)。右键点
击JMSAdmin 类,再选择Properties(见图8-8)。
265
图8-8 为JMSAdmin 设置类路径(步骤一)
.. 在随后生成的JMSAdmin 属性窗口中(见图8-8),选择Classpath 标签,而后点
击Edit 并选择IBM 企业扩展库和IBM WebSphere 测试上下文,再点击OK。
266
图8-9 JMSAdmin 的类路径
JMSAdmin 设置至此完成。现在,我们就可以运行了。选中JMSAdmin 类,并且点击
Run 按钮来运行工具。启动成功时控制台将如图8-10 所示。
267
图8-10 JMSAdmin 控制台
重点:如果您启动JMSAdmin 工具遇到了问题,那么请校验类路径设置是正确的,且持
久名服务器已经启动。
现在,我们就可以在JNDI 创建JMS 管理对象了。
1. 创建上下文。我们使用JMSAdmin 命令DEFINE CONTEXT(context)名来为称作
ptpCtx 的上下文(它将用于点到点程序样例中)创建上下文。
在控制台标准输入区(见第268 页上的图8-11),输入命令:(283)
def ctx(ptpCtx)
268
图8-11 使用JMSAdmin 工具创建JNDI 上下文
为了显示您刚刚定义的上下文,请使用dis ctx 命令。在显示上下文时,您应当可以看到
您刚刚创建的上下文。请见图8-12。
269
图8-12 JMS admin 工具中的上下文
2. 利用以下命令改变到您刚刚创建的上下文:
chg ctx(ptpCtx)
3. 用下面的命令创建名为ptpQcf 的QueueConnection 库:
def qcf(ptpQcf) transport(CLIENT)+
channel(JMS.SRV.CHNL) qmanager(SAMPLE.QMGR1) host(ITSOG)
4. 如下创建名为ptpQcf 的队列对象:
def q(ptpQueue) queue(PTP.QUEUE.LOCAL)+
qmanager(SAMPLE.QMGR1)
您可以利用dis ctx 名校验在当前上下文下创建的对象。您应当可以看到
QueueConnecitonFactory(ptpQcf)和Queue(ptpQueue)对象,请见图8-13。
270
图8-13 显示上下文
8.5 JMS应用程序开发
JMS 应用程序使用消息发送的点到点(PTP)或发布/预订风格。没有什么会阻止这两
种风格结合到单一的应用程序中;但是,JMS 集中在使用二者之一的应用程序上。JMS
定义了这两种风格,因为它们代表着当前使用的消息发送的两种主要方法。由于许多消
息发送系统仅支持二者之一,因此JMS 为每种风格都提供了不同的域,并为每个域定
义了兼容。
8.5.1 JMS点到点(PTP)模型
点到点消息发送包括消息队列处理。发送器发送消息到通常由单一接收器使用的特定队
列。在点到点通讯中,消息最多只有一个接收器。发送客户机发送消息到包含目的(接
收)客户机消息的队列。您可以将队列看作信箱。许多客户机可能发送消息到队列,但
消息仅由一个客户机取出。而且,正如信箱一样,消息直到删除前会一直保存在队列中。
因此,接收客户机的可用性不影响发送消息的能力。在点到点系统中,客户机可以是发
送器(消息生成器)、接收器(消息使用者)或二者都是。
271
8.5.2 点到点消息发送的编程方法
图8-14 显示了在JMS 中开发点到点消息发送程序的高级步骤:
图8-14 JMS PTP 编程方法概述
272
点到点模型中的JMS 接口如下:
.. QueueConnection
.. QueueSession
.. QueueSender
.. QueueReceiver
当使用JMS 时,连接不是直接生成的,而是利用连接库构建的。这些库对象可以储存
在JNDI 名称空间中,从而隐藏了销售商的特定实施。
在点到点消息发送中,一般有两种消息发送模式,如图8-15 所示。第一种方法是发送
-遗忘模型,第二种方法为请求/回复模式。
图8-15 点到点消息发送模型
8.5.3 发送-遗忘
利用发送-遗忘模式(或发射-忘记模式)时,不期待从(数据报)消息接收器获取回
复。
简单消息生成器应用程序
我们的第一台PTP 客户机将创建一条文本消息,并将其发送到MQSeries 队列。我们还
将讲解处理发送器发送的应用程序的接收器程序。有关步骤如下:
273
.. 在JNDI 名称空间查找QueueConnectionFactory 和Queue;
.. 获取Queue Connection 对象;
.. 创建QueueSession;
.. 创建QueueSender;
.. 创建TextMessage;
.. 发送消息到Queue;
.. 关闭以及断开与连接对象的连接。
程序PtpSender.java 显示了有关开发点到点消息发送应用程序的有关步骤。程序发送一
条简单文本消息至MQSeries 队列。
例8-1 PtpSender.java
Step 1 Import Necessary Packages(步骤一,导入所需的软件包)
import java.util.*;
import javax.jms.*;
import javax.naming.directory.*;
import javax.naming.*;
public class PtpSender {
public static void main(String[] args) {
String queueName = "ptpQueue";
String qcfName = "ptpQcf" ;
Context jndiContext = null;
QueueConnectionFactory queueConnectionFactory = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
Queue queue = null;
QueueSender queueSender = null;
TextMessage message = null;
String providerUrl = "iiop://itsog:900/ptpCtx" ;
String initialContextFactory = "com.ibm.ejs.ns.jndi.CNInitialContextFactory";
/**
Step 2 set up an Initial Context for JNDI lookup(步骤二,建立初始上下文以便JNDI 查找)
*/
try{
Hashtable env = new Hashtable() ;
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory) ;
env.put(Context.PROVIDER_URL , providerUrl );
//env.put(Context.REFERRAL, "throw") ;
jndiContext = new InitialDirContext(env);
/**
Step 3 get a QueueConnectionFactory. We will retrieve the
QueueConnectionFacotry object named ptpQcf created in Persistent Name Server
using JMSAdmin tool.(步骤三,获取QueueConnectionFactory。我们将接收QueueConnectionFactory 对象
指定的使用JMSAdmin 工具在Persistent Name Server 里创建的ptpQcf。)
*/
queueConnectionFactory = (QueueConnectionFactory)jndiContext.lookup(qcfName);
274
/**
Step 4 the Queue object from the JNDI namespace.(步骤四,来自JNDI 名称空间的队列对象)
*/
queue = (Queue)jndiContext.lookup(queueName);
/**
Step 5 Create a QueueConnection from the QueueConnectionFactory(从队列连接库创建队列连接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/**
Step 6 Start the QueueConnection.(步骤六,启动队列连接)
*/
queueConnection.start();
/**
Step 7 Create a QueueSession object from the QueueConnection(步骤七,从队列连接创建队列会话)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
Step 8 Create a QueueSender object for sending messages from the queue session.(步骤八,创建队列发送器
以便从队列会话发送消息)
*/
queueSender = queueSession.createSender(queue);
/**
Step 9 prepare a message object from the queuesession. we will create a
textMessage message object.(步骤九,从队列会话准备消息对象。我们将创建textMessage 消息对象。)
*/
message = queueSession.createTextMessage();
/**
Step 10 Set the message you want, to the message object.(步骤十,设置您想要的消息到消息对象。)
*/
message.setText("This is a Test Message from PtpSender Class " ) ;
/**
Step 11 Now we are ready to send the message.(步骤十一,现在我们已准备好发送消息。)
*/
queueSender.send(message);
System.out.println(“\n The Message has been sent”);
/**
Step 12 Close the Queue Connection Before exiting from the program.(步骤十二,在从程序退出前关闭队列
连接)
*/
queueConnection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
275
简单消息使用者应用程序
我们的下一个客户机应用程序是消息接收器应用程序,它获取PtpSender 应用程序发送
的消息并打印出控制台上的消息。有关步骤如下:
.. 在JNDI 名称空间中查找QueueConnectionFactory 和Queue;
.. 获取Queue Connection 对象;
.. 创建QueueSession;
.. 创建QueueReceiver;
.. 接收消息并显示它;
.. 关闭以及断开与连接对象的连接。
程序PtpReceiver.java 显示了创建点到点消息使用者应用程序的有关步骤。应用程序从
MQSeries 队列获取消息并显示消息。
例8-2 PtpReceiver.java
//Step 1 Import the Necessary Packages(步骤一,导入所需的软件包)
import java.util.*;
import javax.jms.*;
import javax.naming.directory.*;
import javax.naming.*;
public class PtpReceiver {
/**
*The Main Method.
* @param no args
*/
public static void main(String[] args) {
String queueName = "ptpQueue";
String qcfName = "ptpQcf" ;
Context jndiContext = null;
QueueConnectionFactory queueConnectionFactory = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
Queue queue = null;
QueueReceiver queueReceiver = null;
TextMessage message = null;
/* Provider url
/For Persistent Name Server- iiop://iiopservername/contextname
/For LDAP Server use ldap//cn=ContextName,o=OrganizationalSuffix,c=coutrysuffix
eg. ldap://machineName/cn=ptpCtx,o=itso,c=uk
*/
String providerUrl = "iiop://itsog/ptpCtx" ;
String initialContextFactory = "com.ibm.ejs.ns.jndi.CNInitialContextFactory";
276
/**
* Step 2 set up Initial Context for JNDI lookup(步骤二,建立初始上下文以便JNDI 查找)
*/
try{
Hashtable env = new Hashtable() ;
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory) ;
env.put(Context.PROVIDER_URL , providerUrl );
//env.put(Context.REFERRAL, "throw") ;
jndiContext = new InitialDirContext(env);
/**
* Step 3 get the QueueConnectionFactory from the JNDI Namespace(步骤三,从JNDI 名称空间获取队列连
接库)
*/
queueConnectionFactory = (QueueConnectionFactory)jndiContext.lookup(qcfName);
/**
* Step 4 get the Queue Object from the JNDI Name space(步骤四,从JNDI 名称空间获取队列对象)
*/
queue = (Queue)jndiContext.lookup(queueName);
/**
* Step 5 Create a QueueConnection using the QueueConnectionFactory(步骤五,利用队列连接库创建队列连
接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/*
*Step 6 Connections are always created in stopped mode. You have to Explicitly
start them. Start the queueConnection(步骤六,在停止模式里创建连接。您必须明确的启动他们。启动队
列连接。)
*/
queueConnection.start();
/**
* Step 7 Create a queueSession object from the QueueConnection(步骤七,从队列连接创建队列会话对象)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
* Step 8 Create a QueueReceiver from the queueSession(步骤八,从队列会话创建队列接收器)
*/
queueReceiver = queueSession.createReceiver(queue);
/**
* Step 9 Receive Messages. Here we are implementing a synchronous message
receiver. The receive call is in a loop so that it would process all the
available messages in the queue(步骤九,接收消息。在此我们实施同步消息接收。循环进行接收调用从而
能在队列中处理所有可用消息。)
*/
boolean eom = true;
while (eom) {
Message m = queueReceiver.receive(1);
if (m != null) {
if (m instanceof TextMessage) {
message = (TextMessage) m;
System.out.println("Reading message: " +
message.getText()); }
else {
277
break;
}
}
else eom = false;
}
/**
* Step 10 Close the connections(步骤十,关闭连接)
*/
queueConnection.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
8.5.4 请求/回复
在请求/回复消息发送中,接收器接受请求消息后将发送回复到发送器。在接收器方,
准备消息与PTP 中一样,但除此之外JMSReplyToQueue 值将设置发送器期待从接收器
回复的Queue Name。当获取消息时,接收器可以使用回复到队列名来发送回复消息至
发送器。
JMS 提供JMSReplyTo 消息标题字段,指定应当发送消息回复的目的地。JMSCorrelationId
标题字段可以用在回复消息中,提供到请求消息的引用。
请求/回复消息发送的应用程序设计考虑
MQSeries 是种异步通讯机制。在请求/回复消息发送中,客户机将消息放入队列并等
待回复时,在应用程序设计层次上必须小心。您不会愿意在队列上无限制地等待回复消
息的到达,因为您并不知道您的请求获取回复要花多长时间。应用程序设计应当考虑到
延迟回复和根本无回复的情况。您也可能希望在等待回复消息时考虑设置合适的超时
值。
如果在等待回复消息时使用超时的话,那么您可能希望在生成回复消息的远程客户机方
考虑采用合适的过期时间值。在许多请求/回复情况下,您可以考虑为提高效能而使用
非持久消息。使用非持久消息将会带来显著的效能改善。
278
非持久消息可以用三种方法发送。持久属性可以用以下方法之一设置:
.. 在创建MQSeries 队列时直接在队列管理器中的队列对象上设置,或者您也可以在
现有MQSeries Queue 上改变它。
.. 在JNDI 中利用JMSAdmin 工具在队列JMS 管理对象上设置。参数PERSISTENCE
可用来指定您希望设置的持久值。不同的值如下:
- APP:应用程序定义的持久值(这是默认的)
- QDEF:MQSeries 队列定义的持久值
- PERS:持久的消息
- NON:非持久的消息
如果您希望以持久NON 定义队列对象名,那么您可以使用JMSAdmin 来指定命令:
DEFINE Q(ptpQueue) PERSISTENCE(NON)
.. 在JMS 应用程序中对每条消息都使用DelieveryMode.NON.PERSISTENT 字段。消
息持久性可以在QueueSender 上调整,或者也可以在发送方法调用上指定,但不能
直接在消息上指定,请参见如下的例子:
QueueSender sender = queueSession.createSender(queue) ;
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
或者您也可以在发送消息时通过方法调用指定送达模式、优先级和使用期限等来设
置送达模式。
8.5.5 JMS发布/预订模型
与点到点通讯模型不同,发布/预订模型可以让一条消息送达多个接收器。发送客户机
指引或发布消息至多个客户机可以预订的主题。一个主题可以有多个发布器和多个预订
器。持久预订或兴趣在客户机关闭和重启时都存在。当客户机关闭时,将储存所有已送
达主题的对象并在客户机重新预订时发送到客户机。在发布/预订系统中,客户机可以
是发布器(消息生成器)、预订器(消息使用者)或二者都是。
JMS 发布/预订模型定义了在基于内容的层次中JMS 客户机如何发布消息至已知的节
点以及如何从已知的节点预订消息。JMS 将这些节点称作“主题”。
279
在本节中,我们使用的是发布和预订这两个术语,而不采用前面用到的生成和使用这两
个一般术语。我们可以把主题看作微型消息中介,它收集并分配指向它的消息。消息发
送器依靠主题作为中间体从而独立于预订器,反过来也是如此。主题自动适应发布器和
预订器到来和离去。当代表发布器和预订器的Java 对象存在时,发布器和预订器是活动
的。
JMS 也支持预订器可选的持久性,并在其不活动时“记住”其存在。MQSeries 发布/
预订使得应用程序不必知道有关目标应用程序的任何信息。它需要做的就是把它希望分
享的信息发送到MQSeries 发布/预订管理的标准目的地,并让MQSeries 发布/预订处
理分配。同样,目标应用程序不必了解其接收的信息来源的任何信息。
图8-16 显示了用JMS 开发发布/预订应用程序的有关步骤。
280
图8-16 JMS 发布/预订编程概述
在JMS 实施发布/预订消息发送模型中,所有特定销售商实施都可以通过在java.jms
中的以下接口引用。所有这些均包括在以下接口的实施中:
.. QueueConnectionFactory
.. TopicConnectionFactory
.. Queue
.. Topic
在JMS 发布/预订模型中,预订主题时就可以实施主题异步预订。
281
简单JMS 发布/预订应用程序
在本节中,我们将用样例程序JMSPublisher.java 来讲解开发发布应用程序的有关步骤。
编写JMS 发布应用程序的有关步骤如下:
1. 定义JMS 管理对象:
a. 创建JNDI 上下文。
我们使用以下命令来创建用于样例程序中的名为psCtx 的JNDI 上下文:
DEF CTX(psCtx)
b. 利用以下命令改变到您刚刚创建的上下文中:
CHG CTX(psCtx)
c. 创建TopicConnectionFactory。
以下命令将创建指向QueueManager ITSOG.QMGR1(它位于名为ITSOG 的主
机上)的名为psTcf 的TopicConnectionFactory,它在默认端口1414 上进行监
听。用于客户机连接的服务器连接为JMS.SRV.CHNL:
DEF TCF(psTcf) TRANSPORT(CLIENT) QMANAGER(ITSOG.QMGR1) HOST(ITSOG)
PORT(1414) CHANNEL(JMS.SRV.CHNL) BROKERQMGR(ITSOG.QMGR1)
BROKERCONQ(SYSTEM.BROKER.CONTROL.QUEUE)
BROKERPUBQ(SYSTEM.BROKER.DEFAULT.STREAM)
BROKERSUBQ(SYSTEM.JMS.ND.SUBSCRIBER.QUEUE)
BROKERCCSUBQ(SYSTEM.JMS.ND.CC.SUBSCRIBER.QUEUE)
d. 定义JNDI 主题。
以下命令为根主题JmsTest 下的名为SampleTopic 的主题创建名为psTopic 的
Topic。
DEF CTX(psCtx) TOPIC(JMSTest/SampleTopic)
2. 在JNDI 名称空间查找TopicConnectionFactory 和Topic;
3. 创建Topic Connection;
4. 创建TopicSession;
5. 创建TopicPublisher;
6. 创建TextMessage;
7. 发布消息至Topic;
8. 关闭以及断开与连接对象的连接。
282
我们的第一个JMS 发布/预订客户机程序JMSPublisher.java 将创建一条文本消息并发
送它到“SampleTopic”主题,它位于根主题JMSTest 之下。我们也将讲解预订主题
“TestTopic”的预订器程序。
例8-3 JMSPublisher.java
//Step 1 Import the necessary packages(步骤一,导入所需的软件包。)
import java.util.*;
import javax.jms.*;
import javax.naming.directory.*;
import javax.naming.*;
public class JMSPublisher {
/**
*The main method
*@param no args
*/
public static void main(String[] args) {
String topicName = "cn=psTopic";
String tcfName = "cn=psTcf" ;
Contex jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
Topic topic = null;
TopicPublisher publisher = null;
TextMessage message = null;
String providerUrl = "ldap://itsog/cn=psCtx,o=itsog,c=uk" ;
String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
//Step 2 Set up an Initial context for JNDI lookUp.(步骤二,建立初始上下文以便JNDI 查找。)
try {
Hashtable env = new Hashtable() ;
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory) ;
env.put(Context.PROVIDER_URL , providerUrl );
jndiContext = new InitialDirContext(env);
//Step 3 Obtain a TopicConnection factory(步骤三,获取主题连接库。)
topicConnectionFactory =
(TopicConnectionFactory)jndiContext.lookup(tcfName);
//Step 4 Create a Topic Connection using the connection factory object(步骤四,使用连接库对象创建主题连
接。)
topicConnection = topicConnectionFactory.createTopicConnection();
//Step 5 Start the topic connection.(步骤五,启动主题连接。)
topicConnection.start();
//Step 6 Obtain a Topic from the JNDI(步骤六,从JNDI 获取主题。)
topic = (Topic)jndiContext.lookup(topicName);
//Step 7 Create a Topic Session from the topic connection(步骤七,从主题连接创建主题会话。)
283
topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
//Step 8 Create a topic publisher for the topic from the session.(步骤八,从会话为主题创建主题发布者。)
publisher = topicSession.createPublisher(topic);
//Step 9 Create a message object(步骤九,创建消息对象。)
message = topicSession.createTextMessage();
//Step 10 prepare the body of the message(步骤十,准备消息主体。)
message.setText("This is a Test Message from JMSPublisher Class " ) ;
//Step 11 Publish the message.(步骤十一,发布消息。)
publisher.publish(message);
//Step 12 Close the connections.(步骤十二,关闭连接。)
publisher.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
简单的JMS 预订器应用程序
在本节中,我们将通过创建一个样例程序来讲解开发JMS 预订器应用程序的有关步骤。
编写JMS 预订器应用程序的有关步骤如下:
1. 在JNDI 名称空间查找TopicConnectionFactory 和Topic;
2. 创建Topic Connection;
3. 创建TopicSession;
4. 创建TopicSubscriber;
5. 从Topic 接收预订;
6. 关闭并断开与连接对象的连接。
程序JMSSubscriber.java 是从“SampleTopic”主题(它位于根主题JMSTest 下)预订消
息的简单预订器应用程序。我们在例子中使用的是非可持续性预订。
例8-4 JMSSubscriber.java
//Step 1 Import the necessary packages.(步骤一,导入所需软件包。)
import java.util.*;
import javax.jms.*;
import javax.naming.directory.*;
import javax.naming.*;
public class JMSSubscriber {
/**
* The main method
284
* @param no args
*/
public static void main(String[] args) {
String topicName = "cn=psTopic";
String tcfName = "cn=psTcf" ;
Context jndiContext = null;
TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
Topic topic = null;
TopicSubscriber subscriber = null;
TextMessage message = null;
String providerUrl = "ldap://itsog/cn=psCtx,o=itsog,c=uk" ;
String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
//Step 2 Set up Initial Context for JNDI lookup(步骤二,建立初始上下文以便JNDI 查找。)
try{
Hashtable env = new Hashtable() ;
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory) ;
env.put(Context.PROVIDER_URL , providerUrl );
env.put(Context.REFERRAL, "throw") ;
jndiContext = new InitialDirContext(env);
//Step 3 Get the TopicConnection factory from the JNDI Namespace(步骤三,从JNDI 名称空间获取主题
连接库。)
topicConnectionFactory = (TopicConnectionFactory)jndiContext.lookup(tcfName);
//Step 4 Create a TopicConnection(步骤四,创建主题连接。)
topicConnection = topicConnectionFactory.createTopicConnection();
//Step 5 Start The topic connection(步骤五,启动主题连接。)
topicConnection.start();
//Step 6 Create a topic session from the topic connection(步骤六,从主题连接创建主题会话。)
topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
//Step 7 Obtain a Topic from the JNDI namespace(步骤七,从JNDI 名称空间获取主题。)
topic = (Topic)jndiContext.lookup(topicName);
//Step 8 Create a topic subscriber for the topic.(步骤八,为主题创建主题订户。)
subscriber = topicSession.createSubscriber(topic);//Non durable subscriber(非持久预定者)
//Step 9 Receive Subscription(步骤九,接收预订。)
message = (TextMessage)subscriber.receive();
System.out.println("\n *** The Message is " + message.getText());
//Step 10Close the connection and other open resources(步骤十,关闭连接和其它打开资源。)
subscriber.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
285
8.6 异步处理
MQSeries JMS 提供了人们广泛期待的MessageListener 接口。利用这个消息侦听器功能,
客户机就可以注册侦听器对象到消息使用者。当消息到达使用者时,它通过调用
onMessage 方法送达至客户机。
这是除利用其他方法(如触发器、以等待发出接收或预订调用或者在应用程序代码中采
用登记机制)外检查新消息或预订的另一种方法。当用侦听器使用异步送达模式时,与
消息使用者相关联的整个会话都标志为异步的。相同的会话不能用来发出任何显式接收
调用。
因为应用程序不发出显式接收调用,因此在异步消息送达中,应用程序代码可能不能获
取接受消息失败所产生的例外。MQSeries JMS 提供了利用ExceptionListener 接口获取异
常的功能。当出现异常时,将调用onException 方法,而JMSException 将作为其参数传
递到方法。
8.6.1 消息侦听器
我们通过实施MessageListener 接口并在侦听器的onMessage 方法中提供应用程序特定处
理来创建消息侦听器。我们将讨论如何在消息使用者应用程序中实施简单的消息侦听
器。
我们首先创建侦听器类(PTPListener.java),其扩展了JMSMessageListener 类并对
onMessage 方法进行了实施。当消息到达时,消息作为自变量被送达至onMessage 方法。
当消息到达时,我们就显示消息。
例8-5 PtpListener.java
import java.io.*;
import javax.jms.*;
public class PtpListener implements MessageListener {
/**
* onMessage.
*/
public void onMessage( Message message) {
try {
if (message instanceof TextMessage) {
System.out.println( ((TextMessage)message).getText());
286
}
} catch (JMSException e) {
e.printStackTrace( );
}
}
}
在PtpAsyncConsumer.java 中,我们将讲解消息使用者如何注册侦听器,从而使用消息
异步处理。PtpAsyncConsumer.java 类将在PtpListener.java 中使用侦听器来实施异步处理
消息。您可以与我们在简单发送器应用程序中介绍的PtpSender 应用程序一起来运行
PtpAsyncConsumer。
例8-6 PtpAsyncConsumer.java
import java.util.*;
import javax.jms.*;
import javax.naming.directory.*;
import javax.naming.*;
public class PtpAsyncConsumer{
String queueName = "cn=ptpQueue";
String qcfName = "cn=ptpQcf" ;
Context jndiContext = null;
QueueConnectionFactory queueConnectionFactory = null;
QueueConnection queueConnection = null;
QueueSession queueSession = null;
Queue queue = null;
QueueReceiver queueReceiver = null;
String providerUrl = "ldap://itsog/cn=ptpCtx,o=itsog,c=uk" ;
String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
public static void main(java.lang.String[] args) {
try {
PtpAsyncConsumer asyncConsumer = new PtpAsyncConsumer() ;
asyncConsumer.performTask();
} catch (Exception e ){
e.printStackTrace();
}
}
/**
* Method performTask Control the flow of control of the logical operations(方法performTask 可控制逻辑操
作的控制流)
287
*/
public synchronized void performTask() throws Exception {
System.out.println("\n Setting Up Initial JNDI Context ");
Hashtable env = new Hashtable() ;
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory) ;
env.put(Context.PROVIDER_URL , providerUrl );
env.put(Context.REFERRAL, "throw") ;
jndiContext = new InitialDirContext(env);
System.out.println("\n Get QueueConnectionFactory ");
queueConnectionFactory =
(QueueConnectionFactory)jndiContext.lookup(qcfName);
System.out.println("\n Get Queue ");
queue = (Queue)jndiContext.lookup(queueName);
System.out.println("\n Create Queue Connections ");
queueConnection = queueConnectionFactory.createQueueConnection();
System.out.println("\n Start Queue Connection ");
queueConnection.start();
System.out.println("\n Create Queue Session ");
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
System.out.println("\n Create Queue Receiver ");
queueReceiver = queueSession.createReceiver(queue);
System.out.println("\n Register the Listener");
PtpListener yahoo = new PtpListener();
queueReceiver.setMessageListener(yahoo);
//Wait for new messages.(等待新消息。)
wait();
}
}
8.6.2 异常侦听器
当以消息侦听器利用异步消息送达模型时,应用程序代码不能接到接收消息失败产生的
异常。这是因为应用程序代码没有显式调用receive()或subscribe()方法。为了在异
步处理中接到错误,您可以注册ExceptionListener,它是实施onException()方法类的
实例。异常侦听器使得客户机可以异步接收错误通知。如果注册了异常侦听器的话,那
么当出现错误时,就会调用onMessage()方法,JMSException 对象则作为自变量被传
递到该方法。异常侦听器是在连接上设置的。
288
我们将用个非常简单的应用程序来讲解如何实施异常侦听器。
例8-7 JMSExceptionListener.java
import java.io.*;
import javax.jms.*;
public class JMSExceptionListener implements ExceptionListener {
/**
* onException. We will just print the exception and exit from the program
execution. Add suitable error handling and recovery logic depending on you
use..(onException。我们将打印异常情况并且从程序的执行中退出。根据您的使用情况添加适当的错误
处理和恢复逻辑。)
*/
public void onException( JMSException e) {
e.printStackTrace();
System.exit(1);
}
}
我们现在已经拥有了异常侦听器,接下来就可以用JMSExceptionListener 实例、如下以
setExceptionListener 方法注册异常侦听器到连接对象,从而在异步使用者中利用侦听器:
JMSExceptionListener exListener = new JMSExceptionListener( );
queueConnection.setExceptionListener(exListener);
setExceptionListner 方法为连接对象queueConnection 设置了异常侦听器。
289
8.7 消息选择器
JMS 提供在队列上查询消息的功能,因此我们可以根据给定的标准选择消息子集。我们
可以把这看作是数据库中的SQL 查询功能。事实上,这种查找的语法就是以SQL92 条
件表达的标准为基础的。消息选择器可以指向JMS 消息标题中的字段,也可以指向消
息属性中的字段,即应用程序定义的字段。
计算消息选择器的顺序是在优先等级内从左至右。您可以利用插句来改变计算顺序。选
择器字面值和操作器名是区分大小写的。
选择器可能包括:
.. 字面值:
- 字符串字面值包括在单一引用中,一个被包括的单一引用由复式单一引用代
表,如“literal”和“literal’s”。与Java String 字面值一样,他们必须使用unicode
字符编码;
- 精确数字字面值是没有小数点的数字值,如57、-957、+62 等。支持Java 长
型范围内的数字。精确数字字面值使用Java 整数字面值语法;
- 近似数字字面值是科学记数法表示的数字值(如7E3, -57.9E2)或带有小数
点的数字值(如7.、-95.7、+6.2 等)。支持Java 双精度型范围内的数字。近
似字面值使用Java 浮点字面值语法;
- Boolean 字面值真和假。
.. 标识符
- 标识符是必须以Java 标识符起始字符开始的无限长度字符序列,所有后续字符
必须为Java 标识符部件字符。标识符起始字符是Character.isJavaIdentifierStart
方法返回为真的任意字符。这包括‘_’ 和‘$’ 。标识符部件字符是
Character.isJavaIdentifierPart 方法返回为真的任意字符。
- 标识符不能是NULL, TRUE 或FALSE 名;
- 标识符不能为NOT, AND, OR, BETWEEN, LIKE, IN 和IS;
- 标识符可以是标题子段引用,也可以是属性引用;
- 标识符是区分大小写的;
- 消息标题子段引用限于JMSDelieveryMode, JMSPriority, JMSMessageID,
JMSTimestamp, JMSCorrelationID 和JMSType 。JMSMessageID,
290
JMSCorrelationID 和JMSType 值可能为空,如果如此的话,那么它们将被作为
NULL 值对待;
- 任何以“JMSX”开始的名都是JMS 定义的属性名;
- 任何以“JMS_”开始的名都是供应方特定属性名;
- 任何不以“JMS”开始的名都是应用程序特定属性名。如果引用消息中不存在
的属性,它的值则为NULL。如果它确实存在的话,那么它的值就是相应的属
性值。
.. 空白空间与Java 中的定义一样:空间、水平标签、表单反馈和行终止符。
.. 表达:
- 选择器是条件表达。计算为真的选择器匹配。计算为假或未知的选择器不匹配;
- 算术表达由其自身、算术操作、带数字值的标识符和数字字面值构成;
- 条件表达由其自身、比较操作、逻辑操作、由boolean 值的标识符和boolean
字面值构成;
- 为表达计算排序的标准括号()被支持。
.. 逻辑操作器的优先顺序为:NOT, AND, OR
.. 比较操作器:=, >, >=, <, <=, <> (不等于)
- 只有相同类型的值才可以比较。这里有一个例外,就是我们可以比较精确数字
值和近似数字值(要求的类型转换由Java 数字升级定义)。如果尝试比较不同
类型的值的话,选择器总为假;
- 字符串和Boolean 比较限于=和<>。只有在两个字符串包含相同的字符顺序时,
它们才是相等的;
.. 算术操作器的优先顺序如下:
- +,- 一元的
- *,/ 乘和除
- +,- 加和减
- 算术操作必须使用Java 数字提升。
.. Arithmetic-expr1 [NOT] BETWEEN arithmetic-expr2 和arithmetic-expr3 比较操
作器
- 年龄介于15 到19 岁等于age >= 15 AND age <= 19;
- 年龄不介于15 至19 之间等于age < 15 OR age > 19。
291
.. 标识符[NOT] IN (string-literal1, string-literal2,...)比较操作器,且标识符有一
个字符串或NULL 值。
- Country IN (’ UK’, ’US’, ’France’)对“UK”为真,对“Peru”为假。它等于
如下表达:(Country = ’ UK’) OR (Country = ’ US’) OR(Country = ’
France’);
- Country NOT IN (’ UK’, ’US’, ’France’)对“UK”为假,对“Peru”为真。
它等于如下表达:NOT ((Country = ’ UK’) OR (Country = ’US’) OR
(Country = ’ France’));
- 如果IN 或NOT IN 操作的标识符为NULL,那么操作的值为未知;
- 标识符[NOT] LIKE pattern-value [ESCAPE escape-character]比较操作器,这
里标识符有字符串值。Pattern-value 是个字符串字面值,其中‘_’代表任意单
一字符。‘%’代表任意字符序列(包括空序列)。所有其他字符代表其本身。
可选的透射字符是单一字符串字面值,其字符用于透射pattern-value 中‘_’和
‘%’的特别意义;
- Phone LIKE ‘12%3’对“123”、“12993”为真,对“1234”为假;
- Word LIKE ‘l_se’对“lose”为真,对“loose”为假;
- Underscored LIKE ‘\_%’ ESCAPE ‘\’对“_foo”为真,对“bar”为假;
- Phone NOT LIKE ‘12%3’对“123”和“12993”为假,对“1234”为真;
- 如果LIKE 或NOT LIKE 操作标识符为NULL,那么操作的值为未知。
.. 标识符IS NULL 比较操作器检测空标题字段值或丢失属性值。
- prop_name IS NULL
.. 标识符IS NOT NULL 比较操作器检测非空标题字段值或属性值的存在。
- prop_name IS NOT NULL
我们要求JMS 供应方在消息选择器出现时校验消息选择器的语法正确性。提供语法不
正确选择器的方法必须以JMS InvalidSelectionException 为结果。
以下消息选择器选择类型为汽车、颜色为蓝、重量大于2500 磅的消息:
“JMSType = ‘car’ AND color = ‘blue’ AND weight > 2500”
292
空值:标题字段和属性值可能为NULL。计算选择器表达的规则是,如果值为NULL,
那么SQL 就把NULL 值作为未知。因此,任何含有NULL 值的算术操作比较,其结果
都是未知值。
8.7.1 用消息选择器库
消息选择器可以在消息上设置为用户定义的属性。在发送或发布方,我们可以通过利用
名值对的设置属性方法来设置属性名和它的值。
为了设置名为“testProperty”、值为100 以及数据类型为整数的属性,我们可以在对象
消息上如下设置属性:
messaget.setIntProperty(“testProperty”, 100) ;
设置属性方法用到两个自变量。第一个自变量是类型字符串,也就是属性值的名。第二
个自变量是属性值。我们应当根据希望设置值的数据类型选择利用合适的设置属性方法
(setIntProperty 用于整数值,setStringProperty 用于字符串类型值等)。
在消息使用者方,我们应当使用具有合适选择标准的消息选择器字符串。选择标准是在
创建消息使用者的时候指定的。
我们使用的选择标准是选择属性名为“testProperty”和值为100 的消息发送。我们可以
用如下方法达到此目的:
String selector = “testProperty = 100 “ ;
queueReceiver = session.createReceiver(queueName, selector).
用选择器创建的消息使用者获取属性名为“testProperty”且值为100 的消息。
一旦创建与消息使用者相关联的选择器,JMS 规范就不允许它改变。如果您需要不同标
准的消息接收器的话,那么您可能需要创建另外的消息接收器。
一旦用消息选择器创建了消息接收器,您就可以利用消息使用者上的getMessageSelector
()方法来检查选择器值。
在我们前面讲解所用的queueReceiver 中,getMessageSelector 方法返回带有选择器值
queReceiver.getMessageSelector()的字符串。这将返回testProperty=100。