8用JMS編程
在本章中,我們將討論Java 消息發(fā)送服務(wù)(JMS)接口概念和MQSeries 實施,以及如
何使用JMS 編程。我們將在消息發(fā)送編程模式的上下文中探討JMS 概念。
8.1 什么是JMS?
與JDBC API for databases 一樣,Java Message Services(JMS)是消息發(fā)送的標(biāo)準(zhǔn)API。
JMS 規(guī)范(1.0.2)由Sun Microsystems 開發(fā),IBM 和其他企業(yè)消息發(fā)送銷售商、事務(wù)處
理銷售商以及RDBMS 銷售商都積極參與了開發(fā)過程。JMS 為Java 程序與對消息發(fā)送
系統(tǒng)對象進行各種操作的消息發(fā)送系統(tǒng)進行互動提供了一個常見的模型。程序?qū)οl(fā)
送系統(tǒng)對象進行的常見操作包括創(chuàng)建消息、發(fā)送消息、接收消息以及從企業(yè)消息發(fā)送系
統(tǒng)中讀取消息。JMS 為那些用Java 開發(fā)的程序提供了一種訪問這些消息發(fā)送系統(tǒng)操作
的常見方法。
JMS 具有兩種消息發(fā)送風(fēng)格,或者說它具有兩個域:
.. 一對一或點到點模型;
.. 發(fā)布/預(yù)訂模型。
JMS 僅僅是種規(guī)范。每個企業(yè)消息發(fā)送系統(tǒng)銷售商都必須就其特定的消息發(fā)送系統(tǒng)提供
實施規(guī)范的類。
在本章中,我們將描述JMS API 的MQSeries 實施、討論JMS API 概念和MQSeries 的
JMS 實施能力,并講解在可以利用MQSeries JMS 實施的不同情境中如何利用MQSeries
JMS。
為什么要使用JMS?
JMS 標(biāo)準(zhǔn)非常重要,其原因在于:
.. 它是第一個獲取廣泛跨行業(yè)支持的企業(yè)消息發(fā)送API;
.. 它提供的標(biāo)準(zhǔn)消息發(fā)送概念和慣例適用于廣泛的企業(yè)消息發(fā)送系統(tǒng),因而簡化了
企業(yè)應(yīng)用程序的開發(fā);
.. 它可以利用現(xiàn)有的、企業(yè)證明成功可行的消息發(fā)送系統(tǒng);
.. 它添加了完全用現(xiàn)有非JMS 客戶機解釋的新JMS 客戶機,從而允許您擴展現(xiàn)有的
基于消息的應(yīng)用程序;
.. 它允許您可以編寫便攜性強的基于消息的商業(yè)應(yīng)用程序。
8.2 概述
JMS 是定義JMS 客戶機如何訪問企業(yè)消息發(fā)送產(chǎn)品功能的一系列接口和相關(guān)語義。我
們這里所描述的消息是指企業(yè)應(yīng)用程序所使用的異步請求、報表或事件。它們既包含協(xié)
同這些系統(tǒng)所需的重要信息;又包含著描述特定商業(yè)行為的精確格式化的數(shù)據(jù)。通過這
些消息的交流,每個應(yīng)用程序都能跟蹤企業(yè)的發(fā)展。
JMS 定義了一系列常見的企業(yè)消息發(fā)送概念和功能。它最大限度地減少了Java 語言程
序設(shè)計人員在使用企業(yè)消息發(fā)送產(chǎn)品前必須學(xué)會的概念集。也最大限度地加強了消息發(fā)
送應(yīng)用程序的便攜性。JMS 標(biāo)準(zhǔn)雖然提供了獨立于不同銷售商的編程接口,但是并不定
義出通訊協(xié)議。
JMS 模型
JMS 定義了消息傳遞服務(wù)的一般視圖。理解該視圖,并弄清它是如何映射到底層的
MQSeries 傳輸上的,相當(dāng)重要。一般JMS 模型是建立在Sun 的javax.jms 包所定義的接
口上的,請見圖8-1。
圖8-1 JMS 模型
連接
連接提供了到底層傳輸?shù)脑L問,并被用來創(chuàng)建會話。在MQSeries 上下文中,連接提供
了儲存參數(shù),如隊列管理器名、遠程主機名(在Java 客戶連接性中)等的地方。換言之,
MQSeries JMS 連接一般都在Java 虛擬機之外分配MQSeries 資源。連接也支持同時使用。
連接可以提供以下好處:
.. 包括與JMS 供應(yīng)方的開放式連接。它通常代表客戶機和供應(yīng)方服務(wù)端口之間的一
個開放的TCP/IP 槽;
.. 它的創(chuàng)建就是客戶機認證發(fā)生之處;
.. 它可以指定唯一的客戶機標(biāo)識符;
.. 它提供ConnectionMetaData;
.. 它支持可選的ExceptionListener。
由于建立連接時完成了認證和通訊設(shè)置,因此連接相對來說是個重量級的JMS 對象。
大多數(shù)客戶機都使用單一的連接進行消息發(fā)送。其他更先進的應(yīng)用程序可能會使用幾個
連接。JMS 并不為使用多個連接而設(shè)計原因;但是,這樣做可能有操作上的原因。
JMS 客戶機一般創(chuàng)建一個連接、一個或多個會話以及許多消息生成器和使用者。當(dāng)創(chuàng)建
連接時,它處在停止模式,這就是說沒有消息再被送達。
重點:連接是在停止模式中創(chuàng)建的。
通常,直到設(shè)置完成前,連接都處在停止模式中。在完成時,將調(diào)用連接的start()方
法,而消息則開始到達連接的使用者。此設(shè)置慣例可以在客戶機仍在進行設(shè)置過程中盡
可能減少異步消息送達所帶來的任何客戶機混亂。
連接可以立即啟動,而設(shè)置可在隨后進行。這樣做的客戶機必須在設(shè)置過程中就準(zhǔn)備好
處理異步消息送達。
提示:消息生成器可以在停止連接時發(fā)送消息。
不是直接創(chuàng)建連接的,而是利用連接庫建立的。庫對象可以儲存在JNDI 名稱空間中,
從而將JMS 應(yīng)用程序與供應(yīng)方特定信息隔離開來。連接庫對象是利用MQSeries JMS 管
理工具JMSAdmin 創(chuàng)建的。該工具使得管理員可以給JNDI 名稱空間定義MQSeries JMS
對象的8 種類型。請參見第8.4.9 節(jié)《利用JMSAdmin 以VisualAge for Java 管理JMS JNDI
對象》(見本書第260 頁)。
創(chuàng)建連接
客戶機利用連接庫來創(chuàng)建連接。要使用何種連接庫類型取決于您想要哪種類型的連接:
.. 就PTP 連接而言,我們利用QueueConnectionFactory 或XAQueueConnectionFactory
來獲取QueueConnection 或XAQueueConnection;
.. 就發(fā)布/ 預(yù)訂消息發(fā)送模式而言, 我們利用TopicConnectionFactory 或
XATopicConnectionFactory 來獲取TopicConnection 或XATopicConnection。
為了創(chuàng)建連接,我們應(yīng)當(dāng)進行以工作:
.. 從JNDI 名稱空間接收庫對象
JNDI API 向以Java 編寫的應(yīng)用程序提供了命名和目錄功能。它是利用Java 的對象
模型專門為Java 設(shè)計的。利用JNDI,Java 應(yīng)用程序可以儲存并接收任何類型的命
名為Java 的對象。另外,JNDI 提供進行標(biāo)準(zhǔn)目錄操作的方法,如將屬性與對象相
關(guān)聯(lián)并利用對象屬性查找對象等。
在此常見的API 之后,可以無縫地插入不同的命名和目錄服務(wù)供應(yīng)方。這使得Java
應(yīng)用程序可以通過各種各樣的命名和目錄服務(wù)來利用信息,如LDAP、NDS、DNS
和NIS(YP)等,也使得Java 應(yīng)用程序可以與傳統(tǒng)應(yīng)用程序和系統(tǒng)并存。
在JNDI 中,所有的命名和目錄操作都是根據(jù)上下文進行的。沒有絕對的根。因此,
JNDI 定義了初始上下文,它是命名和目錄操作名解析的起始點。一旦有了初始上
下文,您就可以利用它來查看其他上下文和對象。
.. 為了從JNDI 名稱空間接收對象,必須按照下面這段代碼所顯示的那樣設(shè)置初始上
下文:
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()方法來創(chuàng)建連接,請參見如下的例子:
QueueConnection connection ;
connection =qFactory.createQueueConnection();
.. 啟動連接
JMS 規(guī)范定義指出,應(yīng)當(dāng)在“停止”狀態(tài)下創(chuàng)建連接。在您可以利用連接發(fā)送消息
之前,必須顯式啟動連接。
我們利用start()方法啟動連接,請參見如下的例子:
connection.start();
會話
為生成和使用消息提供上下文,包括用來創(chuàng)建消息生成器和消息使用者的方法。
JMS 會話是生成和使用消息的單線程上下文。盡管它可以在Java 虛擬機之外分配供應(yīng)
方資源,但我們還是將其看作輕量級JMS 對象。
我們可以分別利用連接對象的createQueueSession()或createTopicSession()方法來創(chuàng)
建會話。
創(chuàng)建會話方法包括兩個參數(shù):
1. 可決定會話是否進行事務(wù)處理的boolean。
在事務(wù)處理的會話中,全部發(fā)送、或者全部接收作為一個單位的一組消息。
在非事務(wù)處理會話中,分別發(fā)送或接收消息。
2. 定義識別模式的參數(shù)。
請參見如下的例子:
session =connection.createQueueSession(false ,Session.AUTO_ACKNOWLEDGE);
這是用AUTO_ACKNOWLEDGE 來創(chuàng)建非事務(wù)處理會話的最簡單的情況。連接是
線程安全的,但會話和由會話創(chuàng)建的對象不是線程安全的。我們建議多線程應(yīng)用程
序應(yīng)當(dāng)對每個線程都使用不同的會話。
會話有以下幾種用途:
.. 它是消息生成器和使用者的庫;
.. 它提供了供應(yīng)方優(yōu)化的消息庫;
.. 它既支持單一事務(wù)處理,又支持一系列將跨越生成器和使用者的庫結(jié)合到原子單
位的事務(wù)處理;
.. 會話為它使用和生成的消息定義了連續(xù)序列;會話保存了它使用的消息,直到識
別該消息;
.. 會話串行化注冊到消息使用者的消息偵聽器。
會話可以創(chuàng)建并服務(wù)于多個消息生成器和使用者。
一種典型的用法就是在消息到達前一直在同步消息使用者上保持線程塊。然后線程可以
使用一個或更多的會話消息生成器。
如果一個客戶機希望在其他客戶機使用消息時具備一個生成消息的線程,那么該客戶機
就應(yīng)當(dāng)為生成線程使用另外的會話。
一旦啟動了連接,任何具有偵聽器或注冊消息偵聽器的會話就會被賦予送達消息的控制
線程??蛻魴C代碼從另一個控制線程利用會話或任何構(gòu)成會話的對象都是錯誤的。唯一
的例外就是使用會話或連接關(guān)閉方法。
大多數(shù)客戶機將它們的庫自然地分成會話應(yīng)當(dāng)是比較容易的。該模型允許客戶機簡單啟
動并隨著并行操作需要的增加而逐漸增加消息線程的復(fù)雜度。
關(guān)閉方法是當(dāng)另一個線程正在執(zhí)行某個其他會話方法時唯一可以調(diào)用的會話方法。
我們可以選擇地指定會話為事務(wù)處理會話。每個事務(wù)處理會話都支持單一系列的事務(wù)處
理。每個事務(wù)處理會將一系列消息發(fā)送和一系列消息接收歸組到原子庫單元中。事實上,
事務(wù)處理將會話的輸入消息流和輸出消息流組織到原子單位集中。當(dāng)提交事務(wù)處理時,
會確認輸入的原子單位,同時發(fā)送與其相關(guān)聯(lián)的輸出原子單位。如果已經(jīng)進行了事務(wù)處
理回退,那么其發(fā)送的消息則被毀壞,會話的輸入也被自動恢復(fù)。
事務(wù)處理輸入和輸出單位的內(nèi)容就是會話的當(dāng)前事務(wù)處理中所生成和使用的那些消息。
我們可以利用會話的提交或回退方法完成事務(wù)處理。會話的當(dāng)前事務(wù)處理的完成將自動
開始下一個事務(wù)處理。其結(jié)果就是,事務(wù)處理會話總是具有一個當(dāng)前事務(wù)處理,且其庫
就在當(dāng)前事務(wù)處理中完成。
消息生成器
JMS 客戶機利用消息生成器發(fā)送消息到特定的目的地。我們通過傳遞目的地到會話對象
提供的創(chuàng)建消息生成器方法,從而創(chuàng)建消息生成器。
在點到點消息發(fā)送中,這將是利用QueueSession 對象上的createSender 方法所創(chuàng)建的
QueueSender。QueueSender 通常是為特定隊列創(chuàng)建的,因此所有利用該發(fā)送器發(fā)送的消
息都會被發(fā)送到同樣的目的地。我們利用隊列對象指定目的地。隊列對象即可以在運行
時間中創(chuàng)建,也可以在JNDI 名稱空間中構(gòu)造和儲存。請參見如下的例子:
Queue ioQueue ;
ioQueue = (Queue).ctx.lookup(qLookUp) ;
sender = session.createSender(ioQueue);
在發(fā)布/預(yù)訂消息發(fā)送中,這將是在TopicSession 對象上利用createPublisher 方法創(chuàng)建
的TopicPublisher。
通常,Topic 是在創(chuàng)建TopicPublisher 時指定的。在這種情況下,嘗試去使用方法,該方
法為未確認的TopicPublisher,將生成UnsupportedOperationException。
請參見如下的例子:
Topic topic ;
topic = (Topic)ctx.lookup( “cn=first.topic”) ;
TopicPublisher pub = session.createPublisher(topic);
客戶機也可以選擇不提供目的地而創(chuàng)建消息生成器。在這種情況下,目的地必須輸入每
個發(fā)送操作。這種風(fēng)格的消息生成器一般用來根據(jù)請求的replyTo 目的地向請求發(fā)送回
復(fù)。
客戶機可以通過消息生成器為發(fā)送的消息指定默認的送達模式、優(yōu)先級和使用期限。它
也可以為每條消息指定送達模式、優(yōu)先級和使用期限。
244
客戶機可以為它發(fā)送的每條消息指定單位為毫秒的使用期限值。這個值定義了消息到期
時間,也就是消息使用期限的和以及消息發(fā)送的GMT 時間(對事務(wù)處理發(fā)送而言,這
是指客戶機發(fā)送消息的時間而不是提交事務(wù)處理的時間)。
發(fā)送消息
消息是用消息生成器發(fā)送的。因此在點到點(PTP)模型中發(fā)送消息時,您應(yīng)當(dāng)使用
QueueSender 對象,而在發(fā)布/預(yù)訂模型中,您則應(yīng)當(dāng)使用TopicPublisher 對象。
在PTP 模型中,應(yīng)使用QueueSender 的send()方法發(fā)送消息。請參見如下的例子:
outmessage = session.createTextMessage();
outmessage.setText(“Sample Message “) ;
sender.send(outMessage);
在發(fā)布/預(yù)訂模型中,應(yīng)使用TopicPublisher 對象的發(fā)布方法來發(fā)布消息。請參見如下
的例子:
pub.publish(outMessage);
消息使用者
消息使用者是用來接收消息的。MessageConsumer 接口是所有消息使用者的父級接口。
在PTP 模型中,這將是QueueReceiver。在發(fā)布/預(yù)訂模型中,這將是TopicSubscriber。
我們可以用消息選擇器來創(chuàng)建消息使用者,消息選擇器允許客戶機根據(jù)消息選擇器指定
的標(biāo)準(zhǔn)選擇消息子集。欲了解詳細信息,請參見第8.7 節(jié)《消息選擇器》(見本書第289
頁)。
消息使用者客戶機可以同步接收消息,也可以讓消息在到達時異步送達。客戶機可以利
用其接收方法之一從消息使用者請求下一條消息。接收有幾種不同的形式,允許客戶機
登記或等待下一條消息。
客戶機可以向消息使用者注冊MessageListener 對象。當(dāng)消息到達消息使用者時,它是通
過調(diào)用MessageListeners onMessage 方法來送達消息的。欲了解更多信息,請參見第8.6
節(jié)《異步處理》(見本書第285 頁)。
245
在PTP 模型中,QueueReceiver 是利用QueueSession 對象上的createReceiver()方法創(chuàng)
建的。該方法采用的是從何處接收消息定義的Queue 參數(shù)。請參見如下的例子:
QueueReceiver queueReceiver = session.createReceiver(ioQueue) ;
在發(fā)布/預(yù)訂模型中,TopicSubscriber 對象是利用TopicSession 對象的createSubscriber
()方法創(chuàng)建的。請參見如下的例子:
TopicSubscriber sub = session.createSubscriber(topic);
接收消息
消息是用消息使用者接收的。在PTP 消息發(fā)送中,應(yīng)當(dāng)用QueueReceiver 對象來接收消
息,而在發(fā)布/預(yù)訂消息發(fā)送中,則應(yīng)當(dāng)用TopicSubscriber 對象來接收消息。
在PTP 模型中,您可以利用QueueReceiver 對象的接收方法來接收消息。請參見如下的
例子:
Message inMessage = queueReceiver.receive(800) ;
指定的參數(shù)是以毫秒為單位的超時。此方法發(fā)出調(diào)用,以接收指定超時間隔內(nèi)到達的下
一條消息。
在發(fā)布/預(yù)訂模型中,我們利用TopicSubscriber 的receive()方法來接收預(yù)訂。請參見
如下的例子:
Message inMsg = sub.receive() ;
這段代碼執(zhí)行了帶有等待的獲取方法。
重點:請注意,連接是線程安全的,但會話、消息生成器和消息使用者則不是。我們建
議的做法是對每個應(yīng)用程序線程使用一個會話。
在MQSeries 條款中,連接為臨時隊列提供了作用域。它也提供了地方,可以用來儲存
參數(shù),該參數(shù)控制如何連接到MQSeries。舉例來說,這些參數(shù)就是隊列管理器名和遠
程主機名(如果您使用MQSeries Java 客戶機連接的話)。會話包含HCONN,因此定義
了事務(wù)處理作用域。
消息生成器和消息使用者包含HOBJ,它定義了向其寫入或從其讀取的特定隊列。請注
意,正常的MQSeries 規(guī)則是適用的。在任何給定時間上,每個HCONN 只能進行一個
單一的操作。因此,不能同時調(diào)用消息生成器或與會話相關(guān)的消息使用者。
這與JMS 每個會話只有單一線程的限制是一致的。放置方法可以使用遠程隊列,但獲
取方法只能適用于本地隊列管理器上的隊列。一般JMS 接口是進一步劃分為更具體的
版本子集,該子集為點到點以及發(fā)布/預(yù)訂行為。點到點版本如下:
.. QueueConnection QueueSession QueueSender QueueReceiver
JMS 的關(guān)鍵理念就是,它編寫的應(yīng)用程序可以只在javax.jms 中接口引用。
我們強烈建議您這么做。所有各銷售商特有的信息都包含在以下實施中:
.. QueueConnectionFactor TopicConnectionFactory
- Queue
- Topic
以上這些稱作“管理對象”,即可以利用銷售商提供的管理工具構(gòu)造、且能被儲存在JNDI
名稱空間中的對象。JMS 應(yīng)用程序可以從名稱空間接收并使用這些對象,而不必知道由
哪個銷售商進行實施。MQSeries classes for Java Message Service 由許多Java 類和接口構(gòu)
成,這些類和接口是以Sun 的接口和類javax.jms 包為基礎(chǔ)的。
應(yīng)當(dāng)用下面所列的Sun 接口和類編寫客戶機,我們將在下面各節(jié)中詳細描述這些接口和
類??蓪嵤㏒un 接口和類MQSeries 對象的名稱具有“MQ”作為前綴(如果對象描述中
沒有做出其它規(guī)定的話)。描述包括有關(guān)MQSeries 對象與標(biāo)準(zhǔn)JMS 定義存在任何偏差
的細節(jié)。
事務(wù)處理
會話是在客戶機和消息發(fā)送系統(tǒng)之間的連接上一系列被發(fā)送和接收的消息。當(dāng)創(chuàng)建會話
時,它可以是非事務(wù)處理的(默認)也可以是事務(wù)處理的。事務(wù)處理會話保證一組消息
要么都被發(fā)送接收,要么都不被發(fā)送接收。非事務(wù)處理會話意味著消息被分別發(fā)送和接
收。在線購物就是事務(wù)處理會話的一個實例??蛻舸蜷_訂單(開始事務(wù)處理)??蛻暨x
擇的每項商品都是一條在訂單中添加商品的消息。客戶關(guān)閉訂單(結(jié)束事務(wù)處理)。如
果發(fā)送器提交了事務(wù)處理,那么將發(fā)送組中所有的消息。如果發(fā)送器回退事務(wù)處理的話,
那么將不訂購任何商品項目。
8.3 JMS消息
JMS 提供不同的消息類型。每種類型都包括有關(guān)其內(nèi)容的某些信息。
JMS 中定義的消息類型如下:
.. BytesMessage
.. MapMessage
.. ObjectMessage
.. StreamMessage
.. TextMessage
JMS 消息由以下三部分構(gòu)成:
.. 標(biāo)題
標(biāo)題字段包括供應(yīng)方用于路由和確認消息的信息,以及客戶機可以利用的信息。
.. 屬性
除標(biāo)題中的字段外,JMS 消息還為添加可選的標(biāo)題字段到消息提供功能。這些屬性
可以分為:
- 應(yīng)用程序特定屬性,客戶機為處理消息用這些屬性添加進程特定信息;
- 標(biāo)準(zhǔn)屬性,即JMS 特定屬性;
- 供應(yīng)方特定屬性,原始客戶機要求這些屬性。
.. 主體
這就是消息數(shù)據(jù)部分的所在。JMS 提供不同類型的消息主體,包括大部分當(dāng)前正被
使用的消息發(fā)送風(fēng)格。
8.3.1 映射JMS消息到MQSeries消息上
MQSeries 消息有三個組成部分:
.. MQSeries 消息描述器(MQMD);
.. MQSeries MQRFH2 標(biāo)題;
.. 消息主體。
MQRFH2 標(biāo)題是可選的。MQRFH2 標(biāo)題是具有固定部分和可變標(biāo)題部分的可擴展的標(biāo)
題。固定部分以標(biāo)準(zhǔn)MQSeries 標(biāo)題模式為模型。MQRFH2 標(biāo)題既可以攜帶與消息內(nèi)容
相關(guān)聯(lián)的JMS 特定數(shù)據(jù),又可以攜帶不直接與JMS 相關(guān)聯(lián)的其他信息。
我們用兩種方法將JMS 消息翻譯或轉(zhuǎn)化為MQSeries 消息。
.. 映射
映射是將JMS 消息標(biāo)題和消息屬性翻譯到有相應(yīng)MQMD 字段的相應(yīng)MQMD 值。
.. 拷貝
如果JMS 消息標(biāo)題和消息屬性值不具有相應(yīng)的MQMD 字段的話,JMS 標(biāo)題字段或
屬性將被傳遞,并可能作為MQRFH2 標(biāo)題中的一個字段被轉(zhuǎn)化。
是否包括MQRFH2 標(biāo)題是可選的。在發(fā)送消息中,JMS 目的地類的標(biāo)記決定是否包括
它。該標(biāo)記可以在定義JMS 管理對象時用JMSAdmin 工具來設(shè)定。管理員可以設(shè)置
MQSeries 目的地的TargetClient 值為JMSC.MQJMS_CLIENT_NONJMS_MQ,從而顯示
出JMS 客戶機正與非JMS 客戶機通訊。由于MQRFH2 標(biāo)題攜帶著JMS 特定信息,因
此如果發(fā)送器知道接收客戶機是JMS 客戶的話,您就應(yīng)當(dāng)包括它。如果接收器不是JMS
客戶機,那么就應(yīng)當(dāng)省略MQRFH2 標(biāo)題。
處理JMS 消息類型
在本節(jié)中,我們將利用發(fā)貨跟蹤ID(它作為JMS 客戶機的一條消息被發(fā)送)講解可以
如何使用不同的JMS 消息類型。
使用TextMessage 類
消息中的信息可以作為人類可以閱讀的文本字符串發(fā)送,客戶機也可以讀取并處理或者
顯示該字符串。
我們可以如下將跟蹤ID 作為TextMessage 對象中的字符串發(fā)送:
String trackingId ;
TextMessage message;
message =session.createTextMessage();
message.SetText(trackingId);
使用BytesMessage 類
在BytesMessage 中,信息是以二進制格式發(fā)送的。消息中的信息可以作為字節(jié)數(shù)組構(gòu)件。
可以如下準(zhǔn)備這樣的消息:
byte [] trackingId ;;
BytesMessage message ;
message =session.createBytesMessage();
message.writeBytes(trackingId);
使用映射消息
在映射消息中,信息可以作為名值對發(fā)送。這樣,消息本身可以包括消息所儲存數(shù)據(jù)的
元數(shù)據(jù)。
在我們所用的例子中,發(fā)送消息請求trackingId 上的信息,名(元數(shù)據(jù))可以是trackingId,
實際值(比如說AMX100000)將是名值對的值。
消息可以如下構(gòu)造:
String attributeName =“trackingId ”;
String attributeValue =“AMX100000 ”
MapMessage message;
message =session.createMapMessage();
message.setString(attributeName,attributeValue);
提示:如果值部分是較長的數(shù)據(jù)類型的話,那么您可以使用消息setLong 方法。請根據(jù)
您處理的數(shù)據(jù)類型選擇合適的方法。
使用流消息
在流消息中,與映射消息相似,消息可以由順序?qū)懭氲母鞣N字段構(gòu)成,每個字段都有其
自己原始的類型。在映射消息中,客戶機可以設(shè)置映射中任意數(shù)量的字段,處理客戶機
則可以讀取特定的字段而不必處理整個映射。但是,在流消息中,即便處理客戶機僅對
流消息中的字段子集感興趣,客戶機仍必須讀取消息中的每個字段(再摒棄它不感興趣
的字段)。此二者的另一個重要差別就是,在映射消息中,字段順序并不重要,但在流
消息中,字段是以寫入順序讀取的。
流消息可以如下構(gòu)造:
String attributeName =“trackingId ” ;;
String attributeValue =“AMX100000 ” ;;
StreamMessage message;
session.createStreamMessage();
message.writeString(attributeName);
message.writeString(attributeValue);
使用對象消息
對象消息可被用來將Java 對象作為一條消息傳遞,該消息就是接收客戶機可以在對象中
利用方法提取數(shù)據(jù)的消息。在下面的例子中,我們使用的是跟蹤對象:
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.(利用以上跟蹤對象,我們能發(fā)送作為跟蹤對象的跟蹤消息)
We can construct such a message as:(我們能構(gòu)造如下的消息)
String attributeName =“trackingId ” ;;
String attributeValue =“ AMX100000” ;
TrackingObject trakingObject =new TrackingObject();
ObjectMessage =message ;
trackingObject.setAttributeName(attributeName);
trackingObject.setAttributeValue(attributeValue);
message =session.createObjectMessage();
message.setObject(trackingObject);
在接收方,客戶機可以使用跟蹤對象的獲取方法來提取數(shù)據(jù)。
消息識別
如果客戶機指定JMS 使用者的消息應(yīng)被顯式識別的話,那么JMS 消息也支持使用識別
方法。如果客戶機使用自動識別的話,那么將忽略識別調(diào)用。
消息識別有三種類型。消息識別的類型是在會話創(chuàng)建時指定的。這些不同的類型如下:
.. AUTO_ACKNOWLEDGE
在AUTO_ACKNOWLEDGE 模式中,當(dāng)消息成功從調(diào)用返回到接收器,或消息使
用者注冊的處理消息的消息偵聽器成功返回時,消息會話將自動識別消息。
Session session =queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
.. CLIENT_ACKNOWLEDGE
利用CLIENT_ACKNOWLEDGE 模式,客戶機通過調(diào)用消息上的識別方法顯式并
確認消息。
Session session =queueConnection.createQueueSession(false,
Session.CLIENT_ACKNOWLEDGE);
Then once the message is processed,the client can issue(然后一旦處理完消息,客戶機就能發(fā)行)
message.acknowledge();
method to acknowledge the message.(方法以確認該消息)
當(dāng)使用CLINET_ACKNOWLEDGE 模式時,我們在處理消息過程中必須注意避免
大量未識別消息的積累,未識別消息的積累可能導(dǎo)致資源耗盡,帶來失敗。
.. DUPS_OK_ACKNOWLEDGE
DUPS_OK_ACKNOWLEDGE 模式命令會話遲鈍地確認消息送達。如果JMS 失敗
的話,其結(jié)果就可能是重復(fù)消息發(fā)送。允許處理重復(fù)消息的使用者應(yīng)當(dāng)使用這種模
式。在客戶機允許重復(fù)消息的情況下,利用這種模式可以獲取一些效能改善,因為
會話在避免重復(fù)消息時的開銷較小。
8.3.2 JMS補充特性
JMS 還包括其他一些特性:
.. 異步消息送達
- 使用消息偵聽器概念;
- 使用基于事件的模型,它在事先設(shè)定的事件上觸發(fā)指定的函數(shù)。
JMS 客戶機可以注冊偵聽器對象,它可以實施具有消息使用者(消息接收器)的
MessageListener 接口。當(dāng)注冊使用者的消息到達時,我們可以調(diào)用listeners
onMessage 方法讓消息對消息使用者可用。
.. 消息選擇器
- 基于內(nèi)容的特定消息接收
- 使用基于SQL92 的查詢函數(shù)
JMS 消息具有向JMS 消息標(biāo)題(在消息實際主體外)提供用戶定義元數(shù)據(jù)的功能。JMS
程序可以利用這一功能根據(jù)選擇標(biāo)準(zhǔn)選擇消息子集,換言之,JMS 客戶機可以僅選擇那
些自己感興趣的消息。
8.4 MQSeries JMS實施
用MQSeries 實施JMS 的重點如下:
.. 支持的平臺:AIX, HP-UX, Windows NT, Solaris 和 Linux
.. 由包括關(guān)鍵功能的JAR 文件構(gòu)成
- com.ibm.mq.jms.jar
- com.ibm.mq.jar
.. 定義管理對象到JNDI 名稱空間的管理工具:
- JMSAdmin
.. 作為產(chǎn)品延伸可以通過網(wǎng)絡(luò)下載獲取
.. MQSeries classes for Java 和JMS with MQSeries V5.2
8.4.1 MQSeries JMS的安裝
為了使JMS 支持MQSeries,您必須安裝并實施JMS 接口的Java 類。JMS classes for
MQSeries 可以從下述IBM 網(wǎng)站的MA88 SupportPac 下載獲取:
http://www-3.ibm.com/software/ts/mqseries/txppacs/
此SupportPac 是免費的。您可以根據(jù)支持的平臺下載合適的版本,并按照隨SupportPac
提供的安裝說明進行安裝。
重點:當(dāng)您在安裝JMS SupportPac 時,位于MQSeries 安裝的Java 次級目錄中的所有文
件都會在安裝過程中備份,新文件拷貝自SupportPac。如果在您的上下文中已經(jīng)安裝了
AMI 支持,那么您可能需要重新安裝AMI SupportPac。
一旦您已安裝了JMS SupportPac,為了用JMS 運行發(fā)布/預(yù)訂應(yīng)用程序,您還需要運行
稱作MQJMS_PSQ.mqsc 的MQSC 腳本,它位于MQSeries 安裝目錄下的java\bin 次級目
錄中。為了在默認的隊列管理器上運行腳本,請從操作系統(tǒng)命令行窗口中運行以下命令:
runmqsc <“C:\Program Files \IBM \MQSeries \java \bin \MQJMS_PSQ.mqsc ”
這里,我們假定C:\Program Files \IBM \MQSeries \就是MQSeries 的安裝目錄。
8.4.2 JMS管理對象——JNDI和JMSAdmin
目錄服務(wù)包括命名功能,在消息發(fā)送語句中提供包括各種實體(機器名、服務(wù)、用戶以
及MueueManagers, Queues, Topics 等供應(yīng)方特定對象)安排和確認的抽象層次名稱空間。
這就使得我們可以利用邏輯名稱空間,簡化了網(wǎng)絡(luò)中的對象發(fā)現(xiàn)和識別。Java 命名和目
錄接口(JNDI)API 實施向以Java 開發(fā)的程序提供了目錄和命名功能。這是的Java 程
序可以從JNDI 名稱空間發(fā)現(xiàn)并接收任何類型的對象。
JMS 管理對象是JMS 應(yīng)用程序儲存并從JNDI 名稱空間接收的對象。JMS 管理對象通常
由管理員創(chuàng)建。JMS 管理對象包含底層消息發(fā)送服務(wù)供應(yīng)方信息的配置信息。在這節(jié)中,
我們將就以MQSeries 開發(fā)和應(yīng)用JMS 應(yīng)用程序來討論這些對象的定義、管理和使用。
重點:在程序樣例中,我們將使用作為JNDI 服務(wù)器的WebSphere 應(yīng)用程序服務(wù)器所提
供的持久名服務(wù)器。我們是利用VisualAge for Java 企業(yè)版和Websphere application Server
來開發(fā)程序樣例的。
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 調(diào)用管理工具
管理工具具有命令行接口。您可以互動地利用此接口,也可以用它來啟動批處理進程。
互動模式提供命令提示符,您可以在此輸入管理命令。在批處理模式中,啟動工具的命
令包括包含管理命令腳本的文件名。
為了在互動模式下啟動該工具,請輸入以下命令:
JMSAdmin [-t ] [-v ] [-cfg config_filename ]
在這里:
.. -t 啟動跟蹤(默認情況下為跟蹤關(guān)閉)
.. -v 生成詳細輸出(默認情況下為簡潔輸出)
.. -cfg config_filename 為備用配置文件名。
8.4.5 JMSAdnub工具配置
JMSAdmin 工具在其中的以下參數(shù)使用配置文件:
.. INITIAL_CONTEXT_FACTORY
這顯示出工具使用的服務(wù)供應(yīng)方?,F(xiàn)在該屬性有3 個被支持的值:
- com.sun.jndi.ldap.LdapCtxFactory
當(dāng)您使用LDAP 服務(wù)器作為JNDI 服務(wù)器時,您需要使用此類名。(在我們的測
試中,我們將使用IBM Secureway Server 3.2.1。)
- com.sun.jndi.fscontext.RefFSContextFactory
當(dāng)您在使用文件系統(tǒng)儲存JNDI 對象時,應(yīng)當(dāng)使用此類名。
- com.ibm.ejs.ns.jndi.CNInitialContextFactory
當(dāng)您在使用Websphere 的持久名服務(wù)器時,您應(yīng)當(dāng)使用此類名。
.. PROVIDER_URL
這顯示出會話初始上下文的URL,即該工具執(zhí)行的所有JNDI 操作的根?,F(xiàn)在支持
該屬性的有3 種形式:
- ldap://hostname/contextname (針對LDAP)
- file:[drive:]/pathname (針對文件系統(tǒng)上下文)
該工具不會創(chuàng)建路徑指定的目錄。您應(yīng)當(dāng)在使用JMSAdmin 工具前確保目錄存
在。
- iiop://hostname[:port] /[?TargetContext=ctx] (針對WebSphere Persistent
Name Server)
.. SECURITY_AUTHENTICATION
這顯示出JNDI 是否傳遞安全證明到您的服務(wù)供應(yīng)方。只有在使用LDAP 服務(wù)供應(yīng)
方時才會用到該參數(shù)。該參數(shù)現(xiàn)在可以具有以下三個值之一:
- None(匿名識別)
- Simple(簡單識別)
- CRAM-MD5(CRAM-MD5 識別機制)
以上的值指定在一個配置文件中,您可以在以-cfg 參數(shù)調(diào)用JMSAdmin 工具時指定配
置文件。稱作jmsadmin.config 的配置文件樣例位于MQSeriesInstalldirectory\java\bin,您
可以編輯它或使用它,以適應(yīng)上下文的屬性值來創(chuàng)建配置文件。
8.4.6 以持久名服務(wù)器使用JMSAdmin
編輯隨安裝帶來的JMSAdmin.config 文件(您可以在MQSeriesInstalldirectory\java\bin 下
找到該文件),指示JMSAdmin 工具應(yīng)當(dāng)使用持久名服務(wù)器,并編輯PROVIDER_URL
屬性值,以指向持久名服務(wù)器。而后在另一個文件夾中保存文件,例如C:\temp。
在JMSAdmin.config 文件中,我們提供如下值:
.. INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
該值對VisualAge for Java 企業(yè)版和WebSphere 應(yīng)用程序服務(wù)器提供的持久名服務(wù)
器有效。對另一個JNDI 服務(wù)器而言,INITIAL_CONTEXT_FACTORY 變量必須隨
配套文檔提供。
.. PROVIDER_URL=iiop://hostname/
在此,hostname 即持久名服務(wù)器運行的機器名。當(dāng)使用IIOP 協(xié)議時,默認端口為
900,不必提及。如果您改變持久名服務(wù)器的默認端口(默認端口為900)的話,那
么您必須在PROVIDER_URL 變量中指示端口,例如:iiop://hostname:901/(這
里假定您將默認端口900 改為901 端口)。
利用-cfg 參數(shù)調(diào)用JMSAdmin 工具,并給出您在編輯后保存JMSAdmin.config 文件的
路徑。
8.4.7 以VisualAge for Java使用持久名服務(wù)器
持久名服務(wù)器的數(shù)據(jù)庫應(yīng)當(dāng)可以利用JDBC 從VisualAge for Java 訪問到。如果是第一次
設(shè)置持久名服務(wù)器的話,您將需要創(chuàng)建數(shù)據(jù)庫(我們利用UDB7.1 作為數(shù)據(jù)庫服務(wù)器)。
我們創(chuàng)建了稱作jndi 的數(shù)據(jù)庫。為了讓持久名服務(wù)器利用JDBC 訪問數(shù)據(jù)庫,VisualAge
for Java 庫空間類路徑也應(yīng)當(dāng)包括Db2UdbInstallDirectory\java\db2java.zip。
從VisualAge for Java 啟動持久名服務(wù)器
.. 從VisualAge for Java 菜單欄選擇Workspace - > Tools - >Websphere Test
Environment(見圖8-2)。
圖8-2 從VisualAge for Java 步驟一啟動持久名服務(wù)器
.. 在左手邊的窗格中選擇持久名服務(wù)器并輸入?yún)?shù)以連接到持久名服務(wù)器的配置數(shù)
據(jù)庫,請看圖8-3;
圖8-3 持久名服務(wù)器配置畫面
重點:在默認情況下,持久名服務(wù)器使用端口號900。如果您已經(jīng)指定了不同的端口的
話,那么為JNDI lookup 使用名服務(wù)器的程序就應(yīng)當(dāng)包括PROVIDER_URL 參數(shù)中的端
口號。
.. 點擊Start Name Server 按鈕啟動持久名服務(wù)器。
8.4.8 為配合使用JMS配置VisualAge for Java
設(shè)置VisualAge for Java 與JMSAdmin 配合庫或開發(fā)JMS 應(yīng)用程序的相關(guān)步驟如下:
.. 校驗IBM 企業(yè)版庫和IBM WebSphere 監(jiān)測上下文已加載入庫空間;
.. 調(diào)入以下JAR 文件到項目中??梢栽贛QInstallationDirectory\Java 下找到這些JAR
文件。我們在VisualAge for Java 中已創(chuàng)建了名為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 和添加目錄來實現(xiàn)
這一目的(請參見圖8-4)。
圖8-4 設(shè)置VisualAge for Java 庫空間類路徑
.. 如果您希望在訪問隊列管理器時使用綁定模式的話,那么路徑系統(tǒng)環(huán)境變量就應(yīng)
當(dāng)包括MQSeriesInstallDirectory\Java\bin 目錄。該目錄包括mqjbnd02.dll,在使用
綁定模式時要求具有該文件。
8.4.9 用JMSAdmin配合VisualAge for Java管理JMS JNDI對象
在本節(jié)中,我們將講解如何定義和管理用MQSeries JMS 管理工具JMSAdmin 配合
VisualAge for Java 實施JMS 時所需的JNDI 對象。
我們將在本章下面的編程樣例中介紹如何使用工具處理對象,既包括點到點模式,又包
括發(fā)布/預(yù)訂消息發(fā)送模式。
被使用的MQSeries 對象
表8-1 列出了程序樣例中用到的MQSeries 對象。
表8-1 程序樣例中用到的MQSeries 對象
對象名 描述
SAMPLE.QMGR1 程序樣例中將要使用的隊列管理器
PTP.QUEUE.LOCAL 點到點樣例中使用的隊列
PTP.REPLY.QUEUE.LOCAL 請求/回復(fù)發(fā)送回復(fù)消息所用的隊列
JMS.SVR.CHNL 客戶機連接所用的服務(wù)器連接通道
SYSTEM.JMS.ADMIN.QUEUE JMS 發(fā)布/預(yù)訂管理隊列
SYSTEM.JMS.PS.STATUS.QUEUE JMS 發(fā)布/預(yù)訂狀態(tài)隊列
SYSTEM.JMS.REPORT.QUEUE JMS 發(fā)布/預(yù)訂報表隊列
SYSTEM.JMS.MODEL.QUEUE JMS 發(fā)布/預(yù)訂預(yù)訂器模型隊列。(預(yù)訂器使
用該模型隊列為預(yù)訂創(chuàng)建永久隊列)
SYSTEM.JMS.ND.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂默認非可持續(xù)共享隊列(非可
持續(xù)預(yù)訂器使用的默認共享隊列)
SYSTEM.JMS.ND.CC.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂ConnectionConsumer 功能的默
認非可持續(xù)共享隊列
SYSTEM.JMS.D.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂默認可持續(xù)共享隊列(可持續(xù)
預(yù)訂器使用的默認共享隊列)
SYSTEM.JMS.D.CC.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂ConnectionConsumer 功能的默
認可持續(xù)共享隊列
261
8.4.10 定義JMS管理對象
首先,我們要實施點到點程序樣例使用的管理對象。我們將用作為JNDI 名服務(wù)器的持
久名服務(wù)器庫,并從VisualAge for Java 調(diào)用JMSAdmin 工具。您應(yīng)當(dāng)設(shè)置VisualAge 來
用JMS 庫,持久名服務(wù)器應(yīng)當(dāng)以第8.4.7 節(jié)《以VisualAge for Java 使用持久名服務(wù)器》
(見本書第256 頁)和第8.4.8 節(jié)《為配合使用JMS 配置VisualAge for Java》(見本書第
258 頁)介紹的方法配置和啟動。
從VisualAge for Java 調(diào)用JMSAdmin 工具
.. 更新JMSAdmin.config 文件指示JMSAdmin 將編輯初始上下文庫屬性值以使用持
久名服務(wù)器。
INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
提示:在LDAP 服務(wù)器中,它將是com.sun.indi.ldap.LdapCtxFactory,而在文件系
統(tǒng)上下文中,值將是com.sun.jndi.fscontext.RefFSContextFactory。
編輯PROVIDER_URL,以指向JNDI 名服務(wù)器。在這種情況下,我們是在名為ITSOG
的服務(wù)器上運行持久名服務(wù)器,因此它將是:
PROVIDER_URL=iiop://itsog/
.. 拷貝上述您正在處理項目(我們正在使用名為JMSTest 的項目)的項目資源中的
JMSAdmin.config 文件。我們可以通過以下方法從VisualAge for Java 實現(xiàn)此目的:
- 在資源標(biāo)簽上右鍵點擊項目,再點擊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 設(shè)置類路徑(步驟一)
.. 在隨后生成的JMSAdmin 屬性窗口中(見圖8-8),選擇Classpath 標(biāo)簽,而后點
擊Edit 并選擇IBM 企業(yè)擴展庫和IBM WebSphere 測試上下文,再點擊OK。
266
圖8-9 JMSAdmin 的類路徑
JMSAdmin 設(shè)置至此完成?,F(xiàn)在,我們就可以運行了。選中JMSAdmin 類,并且點擊
Run 按鈕來運行工具。啟動成功時控制臺將如圖8-10 所示。
267
圖8-10 JMSAdmin 控制臺
重點:如果您啟動JMSAdmin 工具遇到了問題,那么請校驗類路徑設(shè)置是正確的,且持
久名服務(wù)器已經(jīng)啟動。
現(xiàn)在,我們就可以在JNDI 創(chuàng)建JMS 管理對象了。
1. 創(chuàng)建上下文。我們使用JMSAdmin 命令DEFINE CONTEXT(context)名來為稱作
ptpCtx 的上下文(它將用于點到點程序樣例中)創(chuàng)建上下文。
在控制臺標(biāo)準(zhǔn)輸入?yún)^(qū)(見第268 頁上的圖8-11),輸入命令:(283)
def ctx(ptpCtx)
268
圖8-11 使用JMSAdmin 工具創(chuàng)建JNDI 上下文
為了顯示您剛剛定義的上下文,請使用dis ctx 命令。在顯示上下文時,您應(yīng)當(dāng)可以看到
您剛剛創(chuàng)建的上下文。請見圖8-12。
269
圖8-12 JMS admin 工具中的上下文
2. 利用以下命令改變到您剛剛創(chuàng)建的上下文:
chg ctx(ptpCtx)
3. 用下面的命令創(chuàng)建名為ptpQcf 的QueueConnection 庫:
def qcf(ptpQcf) transport(CLIENT)+
channel(JMS.SRV.CHNL) qmanager(SAMPLE.QMGR1) host(ITSOG)
4. 如下創(chuàng)建名為ptpQcf 的隊列對象:
def q(ptpQueue) queue(PTP.QUEUE.LOCAL)+
qmanager(SAMPLE.QMGR1)
您可以利用dis ctx 名校驗在當(dāng)前上下文下創(chuàng)建的對象。您應(yīng)當(dāng)可以看到
QueueConnecitonFactory(ptpQcf)和Queue(ptpQueue)對象,請見圖8-13。
270
圖8-13 顯示上下文
8.5 JMS應(yīng)用程序開發(fā)
JMS 應(yīng)用程序使用消息發(fā)送的點到點(PTP)或發(fā)布/預(yù)訂風(fēng)格。沒有什么會阻止這兩
種風(fēng)格結(jié)合到單一的應(yīng)用程序中;但是,JMS 集中在使用二者之一的應(yīng)用程序上。JMS
定義了這兩種風(fēng)格,因為它們代表著當(dāng)前使用的消息發(fā)送的兩種主要方法。由于許多消
息發(fā)送系統(tǒng)僅支持二者之一,因此JMS 為每種風(fēng)格都提供了不同的域,并為每個域定
義了兼容。
8.5.1 JMS點到點(PTP)模型
點到點消息發(fā)送包括消息隊列處理。發(fā)送器發(fā)送消息到通常由單一接收器使用的特定隊
列。在點到點通訊中,消息最多只有一個接收器。發(fā)送客戶機發(fā)送消息到包含目的(接
收)客戶機消息的隊列。您可以將隊列看作信箱。許多客戶機可能發(fā)送消息到隊列,但
消息僅由一個客戶機取出。而且,正如信箱一樣,消息直到刪除前會一直保存在隊列中。
因此,接收客戶機的可用性不影響發(fā)送消息的能力。在點到點系統(tǒng)中,客戶機可以是發(fā)
送器(消息生成器)、接收器(消息使用者)或二者都是。
271
8.5.2 點到點消息發(fā)送的編程方法
圖8-14 顯示了在JMS 中開發(fā)點到點消息發(fā)送程序的高級步驟:
圖8-14 JMS PTP 編程方法概述
272
點到點模型中的JMS 接口如下:
.. QueueConnection
.. QueueSession
.. QueueSender
.. QueueReceiver
當(dāng)使用JMS 時,連接不是直接生成的,而是利用連接庫構(gòu)建的。這些庫對象可以儲存
在JNDI 名稱空間中,從而隱藏了銷售商的特定實施。
在點到點消息發(fā)送中,一般有兩種消息發(fā)送模式,如圖8-15 所示。第一種方法是發(fā)送
-遺忘模型,第二種方法為請求/回復(fù)模式。
圖8-15 點到點消息發(fā)送模型
8.5.3 發(fā)送-遺忘
利用發(fā)送-遺忘模式(或發(fā)射-忘記模式)時,不期待從(數(shù)據(jù)報)消息接收器獲取回
復(fù)。
簡單消息生成器應(yīng)用程序
我們的第一臺PTP 客戶機將創(chuàng)建一條文本消息,并將其發(fā)送到MQSeries 隊列。我們還
將講解處理發(fā)送器發(fā)送的應(yīng)用程序的接收器程序。有關(guān)步驟如下:
273
.. 在JNDI 名稱空間查找QueueConnectionFactory 和Queue;
.. 獲取Queue Connection 對象;
.. 創(chuàng)建QueueSession;
.. 創(chuàng)建QueueSender;
.. 創(chuàng)建TextMessage;
.. 發(fā)送消息到Queue;
.. 關(guān)閉以及斷開與連接對象的連接。
程序PtpSender.java 顯示了有關(guān)開發(fā)點到點消息發(fā)送應(yīng)用程序的有關(guān)步驟。程序發(fā)送一
條簡單文本消息至MQSeries 隊列。
例8-1 PtpSender.java
Step 1 Import Necessary Packages(步驟一,導(dǎo)入所需的軟件包)
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 里創(chuàng)建的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(從隊列連接庫創(chuàng)建隊列連接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/**
Step 6 Start the QueueConnection.(步驟六,啟動隊列連接)
*/
queueConnection.start();
/**
Step 7 Create a QueueSession object from the QueueConnection(步驟七,從隊列連接創(chuàng)建隊列會話)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
Step 8 Create a QueueSender object for sending messages from the queue session.(步驟八,創(chuàng)建隊列發(fā)送器
以便從隊列會話發(fā)送消息)
*/
queueSender = queueSession.createSender(queue);
/**
Step 9 prepare a message object from the queuesession. we will create a
textMessage message object.(步驟九,從隊列會話準(zhǔn)備消息對象。我們將創(chuàng)建textMessage 消息對象。)
*/
message = queueSession.createTextMessage();
/**
Step 10 Set the message you want, to the message object.(步驟十,設(shè)置您想要的消息到消息對象。)
*/
message.setText("This is a Test Message from PtpSender Class " ) ;
/**
Step 11 Now we are ready to send the message.(步驟十一,現(xiàn)在我們已準(zhǔn)備好發(fā)送消息。)
*/
queueSender.send(message);
System.out.println(“\n The Message has been sent”);
/**
Step 12 Close the Queue Connection Before exiting from the program.(步驟十二,在從程序退出前關(guān)閉隊列
連接)
*/
queueConnection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
275
簡單消息使用者應(yīng)用程序
我們的下一個客戶機應(yīng)用程序是消息接收器應(yīng)用程序,它獲取PtpSender 應(yīng)用程序發(fā)送
的消息并打印出控制臺上的消息。有關(guān)步驟如下:
.. 在JNDI 名稱空間中查找QueueConnectionFactory 和Queue;
.. 獲取Queue Connection 對象;
.. 創(chuàng)建QueueSession;
.. 創(chuàng)建QueueReceiver;
.. 接收消息并顯示它;
.. 關(guān)閉以及斷開與連接對象的連接。
程序PtpReceiver.java 顯示了創(chuàng)建點到點消息使用者應(yīng)用程序的有關(guān)步驟。應(yīng)用程序從
MQSeries 隊列獲取消息并顯示消息。
例8-2 PtpReceiver.java
//Step 1 Import the Necessary Packages(步驟一,導(dǎo)入所需的軟件包)
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(步驟五,利用隊列連接庫創(chuàng)建隊列連
接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/*
*Step 6 Connections are always created in stopped mode. You have to Explicitly
start them. Start the queueConnection(步驟六,在停止模式里創(chuàng)建連接。您必須明確的啟動他們。啟動隊
列連接。)
*/
queueConnection.start();
/**
* Step 7 Create a queueSession object from the QueueConnection(步驟七,從隊列連接創(chuàng)建隊列會話對象)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
* Step 8 Create a QueueReceiver from the queueSession(步驟八,從隊列會話創(chuàng)建隊列接收器)
*/
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(步驟九,接收消息。在此我們實施同步消息接收。循環(huán)進行接收調(diào)用從而
能在隊列中處理所有可用消息。)
*/
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(步驟十,關(guān)閉連接)
*/
queueConnection.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
8.5.4 請求/回復(fù)
在請求/回復(fù)消息發(fā)送中,接收器接受請求消息后將發(fā)送回復(fù)到發(fā)送器。在接收器方,
準(zhǔn)備消息與PTP 中一樣,但除此之外JMSReplyToQueue 值將設(shè)置發(fā)送器期待從接收器
回復(fù)的Queue Name。當(dāng)獲取消息時,接收器可以使用回復(fù)到隊列名來發(fā)送回復(fù)消息至
發(fā)送器。
JMS 提供JMSReplyTo 消息標(biāo)題字段,指定應(yīng)當(dāng)發(fā)送消息回復(fù)的目的地。JMSCorrelationId
標(biāo)題字段可以用在回復(fù)消息中,提供到請求消息的引用。
請求/回復(fù)消息發(fā)送的應(yīng)用程序設(shè)計考慮
MQSeries 是種異步通訊機制。在請求/回復(fù)消息發(fā)送中,客戶機將消息放入隊列并等
待回復(fù)時,在應(yīng)用程序設(shè)計層次上必須小心。您不會愿意在隊列上無限制地等待回復(fù)消
息的到達,因為您并不知道您的請求獲取回復(fù)要花多長時間。應(yīng)用程序設(shè)計應(yīng)當(dāng)考慮到
延遲回復(fù)和根本無回復(fù)的情況。您也可能希望在等待回復(fù)消息時考慮設(shè)置合適的超時
值。
如果在等待回復(fù)消息時使用超時的話,那么您可能希望在生成回復(fù)消息的遠程客戶機方
考慮采用合適的過期時間值。在許多請求/回復(fù)情況下,您可以考慮為提高效能而使用
非持久消息。使用非持久消息將會帶來顯著的效能改善。
278
非持久消息可以用三種方法發(fā)送。持久屬性可以用以下方法之一設(shè)置:
.. 在創(chuàng)建MQSeries 隊列時直接在隊列管理器中的隊列對象上設(shè)置,或者您也可以在
現(xiàn)有MQSeries Queue 上改變它。
.. 在JNDI 中利用JMSAdmin 工具在隊列JMS 管理對象上設(shè)置。參數(shù)PERSISTENCE
可用來指定您希望設(shè)置的持久值。不同的值如下:
- APP:應(yīng)用程序定義的持久值(這是默認的)
- QDEF:MQSeries 隊列定義的持久值
- PERS:持久的消息
- NON:非持久的消息
如果您希望以持久NON 定義隊列對象名,那么您可以使用JMSAdmin 來指定命令:
DEFINE Q(ptpQueue) PERSISTENCE(NON)
.. 在JMS 應(yīng)用程序中對每條消息都使用DelieveryMode.NON.PERSISTENT 字段。消
息持久性可以在QueueSender 上調(diào)整,或者也可以在發(fā)送方法調(diào)用上指定,但不能
直接在消息上指定,請參見如下的例子:
QueueSender sender = queueSession.createSender(queue) ;
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
或者您也可以在發(fā)送消息時通過方法調(diào)用指定送達模式、優(yōu)先級和使用期限等來設(shè)
置送達模式。
8.5.5 JMS發(fā)布/預(yù)訂模型
與點到點通訊模型不同,發(fā)布/預(yù)訂模型可以讓一條消息送達多個接收器。發(fā)送客戶機
指引或發(fā)布消息至多個客戶機可以預(yù)訂的主題。一個主題可以有多個發(fā)布器和多個預(yù)訂
器。持久預(yù)訂或興趣在客戶機關(guān)閉和重啟時都存在。當(dāng)客戶機關(guān)閉時,將儲存所有已送
達主題的對象并在客戶機重新預(yù)訂時發(fā)送到客戶機。在發(fā)布/預(yù)訂系統(tǒng)中,客戶機可以
是發(fā)布器(消息生成器)、預(yù)訂器(消息使用者)或二者都是。
JMS 發(fā)布/預(yù)訂模型定義了在基于內(nèi)容的層次中JMS 客戶機如何發(fā)布消息至已知的節(jié)
點以及如何從已知的節(jié)點預(yù)訂消息。JMS 將這些節(jié)點稱作“主題”。
279
在本節(jié)中,我們使用的是發(fā)布和預(yù)訂這兩個術(shù)語,而不采用前面用到的生成和使用這兩
個一般術(shù)語。我們可以把主題看作微型消息中介,它收集并分配指向它的消息。消息發(fā)
送器依靠主題作為中間體從而獨立于預(yù)訂器,反過來也是如此。主題自動適應(yīng)發(fā)布器和
預(yù)訂器到來和離去。當(dāng)代表發(fā)布器和預(yù)訂器的Java 對象存在時,發(fā)布器和預(yù)訂器是活動
的。
JMS 也支持預(yù)訂器可選的持久性,并在其不活動時“記住”其存在。MQSeries 發(fā)布/
預(yù)訂使得應(yīng)用程序不必知道有關(guān)目標(biāo)應(yīng)用程序的任何信息。它需要做的就是把它希望分
享的信息發(fā)送到MQSeries 發(fā)布/預(yù)訂管理的標(biāo)準(zhǔn)目的地,并讓MQSeries 發(fā)布/預(yù)訂處
理分配。同樣,目標(biāo)應(yīng)用程序不必了解其接收的信息來源的任何信息。
圖8-16 顯示了用JMS 開發(fā)發(fā)布/預(yù)訂應(yīng)用程序的有關(guān)步驟。
280
圖8-16 JMS 發(fā)布/預(yù)訂編程概述
在JMS 實施發(fā)布/預(yù)訂消息發(fā)送模型中,所有特定銷售商實施都可以通過在java.jms
中的以下接口引用。所有這些均包括在以下接口的實施中:
.. QueueConnectionFactory
.. TopicConnectionFactory
.. Queue
.. Topic
在JMS 發(fā)布/預(yù)訂模型中,預(yù)訂主題時就可以實施主題異步預(yù)訂。
281
簡單JMS 發(fā)布/預(yù)訂應(yīng)用程序
在本節(jié)中,我們將用樣例程序JMSPublisher.java 來講解開發(fā)發(fā)布應(yīng)用程序的有關(guān)步驟。
編寫JMS 發(fā)布應(yīng)用程序的有關(guān)步驟如下:
1. 定義JMS 管理對象:
a. 創(chuàng)建JNDI 上下文。
我們使用以下命令來創(chuàng)建用于樣例程序中的名為psCtx 的JNDI 上下文:
DEF CTX(psCtx)
b. 利用以下命令改變到您剛剛創(chuàng)建的上下文中:
CHG CTX(psCtx)
c. 創(chuàng)建TopicConnectionFactory。
以下命令將創(chuàng)建指向QueueManager ITSOG.QMGR1(它位于名為ITSOG 的主
機上)的名為psTcf 的TopicConnectionFactory,它在默認端口1414 上進行監(jiān)
聽。用于客戶機連接的服務(wù)器連接為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 的主題創(chuàng)建名為psTopic 的
Topic。
DEF CTX(psCtx) TOPIC(JMSTest/SampleTopic)
2. 在JNDI 名稱空間查找TopicConnectionFactory 和Topic;
3. 創(chuàng)建Topic Connection;
4. 創(chuàng)建TopicSession;
5. 創(chuàng)建TopicPublisher;
6. 創(chuàng)建TextMessage;
7. 發(fā)布消息至Topic;
8. 關(guān)閉以及斷開與連接對象的連接。
282
我們的第一個JMS 發(fā)布/預(yù)訂客戶機程序JMSPublisher.java 將創(chuàng)建一條文本消息并發(fā)
送它到“SampleTopic”主題,它位于根主題JMSTest 之下。我們也將講解預(yù)訂主題
“TestTopic”的預(yù)訂器程序。
例8-3 JMSPublisher.java
//Step 1 Import the necessary packages(步驟一,導(dǎo)入所需的軟件包。)
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(步驟四,使用連接庫對象創(chuàng)建主題連
接。)
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(步驟七,從主題連接創(chuàng)建主題會話。)
283
topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
//Step 8 Create a topic publisher for the topic from the session.(步驟八,從會話為主題創(chuàng)建主題發(fā)布者。)
publisher = topicSession.createPublisher(topic);
//Step 9 Create a message object(步驟九,創(chuàng)建消息對象。)
message = topicSession.createTextMessage();
//Step 10 prepare the body of the message(步驟十,準(zhǔn)備消息主體。)
message.setText("This is a Test Message from JMSPublisher Class " ) ;
//Step 11 Publish the message.(步驟十一,發(fā)布消息。)
publisher.publish(message);
//Step 12 Close the connections.(步驟十二,關(guān)閉連接。)
publisher.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
簡單的JMS 預(yù)訂器應(yīng)用程序
在本節(jié)中,我們將通過創(chuàng)建一個樣例程序來講解開發(fā)JMS 預(yù)訂器應(yīng)用程序的有關(guān)步驟。
編寫JMS 預(yù)訂器應(yīng)用程序的有關(guān)步驟如下:
1. 在JNDI 名稱空間查找TopicConnectionFactory 和Topic;
2. 創(chuàng)建Topic Connection;
3. 創(chuàng)建TopicSession;
4. 創(chuàng)建TopicSubscriber;
5. 從Topic 接收預(yù)訂;
6. 關(guān)閉并斷開與連接對象的連接。
程序JMSSubscriber.java 是從“SampleTopic”主題(它位于根主題JMSTest 下)預(yù)訂消
息的簡單預(yù)訂器應(yīng)用程序。我們在例子中使用的是非可持續(xù)性預(yù)訂。
例8-4 JMSSubscriber.java
//Step 1 Import the necessary packages.(步驟一,導(dǎo)入所需軟件包。)
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(步驟四,創(chuàng)建主題連接。)
topicConnection = topicConnectionFactory.createTopicConnection();
//Step 5 Start The topic connection(步驟五,啟動主題連接。)
topicConnection.start();
//Step 6 Create a topic session from the topic connection(步驟六,從主題連接創(chuàng)建主題會話。)
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.(步驟八,為主題創(chuàng)建主題訂戶。)
subscriber = topicSession.createSubscriber(topic);//Non durable subscriber(非持久預(yù)定者)
//Step 9 Receive Subscription(步驟九,接收預(yù)訂。)
message = (TextMessage)subscriber.receive();
System.out.println("\n *** The Message is " + message.getText());
//Step 10Close the connection and other open resources(步驟十,關(guān)閉連接和其它打開資源。)
subscriber.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
285
8.6 異步處理
MQSeries JMS 提供了人們廣泛期待的MessageListener 接口。利用這個消息偵聽器功能,
客戶機就可以注冊偵聽器對象到消息使用者。當(dāng)消息到達使用者時,它通過調(diào)用
onMessage 方法送達至客戶機。
這是除利用其他方法(如觸發(fā)器、以等待發(fā)出接收或預(yù)訂調(diào)用或者在應(yīng)用程序代碼中采
用登記機制)外檢查新消息或預(yù)訂的另一種方法。當(dāng)用偵聽器使用異步送達模式時,與
消息使用者相關(guān)聯(lián)的整個會話都標(biāo)志為異步的。相同的會話不能用來發(fā)出任何顯式接收
調(diào)用。
因為應(yīng)用程序不發(fā)出顯式接收調(diào)用,因此在異步消息送達中,應(yīng)用程序代碼可能不能獲
取接受消息失敗所產(chǎn)生的例外。MQSeries JMS 提供了利用ExceptionListener 接口獲取異
常的功能。當(dāng)出現(xiàn)異常時,將調(diào)用onException 方法,而JMSException 將作為其參數(shù)傳
遞到方法。
8.6.1 消息偵聽器
我們通過實施MessageListener 接口并在偵聽器的onMessage 方法中提供應(yīng)用程序特定處
理來創(chuàng)建消息偵聽器。我們將討論如何在消息使用者應(yīng)用程序中實施簡單的消息偵聽
器。
我們首先創(chuàng)建偵聽器類(PTPListener.java),其擴展了JMSMessageListener 類并對
onMessage 方法進行了實施。當(dāng)消息到達時,消息作為自變量被送達至onMessage 方法。
當(dāng)消息到達時,我們就顯示消息。
例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 中使用偵聽器來實施異步處理
消息。您可以與我們在簡單發(fā)送器應(yīng)用程序中介紹的PtpSender 應(yīng)用程序一起來運行
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 異常偵聽器
當(dāng)以消息偵聽器利用異步消息送達模型時,應(yīng)用程序代碼不能接到接收消息失敗產(chǎn)生的
異常。這是因為應(yīng)用程序代碼沒有顯式調(diào)用receive()或subscribe()方法。為了在異
步處理中接到錯誤,您可以注冊ExceptionListener,它是實施onException()方法類的
實例。異常偵聽器使得客戶機可以異步接收錯誤通知。如果注冊了異常偵聽器的話,那
么當(dāng)出現(xiàn)錯誤時,就會調(diào)用onMessage()方法,JMSException 對象則作為自變量被傳
遞到該方法。異常偵聽器是在連接上設(shè)置的。
288
我們將用個非常簡單的應(yīng)用程序來講解如何實施異常偵聽器。
例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。我們將打印異常情況并且從程序的執(zhí)行中退出。根據(jù)您的使用情況添加適當(dāng)?shù)腻e誤
處理和恢復(fù)邏輯。)
*/
public void onException( JMSException e) {
e.printStackTrace();
System.exit(1);
}
}
我們現(xiàn)在已經(jīng)擁有了異常偵聽器,接下來就可以用JMSExceptionListener 實例、如下以
setExceptionListener 方法注冊異常偵聽器到連接對象,從而在異步使用者中利用偵聽器:
JMSExceptionListener exListener = new JMSExceptionListener( );
queueConnection.setExceptionListener(exListener);
setExceptionListner 方法為連接對象queueConnection 設(shè)置了異常偵聽器。
289
8.7 消息選擇器
JMS 提供在隊列上查詢消息的功能,因此我們可以根據(jù)給定的標(biāo)準(zhǔn)選擇消息子集。我們
可以把這看作是數(shù)據(jù)庫中的SQL 查詢功能。事實上,這種查找的語法就是以SQL92 條
件表達的標(biāo)準(zhǔn)為基礎(chǔ)的。消息選擇器可以指向JMS 消息標(biāo)題中的字段,也可以指向消
息屬性中的字段,即應(yīng)用程序定義的字段。
計算消息選擇器的順序是在優(yōu)先等級內(nèi)從左至右。您可以利用插句來改變計算順序。選
擇器字面值和操作器名是區(qū)分大小寫的。
選擇器可能包括:
.. 字面值:
- 字符串字面值包括在單一引用中,一個被包括的單一引用由復(fù)式單一引用代
表,如“literal”和“literal’s”。與Java String 字面值一樣,他們必須使用unicode
字符編碼;
- 精確數(shù)字字面值是沒有小數(shù)點的數(shù)字值,如57、-957、+62 等。支持Java 長
型范圍內(nèi)的數(shù)字。精確數(shù)字字面值使用Java 整數(shù)字面值語法;
- 近似數(shù)字字面值是科學(xué)記數(shù)法表示的數(shù)字值(如7E3, -57.9E2)或帶有小數(shù)
點的數(shù)字值(如7.、-95.7、+6.2 等)。支持Java 雙精度型范圍內(nèi)的數(shù)字。近
似字面值使用Java 浮點字面值語法;
- Boolean 字面值真和假。
.. 標(biāo)識符
- 標(biāo)識符是必須以Java 標(biāo)識符起始字符開始的無限長度字符序列,所有后續(xù)字符
必須為Java 標(biāo)識符部件字符。標(biāo)識符起始字符是Character.isJavaIdentifierStart
方法返回為真的任意字符。這包括‘_’ 和‘$’ 。標(biāo)識符部件字符是
Character.isJavaIdentifierPart 方法返回為真的任意字符。
- 標(biāo)識符不能是NULL, TRUE 或FALSE 名;
- 標(biāo)識符不能為NOT, AND, OR, BETWEEN, LIKE, IN 和IS;
- 標(biāo)識符可以是標(biāo)題子段引用,也可以是屬性引用;
- 標(biāo)識符是區(qū)分大小寫的;
- 消息標(biāo)題子段引用限于JMSDelieveryMode, JMSPriority, JMSMessageID,
JMSTimestamp, JMSCorrelationID 和JMSType 。JMSMessageID,
290
JMSCorrelationID 和JMSType 值可能為空,如果如此的話,那么它們將被作為
NULL 值對待;
- 任何以“JMSX”開始的名都是JMS 定義的屬性名;
- 任何以“JMS_”開始的名都是供應(yīng)方特定屬性名;
- 任何不以“JMS”開始的名都是應(yīng)用程序特定屬性名。如果引用消息中不存在
的屬性,它的值則為NULL。如果它確實存在的話,那么它的值就是相應(yīng)的屬
性值。
.. 空白空間與Java 中的定義一樣:空間、水平標(biāo)簽、表單反饋和行終止符。
.. 表達:
- 選擇器是條件表達。計算為真的選擇器匹配。計算為假或未知的選擇器不匹配;
- 算術(shù)表達由其自身、算術(shù)操作、帶數(shù)字值的標(biāo)識符和數(shù)字字面值構(gòu)成;
- 條件表達由其自身、比較操作、邏輯操作、由boolean 值的標(biāo)識符和boolean
字面值構(gòu)成;
- 為表達計算排序的標(biāo)準(zhǔn)括號()被支持。
.. 邏輯操作器的優(yōu)先順序為:NOT, AND, OR
.. 比較操作器:=, >, >=, <, <=, <> (不等于)
- 只有相同類型的值才可以比較。這里有一個例外,就是我們可以比較精確數(shù)字
值和近似數(shù)字值(要求的類型轉(zhuǎn)換由Java 數(shù)字升級定義)。如果嘗試比較不同
類型的值的話,選擇器總為假;
- 字符串和Boolean 比較限于=和<>。只有在兩個字符串包含相同的字符順序時,
它們才是相等的;
.. 算術(shù)操作器的優(yōu)先順序如下:
- +,- 一元的
- *,/ 乘和除
- +,- 加和減
- 算術(shù)操作必須使用Java 數(shù)字提升。
.. Arithmetic-expr1 [NOT] BETWEEN arithmetic-expr2 和arithmetic-expr3 比較操
作器
- 年齡介于15 到19 歲等于age >= 15 AND age <= 19;
- 年齡不介于15 至19 之間等于age < 15 OR age > 19。
291
.. 標(biāo)識符[NOT] IN (string-literal1, string-literal2,...)比較操作器,且標(biāo)識符有一
個字符串或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 操作的標(biāo)識符為NULL,那么操作的值為未知;
- 標(biāo)識符[NOT] LIKE pattern-value [ESCAPE escape-character]比較操作器,這
里標(biāo)識符有字符串值。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 操作標(biāo)識符為NULL,那么操作的值為未知。
.. 標(biāo)識符IS NULL 比較操作器檢測空標(biāo)題字段值或丟失屬性值。
- prop_name IS NULL
.. 標(biāo)識符IS NOT NULL 比較操作器檢測非空標(biāo)題字段值或?qū)傩灾档拇嬖凇?br /> - prop_name IS NOT NULL
我們要求JMS 供應(yīng)方在消息選擇器出現(xiàn)時校驗消息選擇器的語法正確性。提供語法不
正確選擇器的方法必須以JMS InvalidSelectionException 為結(jié)果。
以下消息選擇器選擇類型為汽車、顏色為藍、重量大于2500 磅的消息:
“JMSType = ‘car’ AND color = ‘blue’ AND weight > 2500”
292
空值:標(biāo)題字段和屬性值可能為NULL。計算選擇器表達的規(guī)則是,如果值為NULL,
那么SQL 就把NULL 值作為未知。因此,任何含有NULL 值的算術(shù)操作比較,其結(jié)果
都是未知值。
8.7.1 用消息選擇器庫
消息選擇器可以在消息上設(shè)置為用戶定義的屬性。在發(fā)送或發(fā)布方,我們可以通過利用
名值對的設(shè)置屬性方法來設(shè)置屬性名和它的值。
為了設(shè)置名為“testProperty”、值為100 以及數(shù)據(jù)類型為整數(shù)的屬性,我們可以在對象
消息上如下設(shè)置屬性:
messaget.setIntProperty(“testProperty”, 100) ;
設(shè)置屬性方法用到兩個自變量。第一個自變量是類型字符串,也就是屬性值的名。第二
個自變量是屬性值。我們應(yīng)當(dāng)根據(jù)希望設(shè)置值的數(shù)據(jù)類型選擇利用合適的設(shè)置屬性方法
(setIntProperty 用于整數(shù)值,setStringProperty 用于字符串類型值等)。
在消息使用者方,我們應(yīng)當(dāng)使用具有合適選擇標(biāo)準(zhǔn)的消息選擇器字符串。選擇標(biāo)準(zhǔn)是在
創(chuàng)建消息使用者的時候指定的。
我們使用的選擇標(biāo)準(zhǔn)是選擇屬性名為“testProperty”和值為100 的消息發(fā)送。我們可以
用如下方法達到此目的:
String selector = “testProperty = 100 “ ;
queueReceiver = session.createReceiver(queueName, selector).
用選擇器創(chuàng)建的消息使用者獲取屬性名為“testProperty”且值為100 的消息。
一旦創(chuàng)建與消息使用者相關(guān)聯(lián)的選擇器,JMS 規(guī)范就不允許它改變。如果您需要不同標(biāo)
準(zhǔn)的消息接收器的話,那么您可能需要創(chuàng)建另外的消息接收器。
一旦用消息選擇器創(chuàng)建了消息接收器,您就可以利用消息使用者上的getMessageSelector
()方法來檢查選擇器值。
在我們前面講解所用的queueReceiver 中,getMessageSelector 方法返回帶有選擇器值
queReceiver.getMessageSelector()的字符串。這將返回testProperty=100。
在本章中,我們將討論Java 消息發(fā)送服務(wù)(JMS)接口概念和MQSeries 實施,以及如
何使用JMS 編程。我們將在消息發(fā)送編程模式的上下文中探討JMS 概念。
8.1 什么是JMS?
與JDBC API for databases 一樣,Java Message Services(JMS)是消息發(fā)送的標(biāo)準(zhǔn)API。
JMS 規(guī)范(1.0.2)由Sun Microsystems 開發(fā),IBM 和其他企業(yè)消息發(fā)送銷售商、事務(wù)處
理銷售商以及RDBMS 銷售商都積極參與了開發(fā)過程。JMS 為Java 程序與對消息發(fā)送
系統(tǒng)對象進行各種操作的消息發(fā)送系統(tǒng)進行互動提供了一個常見的模型。程序?qū)οl(fā)
送系統(tǒng)對象進行的常見操作包括創(chuàng)建消息、發(fā)送消息、接收消息以及從企業(yè)消息發(fā)送系
統(tǒng)中讀取消息。JMS 為那些用Java 開發(fā)的程序提供了一種訪問這些消息發(fā)送系統(tǒng)操作
的常見方法。
JMS 具有兩種消息發(fā)送風(fēng)格,或者說它具有兩個域:
.. 一對一或點到點模型;
.. 發(fā)布/預(yù)訂模型。
JMS 僅僅是種規(guī)范。每個企業(yè)消息發(fā)送系統(tǒng)銷售商都必須就其特定的消息發(fā)送系統(tǒng)提供
實施規(guī)范的類。
在本章中,我們將描述JMS API 的MQSeries 實施、討論JMS API 概念和MQSeries 的
JMS 實施能力,并講解在可以利用MQSeries JMS 實施的不同情境中如何利用MQSeries
JMS。
為什么要使用JMS?
JMS 標(biāo)準(zhǔn)非常重要,其原因在于:
.. 它是第一個獲取廣泛跨行業(yè)支持的企業(yè)消息發(fā)送API;
.. 它提供的標(biāo)準(zhǔn)消息發(fā)送概念和慣例適用于廣泛的企業(yè)消息發(fā)送系統(tǒng),因而簡化了
企業(yè)應(yīng)用程序的開發(fā);
.. 它可以利用現(xiàn)有的、企業(yè)證明成功可行的消息發(fā)送系統(tǒng);
.. 它添加了完全用現(xiàn)有非JMS 客戶機解釋的新JMS 客戶機,從而允許您擴展現(xiàn)有的
基于消息的應(yīng)用程序;
.. 它允許您可以編寫便攜性強的基于消息的商業(yè)應(yīng)用程序。
8.2 概述
JMS 是定義JMS 客戶機如何訪問企業(yè)消息發(fā)送產(chǎn)品功能的一系列接口和相關(guān)語義。我
們這里所描述的消息是指企業(yè)應(yīng)用程序所使用的異步請求、報表或事件。它們既包含協(xié)
同這些系統(tǒng)所需的重要信息;又包含著描述特定商業(yè)行為的精確格式化的數(shù)據(jù)。通過這
些消息的交流,每個應(yīng)用程序都能跟蹤企業(yè)的發(fā)展。
JMS 定義了一系列常見的企業(yè)消息發(fā)送概念和功能。它最大限度地減少了Java 語言程
序設(shè)計人員在使用企業(yè)消息發(fā)送產(chǎn)品前必須學(xué)會的概念集。也最大限度地加強了消息發(fā)
送應(yīng)用程序的便攜性。JMS 標(biāo)準(zhǔn)雖然提供了獨立于不同銷售商的編程接口,但是并不定
義出通訊協(xié)議。
JMS 模型
JMS 定義了消息傳遞服務(wù)的一般視圖。理解該視圖,并弄清它是如何映射到底層的
MQSeries 傳輸上的,相當(dāng)重要。一般JMS 模型是建立在Sun 的javax.jms 包所定義的接
口上的,請見圖8-1。
圖8-1 JMS 模型
連接
連接提供了到底層傳輸?shù)脑L問,并被用來創(chuàng)建會話。在MQSeries 上下文中,連接提供
了儲存參數(shù),如隊列管理器名、遠程主機名(在Java 客戶連接性中)等的地方。換言之,
MQSeries JMS 連接一般都在Java 虛擬機之外分配MQSeries 資源。連接也支持同時使用。
連接可以提供以下好處:
.. 包括與JMS 供應(yīng)方的開放式連接。它通常代表客戶機和供應(yīng)方服務(wù)端口之間的一
個開放的TCP/IP 槽;
.. 它的創(chuàng)建就是客戶機認證發(fā)生之處;
.. 它可以指定唯一的客戶機標(biāo)識符;
.. 它提供ConnectionMetaData;
.. 它支持可選的ExceptionListener。
由于建立連接時完成了認證和通訊設(shè)置,因此連接相對來說是個重量級的JMS 對象。
大多數(shù)客戶機都使用單一的連接進行消息發(fā)送。其他更先進的應(yīng)用程序可能會使用幾個
連接。JMS 并不為使用多個連接而設(shè)計原因;但是,這樣做可能有操作上的原因。
JMS 客戶機一般創(chuàng)建一個連接、一個或多個會話以及許多消息生成器和使用者。當(dāng)創(chuàng)建
連接時,它處在停止模式,這就是說沒有消息再被送達。
重點:連接是在停止模式中創(chuàng)建的。
通常,直到設(shè)置完成前,連接都處在停止模式中。在完成時,將調(diào)用連接的start()方
法,而消息則開始到達連接的使用者。此設(shè)置慣例可以在客戶機仍在進行設(shè)置過程中盡
可能減少異步消息送達所帶來的任何客戶機混亂。
連接可以立即啟動,而設(shè)置可在隨后進行。這樣做的客戶機必須在設(shè)置過程中就準(zhǔn)備好
處理異步消息送達。
提示:消息生成器可以在停止連接時發(fā)送消息。
不是直接創(chuàng)建連接的,而是利用連接庫建立的。庫對象可以儲存在JNDI 名稱空間中,
從而將JMS 應(yīng)用程序與供應(yīng)方特定信息隔離開來。連接庫對象是利用MQSeries JMS 管
理工具JMSAdmin 創(chuàng)建的。該工具使得管理員可以給JNDI 名稱空間定義MQSeries JMS
對象的8 種類型。請參見第8.4.9 節(jié)《利用JMSAdmin 以VisualAge for Java 管理JMS JNDI
對象》(見本書第260 頁)。
創(chuàng)建連接
客戶機利用連接庫來創(chuàng)建連接。要使用何種連接庫類型取決于您想要哪種類型的連接:
.. 就PTP 連接而言,我們利用QueueConnectionFactory 或XAQueueConnectionFactory
來獲取QueueConnection 或XAQueueConnection;
.. 就發(fā)布/ 預(yù)訂消息發(fā)送模式而言, 我們利用TopicConnectionFactory 或
XATopicConnectionFactory 來獲取TopicConnection 或XATopicConnection。
為了創(chuàng)建連接,我們應(yīng)當(dāng)進行以工作:
.. 從JNDI 名稱空間接收庫對象
JNDI API 向以Java 編寫的應(yīng)用程序提供了命名和目錄功能。它是利用Java 的對象
模型專門為Java 設(shè)計的。利用JNDI,Java 應(yīng)用程序可以儲存并接收任何類型的命
名為Java 的對象。另外,JNDI 提供進行標(biāo)準(zhǔn)目錄操作的方法,如將屬性與對象相
關(guān)聯(lián)并利用對象屬性查找對象等。
在此常見的API 之后,可以無縫地插入不同的命名和目錄服務(wù)供應(yīng)方。這使得Java
應(yīng)用程序可以通過各種各樣的命名和目錄服務(wù)來利用信息,如LDAP、NDS、DNS
和NIS(YP)等,也使得Java 應(yīng)用程序可以與傳統(tǒng)應(yīng)用程序和系統(tǒng)并存。
在JNDI 中,所有的命名和目錄操作都是根據(jù)上下文進行的。沒有絕對的根。因此,
JNDI 定義了初始上下文,它是命名和目錄操作名解析的起始點。一旦有了初始上
下文,您就可以利用它來查看其他上下文和對象。
.. 為了從JNDI 名稱空間接收對象,必須按照下面這段代碼所顯示的那樣設(shè)置初始上
下文:
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()方法來創(chuàng)建連接,請參見如下的例子:
QueueConnection connection ;
connection =qFactory.createQueueConnection();
.. 啟動連接
JMS 規(guī)范定義指出,應(yīng)當(dāng)在“停止”狀態(tài)下創(chuàng)建連接。在您可以利用連接發(fā)送消息
之前,必須顯式啟動連接。
我們利用start()方法啟動連接,請參見如下的例子:
connection.start();
會話
為生成和使用消息提供上下文,包括用來創(chuàng)建消息生成器和消息使用者的方法。
JMS 會話是生成和使用消息的單線程上下文。盡管它可以在Java 虛擬機之外分配供應(yīng)
方資源,但我們還是將其看作輕量級JMS 對象。
我們可以分別利用連接對象的createQueueSession()或createTopicSession()方法來創(chuàng)
建會話。
創(chuàng)建會話方法包括兩個參數(shù):
1. 可決定會話是否進行事務(wù)處理的boolean。
在事務(wù)處理的會話中,全部發(fā)送、或者全部接收作為一個單位的一組消息。
在非事務(wù)處理會話中,分別發(fā)送或接收消息。
2. 定義識別模式的參數(shù)。
請參見如下的例子:
session =connection.createQueueSession(false ,Session.AUTO_ACKNOWLEDGE);
這是用AUTO_ACKNOWLEDGE 來創(chuàng)建非事務(wù)處理會話的最簡單的情況。連接是
線程安全的,但會話和由會話創(chuàng)建的對象不是線程安全的。我們建議多線程應(yīng)用程
序應(yīng)當(dāng)對每個線程都使用不同的會話。
會話有以下幾種用途:
.. 它是消息生成器和使用者的庫;
.. 它提供了供應(yīng)方優(yōu)化的消息庫;
.. 它既支持單一事務(wù)處理,又支持一系列將跨越生成器和使用者的庫結(jié)合到原子單
位的事務(wù)處理;
.. 會話為它使用和生成的消息定義了連續(xù)序列;會話保存了它使用的消息,直到識
別該消息;
.. 會話串行化注冊到消息使用者的消息偵聽器。
會話可以創(chuàng)建并服務(wù)于多個消息生成器和使用者。
一種典型的用法就是在消息到達前一直在同步消息使用者上保持線程塊。然后線程可以
使用一個或更多的會話消息生成器。
如果一個客戶機希望在其他客戶機使用消息時具備一個生成消息的線程,那么該客戶機
就應(yīng)當(dāng)為生成線程使用另外的會話。
一旦啟動了連接,任何具有偵聽器或注冊消息偵聽器的會話就會被賦予送達消息的控制
線程??蛻魴C代碼從另一個控制線程利用會話或任何構(gòu)成會話的對象都是錯誤的。唯一
的例外就是使用會話或連接關(guān)閉方法。
大多數(shù)客戶機將它們的庫自然地分成會話應(yīng)當(dāng)是比較容易的。該模型允許客戶機簡單啟
動并隨著并行操作需要的增加而逐漸增加消息線程的復(fù)雜度。
關(guān)閉方法是當(dāng)另一個線程正在執(zhí)行某個其他會話方法時唯一可以調(diào)用的會話方法。
我們可以選擇地指定會話為事務(wù)處理會話。每個事務(wù)處理會話都支持單一系列的事務(wù)處
理。每個事務(wù)處理會將一系列消息發(fā)送和一系列消息接收歸組到原子庫單元中。事實上,
事務(wù)處理將會話的輸入消息流和輸出消息流組織到原子單位集中。當(dāng)提交事務(wù)處理時,
會確認輸入的原子單位,同時發(fā)送與其相關(guān)聯(lián)的輸出原子單位。如果已經(jīng)進行了事務(wù)處
理回退,那么其發(fā)送的消息則被毀壞,會話的輸入也被自動恢復(fù)。
事務(wù)處理輸入和輸出單位的內(nèi)容就是會話的當(dāng)前事務(wù)處理中所生成和使用的那些消息。
我們可以利用會話的提交或回退方法完成事務(wù)處理。會話的當(dāng)前事務(wù)處理的完成將自動
開始下一個事務(wù)處理。其結(jié)果就是,事務(wù)處理會話總是具有一個當(dāng)前事務(wù)處理,且其庫
就在當(dāng)前事務(wù)處理中完成。
消息生成器
JMS 客戶機利用消息生成器發(fā)送消息到特定的目的地。我們通過傳遞目的地到會話對象
提供的創(chuàng)建消息生成器方法,從而創(chuàng)建消息生成器。
在點到點消息發(fā)送中,這將是利用QueueSession 對象上的createSender 方法所創(chuàng)建的
QueueSender。QueueSender 通常是為特定隊列創(chuàng)建的,因此所有利用該發(fā)送器發(fā)送的消
息都會被發(fā)送到同樣的目的地。我們利用隊列對象指定目的地。隊列對象即可以在運行
時間中創(chuàng)建,也可以在JNDI 名稱空間中構(gòu)造和儲存。請參見如下的例子:
Queue ioQueue ;
ioQueue = (Queue).ctx.lookup(qLookUp) ;
sender = session.createSender(ioQueue);
在發(fā)布/預(yù)訂消息發(fā)送中,這將是在TopicSession 對象上利用createPublisher 方法創(chuàng)建
的TopicPublisher。
通常,Topic 是在創(chuàng)建TopicPublisher 時指定的。在這種情況下,嘗試去使用方法,該方
法為未確認的TopicPublisher,將生成UnsupportedOperationException。
請參見如下的例子:
Topic topic ;
topic = (Topic)ctx.lookup( “cn=first.topic”) ;
TopicPublisher pub = session.createPublisher(topic);
客戶機也可以選擇不提供目的地而創(chuàng)建消息生成器。在這種情況下,目的地必須輸入每
個發(fā)送操作。這種風(fēng)格的消息生成器一般用來根據(jù)請求的replyTo 目的地向請求發(fā)送回
復(fù)。
客戶機可以通過消息生成器為發(fā)送的消息指定默認的送達模式、優(yōu)先級和使用期限。它
也可以為每條消息指定送達模式、優(yōu)先級和使用期限。
244
客戶機可以為它發(fā)送的每條消息指定單位為毫秒的使用期限值。這個值定義了消息到期
時間,也就是消息使用期限的和以及消息發(fā)送的GMT 時間(對事務(wù)處理發(fā)送而言,這
是指客戶機發(fā)送消息的時間而不是提交事務(wù)處理的時間)。
發(fā)送消息
消息是用消息生成器發(fā)送的。因此在點到點(PTP)模型中發(fā)送消息時,您應(yīng)當(dāng)使用
QueueSender 對象,而在發(fā)布/預(yù)訂模型中,您則應(yīng)當(dāng)使用TopicPublisher 對象。
在PTP 模型中,應(yīng)使用QueueSender 的send()方法發(fā)送消息。請參見如下的例子:
outmessage = session.createTextMessage();
outmessage.setText(“Sample Message “) ;
sender.send(outMessage);
在發(fā)布/預(yù)訂模型中,應(yīng)使用TopicPublisher 對象的發(fā)布方法來發(fā)布消息。請參見如下
的例子:
pub.publish(outMessage);
消息使用者
消息使用者是用來接收消息的。MessageConsumer 接口是所有消息使用者的父級接口。
在PTP 模型中,這將是QueueReceiver。在發(fā)布/預(yù)訂模型中,這將是TopicSubscriber。
我們可以用消息選擇器來創(chuàng)建消息使用者,消息選擇器允許客戶機根據(jù)消息選擇器指定
的標(biāo)準(zhǔn)選擇消息子集。欲了解詳細信息,請參見第8.7 節(jié)《消息選擇器》(見本書第289
頁)。
消息使用者客戶機可以同步接收消息,也可以讓消息在到達時異步送達。客戶機可以利
用其接收方法之一從消息使用者請求下一條消息。接收有幾種不同的形式,允許客戶機
登記或等待下一條消息。
客戶機可以向消息使用者注冊MessageListener 對象。當(dāng)消息到達消息使用者時,它是通
過調(diào)用MessageListeners onMessage 方法來送達消息的。欲了解更多信息,請參見第8.6
節(jié)《異步處理》(見本書第285 頁)。
245
在PTP 模型中,QueueReceiver 是利用QueueSession 對象上的createReceiver()方法創(chuàng)
建的。該方法采用的是從何處接收消息定義的Queue 參數(shù)。請參見如下的例子:
QueueReceiver queueReceiver = session.createReceiver(ioQueue) ;
在發(fā)布/預(yù)訂模型中,TopicSubscriber 對象是利用TopicSession 對象的createSubscriber
()方法創(chuàng)建的。請參見如下的例子:
TopicSubscriber sub = session.createSubscriber(topic);
接收消息
消息是用消息使用者接收的。在PTP 消息發(fā)送中,應(yīng)當(dāng)用QueueReceiver 對象來接收消
息,而在發(fā)布/預(yù)訂消息發(fā)送中,則應(yīng)當(dāng)用TopicSubscriber 對象來接收消息。
在PTP 模型中,您可以利用QueueReceiver 對象的接收方法來接收消息。請參見如下的
例子:
Message inMessage = queueReceiver.receive(800) ;
指定的參數(shù)是以毫秒為單位的超時。此方法發(fā)出調(diào)用,以接收指定超時間隔內(nèi)到達的下
一條消息。
在發(fā)布/預(yù)訂模型中,我們利用TopicSubscriber 的receive()方法來接收預(yù)訂。請參見
如下的例子:
Message inMsg = sub.receive() ;
這段代碼執(zhí)行了帶有等待的獲取方法。
重點:請注意,連接是線程安全的,但會話、消息生成器和消息使用者則不是。我們建
議的做法是對每個應(yīng)用程序線程使用一個會話。
在MQSeries 條款中,連接為臨時隊列提供了作用域。它也提供了地方,可以用來儲存
參數(shù),該參數(shù)控制如何連接到MQSeries。舉例來說,這些參數(shù)就是隊列管理器名和遠
程主機名(如果您使用MQSeries Java 客戶機連接的話)。會話包含HCONN,因此定義
了事務(wù)處理作用域。
消息生成器和消息使用者包含HOBJ,它定義了向其寫入或從其讀取的特定隊列。請注
意,正常的MQSeries 規(guī)則是適用的。在任何給定時間上,每個HCONN 只能進行一個
單一的操作。因此,不能同時調(diào)用消息生成器或與會話相關(guān)的消息使用者。
這與JMS 每個會話只有單一線程的限制是一致的。放置方法可以使用遠程隊列,但獲
取方法只能適用于本地隊列管理器上的隊列。一般JMS 接口是進一步劃分為更具體的
版本子集,該子集為點到點以及發(fā)布/預(yù)訂行為。點到點版本如下:
.. QueueConnection QueueSession QueueSender QueueReceiver
JMS 的關(guān)鍵理念就是,它編寫的應(yīng)用程序可以只在javax.jms 中接口引用。
我們強烈建議您這么做。所有各銷售商特有的信息都包含在以下實施中:
.. QueueConnectionFactor TopicConnectionFactory
- Queue
- Topic
以上這些稱作“管理對象”,即可以利用銷售商提供的管理工具構(gòu)造、且能被儲存在JNDI
名稱空間中的對象。JMS 應(yīng)用程序可以從名稱空間接收并使用這些對象,而不必知道由
哪個銷售商進行實施。MQSeries classes for Java Message Service 由許多Java 類和接口構(gòu)
成,這些類和接口是以Sun 的接口和類javax.jms 包為基礎(chǔ)的。
應(yīng)當(dāng)用下面所列的Sun 接口和類編寫客戶機,我們將在下面各節(jié)中詳細描述這些接口和
類??蓪嵤㏒un 接口和類MQSeries 對象的名稱具有“MQ”作為前綴(如果對象描述中
沒有做出其它規(guī)定的話)。描述包括有關(guān)MQSeries 對象與標(biāo)準(zhǔn)JMS 定義存在任何偏差
的細節(jié)。
事務(wù)處理
會話是在客戶機和消息發(fā)送系統(tǒng)之間的連接上一系列被發(fā)送和接收的消息。當(dāng)創(chuàng)建會話
時,它可以是非事務(wù)處理的(默認)也可以是事務(wù)處理的。事務(wù)處理會話保證一組消息
要么都被發(fā)送接收,要么都不被發(fā)送接收。非事務(wù)處理會話意味著消息被分別發(fā)送和接
收。在線購物就是事務(wù)處理會話的一個實例??蛻舸蜷_訂單(開始事務(wù)處理)??蛻暨x
擇的每項商品都是一條在訂單中添加商品的消息。客戶關(guān)閉訂單(結(jié)束事務(wù)處理)。如
果發(fā)送器提交了事務(wù)處理,那么將發(fā)送組中所有的消息。如果發(fā)送器回退事務(wù)處理的話,
那么將不訂購任何商品項目。
8.3 JMS消息
JMS 提供不同的消息類型。每種類型都包括有關(guān)其內(nèi)容的某些信息。
JMS 中定義的消息類型如下:
.. BytesMessage
.. MapMessage
.. ObjectMessage
.. StreamMessage
.. TextMessage
JMS 消息由以下三部分構(gòu)成:
.. 標(biāo)題
標(biāo)題字段包括供應(yīng)方用于路由和確認消息的信息,以及客戶機可以利用的信息。
.. 屬性
除標(biāo)題中的字段外,JMS 消息還為添加可選的標(biāo)題字段到消息提供功能。這些屬性
可以分為:
- 應(yīng)用程序特定屬性,客戶機為處理消息用這些屬性添加進程特定信息;
- 標(biāo)準(zhǔn)屬性,即JMS 特定屬性;
- 供應(yīng)方特定屬性,原始客戶機要求這些屬性。
.. 主體
這就是消息數(shù)據(jù)部分的所在。JMS 提供不同類型的消息主體,包括大部分當(dāng)前正被
使用的消息發(fā)送風(fēng)格。
8.3.1 映射JMS消息到MQSeries消息上
MQSeries 消息有三個組成部分:
.. MQSeries 消息描述器(MQMD);
.. MQSeries MQRFH2 標(biāo)題;
.. 消息主體。
MQRFH2 標(biāo)題是可選的。MQRFH2 標(biāo)題是具有固定部分和可變標(biāo)題部分的可擴展的標(biāo)
題。固定部分以標(biāo)準(zhǔn)MQSeries 標(biāo)題模式為模型。MQRFH2 標(biāo)題既可以攜帶與消息內(nèi)容
相關(guān)聯(lián)的JMS 特定數(shù)據(jù),又可以攜帶不直接與JMS 相關(guān)聯(lián)的其他信息。
我們用兩種方法將JMS 消息翻譯或轉(zhuǎn)化為MQSeries 消息。
.. 映射
映射是將JMS 消息標(biāo)題和消息屬性翻譯到有相應(yīng)MQMD 字段的相應(yīng)MQMD 值。
.. 拷貝
如果JMS 消息標(biāo)題和消息屬性值不具有相應(yīng)的MQMD 字段的話,JMS 標(biāo)題字段或
屬性將被傳遞,并可能作為MQRFH2 標(biāo)題中的一個字段被轉(zhuǎn)化。
是否包括MQRFH2 標(biāo)題是可選的。在發(fā)送消息中,JMS 目的地類的標(biāo)記決定是否包括
它。該標(biāo)記可以在定義JMS 管理對象時用JMSAdmin 工具來設(shè)定。管理員可以設(shè)置
MQSeries 目的地的TargetClient 值為JMSC.MQJMS_CLIENT_NONJMS_MQ,從而顯示
出JMS 客戶機正與非JMS 客戶機通訊。由于MQRFH2 標(biāo)題攜帶著JMS 特定信息,因
此如果發(fā)送器知道接收客戶機是JMS 客戶的話,您就應(yīng)當(dāng)包括它。如果接收器不是JMS
客戶機,那么就應(yīng)當(dāng)省略MQRFH2 標(biāo)題。
處理JMS 消息類型
在本節(jié)中,我們將利用發(fā)貨跟蹤ID(它作為JMS 客戶機的一條消息被發(fā)送)講解可以
如何使用不同的JMS 消息類型。
使用TextMessage 類
消息中的信息可以作為人類可以閱讀的文本字符串發(fā)送,客戶機也可以讀取并處理或者
顯示該字符串。
我們可以如下將跟蹤ID 作為TextMessage 對象中的字符串發(fā)送:
String trackingId ;
TextMessage message;
message =session.createTextMessage();
message.SetText(trackingId);
使用BytesMessage 類
在BytesMessage 中,信息是以二進制格式發(fā)送的。消息中的信息可以作為字節(jié)數(shù)組構(gòu)件。
可以如下準(zhǔn)備這樣的消息:
byte [] trackingId ;;
BytesMessage message ;
message =session.createBytesMessage();
message.writeBytes(trackingId);
使用映射消息
在映射消息中,信息可以作為名值對發(fā)送。這樣,消息本身可以包括消息所儲存數(shù)據(jù)的
元數(shù)據(jù)。
在我們所用的例子中,發(fā)送消息請求trackingId 上的信息,名(元數(shù)據(jù))可以是trackingId,
實際值(比如說AMX100000)將是名值對的值。
消息可以如下構(gòu)造:
String attributeName =“trackingId ”;
String attributeValue =“AMX100000 ”
MapMessage message;
message =session.createMapMessage();
message.setString(attributeName,attributeValue);
提示:如果值部分是較長的數(shù)據(jù)類型的話,那么您可以使用消息setLong 方法。請根據(jù)
您處理的數(shù)據(jù)類型選擇合適的方法。
使用流消息
在流消息中,與映射消息相似,消息可以由順序?qū)懭氲母鞣N字段構(gòu)成,每個字段都有其
自己原始的類型。在映射消息中,客戶機可以設(shè)置映射中任意數(shù)量的字段,處理客戶機
則可以讀取特定的字段而不必處理整個映射。但是,在流消息中,即便處理客戶機僅對
流消息中的字段子集感興趣,客戶機仍必須讀取消息中的每個字段(再摒棄它不感興趣
的字段)。此二者的另一個重要差別就是,在映射消息中,字段順序并不重要,但在流
消息中,字段是以寫入順序讀取的。
流消息可以如下構(gòu)造:
String attributeName =“trackingId ” ;;
String attributeValue =“AMX100000 ” ;;
StreamMessage message;
session.createStreamMessage();
message.writeString(attributeName);
message.writeString(attributeValue);
使用對象消息
對象消息可被用來將Java 對象作為一條消息傳遞,該消息就是接收客戶機可以在對象中
利用方法提取數(shù)據(jù)的消息。在下面的例子中,我們使用的是跟蹤對象:
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.(利用以上跟蹤對象,我們能發(fā)送作為跟蹤對象的跟蹤消息)
We can construct such a message as:(我們能構(gòu)造如下的消息)
String attributeName =“trackingId ” ;;
String attributeValue =“ AMX100000” ;
TrackingObject trakingObject =new TrackingObject();
ObjectMessage =message ;
trackingObject.setAttributeName(attributeName);
trackingObject.setAttributeValue(attributeValue);
message =session.createObjectMessage();
message.setObject(trackingObject);
在接收方,客戶機可以使用跟蹤對象的獲取方法來提取數(shù)據(jù)。
消息識別
如果客戶機指定JMS 使用者的消息應(yīng)被顯式識別的話,那么JMS 消息也支持使用識別
方法。如果客戶機使用自動識別的話,那么將忽略識別調(diào)用。
消息識別有三種類型。消息識別的類型是在會話創(chuàng)建時指定的。這些不同的類型如下:
.. AUTO_ACKNOWLEDGE
在AUTO_ACKNOWLEDGE 模式中,當(dāng)消息成功從調(diào)用返回到接收器,或消息使
用者注冊的處理消息的消息偵聽器成功返回時,消息會話將自動識別消息。
Session session =queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
.. CLIENT_ACKNOWLEDGE
利用CLIENT_ACKNOWLEDGE 模式,客戶機通過調(diào)用消息上的識別方法顯式并
確認消息。
Session session =queueConnection.createQueueSession(false,
Session.CLIENT_ACKNOWLEDGE);
Then once the message is processed,the client can issue(然后一旦處理完消息,客戶機就能發(fā)行)
message.acknowledge();
method to acknowledge the message.(方法以確認該消息)
當(dāng)使用CLINET_ACKNOWLEDGE 模式時,我們在處理消息過程中必須注意避免
大量未識別消息的積累,未識別消息的積累可能導(dǎo)致資源耗盡,帶來失敗。
.. DUPS_OK_ACKNOWLEDGE
DUPS_OK_ACKNOWLEDGE 模式命令會話遲鈍地確認消息送達。如果JMS 失敗
的話,其結(jié)果就可能是重復(fù)消息發(fā)送。允許處理重復(fù)消息的使用者應(yīng)當(dāng)使用這種模
式。在客戶機允許重復(fù)消息的情況下,利用這種模式可以獲取一些效能改善,因為
會話在避免重復(fù)消息時的開銷較小。
8.3.2 JMS補充特性
JMS 還包括其他一些特性:
.. 異步消息送達
- 使用消息偵聽器概念;
- 使用基于事件的模型,它在事先設(shè)定的事件上觸發(fā)指定的函數(shù)。
JMS 客戶機可以注冊偵聽器對象,它可以實施具有消息使用者(消息接收器)的
MessageListener 接口。當(dāng)注冊使用者的消息到達時,我們可以調(diào)用listeners
onMessage 方法讓消息對消息使用者可用。
.. 消息選擇器
- 基于內(nèi)容的特定消息接收
- 使用基于SQL92 的查詢函數(shù)
JMS 消息具有向JMS 消息標(biāo)題(在消息實際主體外)提供用戶定義元數(shù)據(jù)的功能。JMS
程序可以利用這一功能根據(jù)選擇標(biāo)準(zhǔn)選擇消息子集,換言之,JMS 客戶機可以僅選擇那
些自己感興趣的消息。
8.4 MQSeries JMS實施
用MQSeries 實施JMS 的重點如下:
.. 支持的平臺:AIX, HP-UX, Windows NT, Solaris 和 Linux
.. 由包括關(guān)鍵功能的JAR 文件構(gòu)成
- com.ibm.mq.jms.jar
- com.ibm.mq.jar
.. 定義管理對象到JNDI 名稱空間的管理工具:
- JMSAdmin
.. 作為產(chǎn)品延伸可以通過網(wǎng)絡(luò)下載獲取
.. MQSeries classes for Java 和JMS with MQSeries V5.2
8.4.1 MQSeries JMS的安裝
為了使JMS 支持MQSeries,您必須安裝并實施JMS 接口的Java 類。JMS classes for
MQSeries 可以從下述IBM 網(wǎng)站的MA88 SupportPac 下載獲取:
http://www-3.ibm.com/software/ts/mqseries/txppacs/
此SupportPac 是免費的。您可以根據(jù)支持的平臺下載合適的版本,并按照隨SupportPac
提供的安裝說明進行安裝。
重點:當(dāng)您在安裝JMS SupportPac 時,位于MQSeries 安裝的Java 次級目錄中的所有文
件都會在安裝過程中備份,新文件拷貝自SupportPac。如果在您的上下文中已經(jīng)安裝了
AMI 支持,那么您可能需要重新安裝AMI SupportPac。
一旦您已安裝了JMS SupportPac,為了用JMS 運行發(fā)布/預(yù)訂應(yīng)用程序,您還需要運行
稱作MQJMS_PSQ.mqsc 的MQSC 腳本,它位于MQSeries 安裝目錄下的java\bin 次級目
錄中。為了在默認的隊列管理器上運行腳本,請從操作系統(tǒng)命令行窗口中運行以下命令:
runmqsc <“C:\Program Files \IBM \MQSeries \java \bin \MQJMS_PSQ.mqsc ”
這里,我們假定C:\Program Files \IBM \MQSeries \就是MQSeries 的安裝目錄。
8.4.2 JMS管理對象——JNDI和JMSAdmin
目錄服務(wù)包括命名功能,在消息發(fā)送語句中提供包括各種實體(機器名、服務(wù)、用戶以
及MueueManagers, Queues, Topics 等供應(yīng)方特定對象)安排和確認的抽象層次名稱空間。
這就使得我們可以利用邏輯名稱空間,簡化了網(wǎng)絡(luò)中的對象發(fā)現(xiàn)和識別。Java 命名和目
錄接口(JNDI)API 實施向以Java 開發(fā)的程序提供了目錄和命名功能。這是的Java 程
序可以從JNDI 名稱空間發(fā)現(xiàn)并接收任何類型的對象。
JMS 管理對象是JMS 應(yīng)用程序儲存并從JNDI 名稱空間接收的對象。JMS 管理對象通常
由管理員創(chuàng)建。JMS 管理對象包含底層消息發(fā)送服務(wù)供應(yīng)方信息的配置信息。在這節(jié)中,
我們將就以MQSeries 開發(fā)和應(yīng)用JMS 應(yīng)用程序來討論這些對象的定義、管理和使用。
重點:在程序樣例中,我們將使用作為JNDI 服務(wù)器的WebSphere 應(yīng)用程序服務(wù)器所提
供的持久名服務(wù)器。我們是利用VisualAge for Java 企業(yè)版和Websphere application Server
來開發(fā)程序樣例的。
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 調(diào)用管理工具
管理工具具有命令行接口。您可以互動地利用此接口,也可以用它來啟動批處理進程。
互動模式提供命令提示符,您可以在此輸入管理命令。在批處理模式中,啟動工具的命
令包括包含管理命令腳本的文件名。
為了在互動模式下啟動該工具,請輸入以下命令:
JMSAdmin [-t ] [-v ] [-cfg config_filename ]
在這里:
.. -t 啟動跟蹤(默認情況下為跟蹤關(guān)閉)
.. -v 生成詳細輸出(默認情況下為簡潔輸出)
.. -cfg config_filename 為備用配置文件名。
8.4.5 JMSAdnub工具配置
JMSAdmin 工具在其中的以下參數(shù)使用配置文件:
.. INITIAL_CONTEXT_FACTORY
這顯示出工具使用的服務(wù)供應(yīng)方?,F(xiàn)在該屬性有3 個被支持的值:
- com.sun.jndi.ldap.LdapCtxFactory
當(dāng)您使用LDAP 服務(wù)器作為JNDI 服務(wù)器時,您需要使用此類名。(在我們的測
試中,我們將使用IBM Secureway Server 3.2.1。)
- com.sun.jndi.fscontext.RefFSContextFactory
當(dāng)您在使用文件系統(tǒng)儲存JNDI 對象時,應(yīng)當(dāng)使用此類名。
- com.ibm.ejs.ns.jndi.CNInitialContextFactory
當(dāng)您在使用Websphere 的持久名服務(wù)器時,您應(yīng)當(dāng)使用此類名。
.. PROVIDER_URL
這顯示出會話初始上下文的URL,即該工具執(zhí)行的所有JNDI 操作的根?,F(xiàn)在支持
該屬性的有3 種形式:
- ldap://hostname/contextname (針對LDAP)
- file:[drive:]/pathname (針對文件系統(tǒng)上下文)
該工具不會創(chuàng)建路徑指定的目錄。您應(yīng)當(dāng)在使用JMSAdmin 工具前確保目錄存
在。
- iiop://hostname[:port] /[?TargetContext=ctx] (針對WebSphere Persistent
Name Server)
.. SECURITY_AUTHENTICATION
這顯示出JNDI 是否傳遞安全證明到您的服務(wù)供應(yīng)方。只有在使用LDAP 服務(wù)供應(yīng)
方時才會用到該參數(shù)。該參數(shù)現(xiàn)在可以具有以下三個值之一:
- None(匿名識別)
- Simple(簡單識別)
- CRAM-MD5(CRAM-MD5 識別機制)
以上的值指定在一個配置文件中,您可以在以-cfg 參數(shù)調(diào)用JMSAdmin 工具時指定配
置文件。稱作jmsadmin.config 的配置文件樣例位于MQSeriesInstalldirectory\java\bin,您
可以編輯它或使用它,以適應(yīng)上下文的屬性值來創(chuàng)建配置文件。
8.4.6 以持久名服務(wù)器使用JMSAdmin
編輯隨安裝帶來的JMSAdmin.config 文件(您可以在MQSeriesInstalldirectory\java\bin 下
找到該文件),指示JMSAdmin 工具應(yīng)當(dāng)使用持久名服務(wù)器,并編輯PROVIDER_URL
屬性值,以指向持久名服務(wù)器。而后在另一個文件夾中保存文件,例如C:\temp。
在JMSAdmin.config 文件中,我們提供如下值:
.. INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
該值對VisualAge for Java 企業(yè)版和WebSphere 應(yīng)用程序服務(wù)器提供的持久名服務(wù)
器有效。對另一個JNDI 服務(wù)器而言,INITIAL_CONTEXT_FACTORY 變量必須隨
配套文檔提供。
.. PROVIDER_URL=iiop://hostname/
在此,hostname 即持久名服務(wù)器運行的機器名。當(dāng)使用IIOP 協(xié)議時,默認端口為
900,不必提及。如果您改變持久名服務(wù)器的默認端口(默認端口為900)的話,那
么您必須在PROVIDER_URL 變量中指示端口,例如:iiop://hostname:901/(這
里假定您將默認端口900 改為901 端口)。
利用-cfg 參數(shù)調(diào)用JMSAdmin 工具,并給出您在編輯后保存JMSAdmin.config 文件的
路徑。
8.4.7 以VisualAge for Java使用持久名服務(wù)器
持久名服務(wù)器的數(shù)據(jù)庫應(yīng)當(dāng)可以利用JDBC 從VisualAge for Java 訪問到。如果是第一次
設(shè)置持久名服務(wù)器的話,您將需要創(chuàng)建數(shù)據(jù)庫(我們利用UDB7.1 作為數(shù)據(jù)庫服務(wù)器)。
我們創(chuàng)建了稱作jndi 的數(shù)據(jù)庫。為了讓持久名服務(wù)器利用JDBC 訪問數(shù)據(jù)庫,VisualAge
for Java 庫空間類路徑也應(yīng)當(dāng)包括Db2UdbInstallDirectory\java\db2java.zip。
從VisualAge for Java 啟動持久名服務(wù)器
.. 從VisualAge for Java 菜單欄選擇Workspace - > Tools - >Websphere Test
Environment(見圖8-2)。
圖8-2 從VisualAge for Java 步驟一啟動持久名服務(wù)器
.. 在左手邊的窗格中選擇持久名服務(wù)器并輸入?yún)?shù)以連接到持久名服務(wù)器的配置數(shù)
據(jù)庫,請看圖8-3;
圖8-3 持久名服務(wù)器配置畫面
重點:在默認情況下,持久名服務(wù)器使用端口號900。如果您已經(jīng)指定了不同的端口的
話,那么為JNDI lookup 使用名服務(wù)器的程序就應(yīng)當(dāng)包括PROVIDER_URL 參數(shù)中的端
口號。
.. 點擊Start Name Server 按鈕啟動持久名服務(wù)器。
8.4.8 為配合使用JMS配置VisualAge for Java
設(shè)置VisualAge for Java 與JMSAdmin 配合庫或開發(fā)JMS 應(yīng)用程序的相關(guān)步驟如下:
.. 校驗IBM 企業(yè)版庫和IBM WebSphere 監(jiān)測上下文已加載入庫空間;
.. 調(diào)入以下JAR 文件到項目中??梢栽贛QInstallationDirectory\Java 下找到這些JAR
文件。我們在VisualAge for Java 中已創(chuàng)建了名為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 和添加目錄來實現(xiàn)
這一目的(請參見圖8-4)。
圖8-4 設(shè)置VisualAge for Java 庫空間類路徑
.. 如果您希望在訪問隊列管理器時使用綁定模式的話,那么路徑系統(tǒng)環(huán)境變量就應(yīng)
當(dāng)包括MQSeriesInstallDirectory\Java\bin 目錄。該目錄包括mqjbnd02.dll,在使用
綁定模式時要求具有該文件。
8.4.9 用JMSAdmin配合VisualAge for Java管理JMS JNDI對象
在本節(jié)中,我們將講解如何定義和管理用MQSeries JMS 管理工具JMSAdmin 配合
VisualAge for Java 實施JMS 時所需的JNDI 對象。
我們將在本章下面的編程樣例中介紹如何使用工具處理對象,既包括點到點模式,又包
括發(fā)布/預(yù)訂消息發(fā)送模式。
被使用的MQSeries 對象
表8-1 列出了程序樣例中用到的MQSeries 對象。
表8-1 程序樣例中用到的MQSeries 對象
對象名 描述
SAMPLE.QMGR1 程序樣例中將要使用的隊列管理器
PTP.QUEUE.LOCAL 點到點樣例中使用的隊列
PTP.REPLY.QUEUE.LOCAL 請求/回復(fù)發(fā)送回復(fù)消息所用的隊列
JMS.SVR.CHNL 客戶機連接所用的服務(wù)器連接通道
SYSTEM.JMS.ADMIN.QUEUE JMS 發(fā)布/預(yù)訂管理隊列
SYSTEM.JMS.PS.STATUS.QUEUE JMS 發(fā)布/預(yù)訂狀態(tài)隊列
SYSTEM.JMS.REPORT.QUEUE JMS 發(fā)布/預(yù)訂報表隊列
SYSTEM.JMS.MODEL.QUEUE JMS 發(fā)布/預(yù)訂預(yù)訂器模型隊列。(預(yù)訂器使
用該模型隊列為預(yù)訂創(chuàng)建永久隊列)
SYSTEM.JMS.ND.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂默認非可持續(xù)共享隊列(非可
持續(xù)預(yù)訂器使用的默認共享隊列)
SYSTEM.JMS.ND.CC.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂ConnectionConsumer 功能的默
認非可持續(xù)共享隊列
SYSTEM.JMS.D.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂默認可持續(xù)共享隊列(可持續(xù)
預(yù)訂器使用的默認共享隊列)
SYSTEM.JMS.D.CC.SUBSCRIBER.QUEUE JMS 發(fā)布/預(yù)訂ConnectionConsumer 功能的默
認可持續(xù)共享隊列
261
8.4.10 定義JMS管理對象
首先,我們要實施點到點程序樣例使用的管理對象。我們將用作為JNDI 名服務(wù)器的持
久名服務(wù)器庫,并從VisualAge for Java 調(diào)用JMSAdmin 工具。您應(yīng)當(dāng)設(shè)置VisualAge 來
用JMS 庫,持久名服務(wù)器應(yīng)當(dāng)以第8.4.7 節(jié)《以VisualAge for Java 使用持久名服務(wù)器》
(見本書第256 頁)和第8.4.8 節(jié)《為配合使用JMS 配置VisualAge for Java》(見本書第
258 頁)介紹的方法配置和啟動。
從VisualAge for Java 調(diào)用JMSAdmin 工具
.. 更新JMSAdmin.config 文件指示JMSAdmin 將編輯初始上下文庫屬性值以使用持
久名服務(wù)器。
INITIAL_CONTEXT_FACTORY=com.ibm.ejs.ns.jndi.CNInitialContextFactory
提示:在LDAP 服務(wù)器中,它將是com.sun.indi.ldap.LdapCtxFactory,而在文件系
統(tǒng)上下文中,值將是com.sun.jndi.fscontext.RefFSContextFactory。
編輯PROVIDER_URL,以指向JNDI 名服務(wù)器。在這種情況下,我們是在名為ITSOG
的服務(wù)器上運行持久名服務(wù)器,因此它將是:
PROVIDER_URL=iiop://itsog/
.. 拷貝上述您正在處理項目(我們正在使用名為JMSTest 的項目)的項目資源中的
JMSAdmin.config 文件。我們可以通過以下方法從VisualAge for Java 實現(xiàn)此目的:
- 在資源標(biāo)簽上右鍵點擊項目,再點擊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 設(shè)置類路徑(步驟一)
.. 在隨后生成的JMSAdmin 屬性窗口中(見圖8-8),選擇Classpath 標(biāo)簽,而后點
擊Edit 并選擇IBM 企業(yè)擴展庫和IBM WebSphere 測試上下文,再點擊OK。
266
圖8-9 JMSAdmin 的類路徑
JMSAdmin 設(shè)置至此完成?,F(xiàn)在,我們就可以運行了。選中JMSAdmin 類,并且點擊
Run 按鈕來運行工具。啟動成功時控制臺將如圖8-10 所示。
267
圖8-10 JMSAdmin 控制臺
重點:如果您啟動JMSAdmin 工具遇到了問題,那么請校驗類路徑設(shè)置是正確的,且持
久名服務(wù)器已經(jīng)啟動。
現(xiàn)在,我們就可以在JNDI 創(chuàng)建JMS 管理對象了。
1. 創(chuàng)建上下文。我們使用JMSAdmin 命令DEFINE CONTEXT(context)名來為稱作
ptpCtx 的上下文(它將用于點到點程序樣例中)創(chuàng)建上下文。
在控制臺標(biāo)準(zhǔn)輸入?yún)^(qū)(見第268 頁上的圖8-11),輸入命令:(283)
def ctx(ptpCtx)
268
圖8-11 使用JMSAdmin 工具創(chuàng)建JNDI 上下文
為了顯示您剛剛定義的上下文,請使用dis ctx 命令。在顯示上下文時,您應(yīng)當(dāng)可以看到
您剛剛創(chuàng)建的上下文。請見圖8-12。
269
圖8-12 JMS admin 工具中的上下文
2. 利用以下命令改變到您剛剛創(chuàng)建的上下文:
chg ctx(ptpCtx)
3. 用下面的命令創(chuàng)建名為ptpQcf 的QueueConnection 庫:
def qcf(ptpQcf) transport(CLIENT)+
channel(JMS.SRV.CHNL) qmanager(SAMPLE.QMGR1) host(ITSOG)
4. 如下創(chuàng)建名為ptpQcf 的隊列對象:
def q(ptpQueue) queue(PTP.QUEUE.LOCAL)+
qmanager(SAMPLE.QMGR1)
您可以利用dis ctx 名校驗在當(dāng)前上下文下創(chuàng)建的對象。您應(yīng)當(dāng)可以看到
QueueConnecitonFactory(ptpQcf)和Queue(ptpQueue)對象,請見圖8-13。
270
圖8-13 顯示上下文
8.5 JMS應(yīng)用程序開發(fā)
JMS 應(yīng)用程序使用消息發(fā)送的點到點(PTP)或發(fā)布/預(yù)訂風(fēng)格。沒有什么會阻止這兩
種風(fēng)格結(jié)合到單一的應(yīng)用程序中;但是,JMS 集中在使用二者之一的應(yīng)用程序上。JMS
定義了這兩種風(fēng)格,因為它們代表著當(dāng)前使用的消息發(fā)送的兩種主要方法。由于許多消
息發(fā)送系統(tǒng)僅支持二者之一,因此JMS 為每種風(fēng)格都提供了不同的域,并為每個域定
義了兼容。
8.5.1 JMS點到點(PTP)模型
點到點消息發(fā)送包括消息隊列處理。發(fā)送器發(fā)送消息到通常由單一接收器使用的特定隊
列。在點到點通訊中,消息最多只有一個接收器。發(fā)送客戶機發(fā)送消息到包含目的(接
收)客戶機消息的隊列。您可以將隊列看作信箱。許多客戶機可能發(fā)送消息到隊列,但
消息僅由一個客戶機取出。而且,正如信箱一樣,消息直到刪除前會一直保存在隊列中。
因此,接收客戶機的可用性不影響發(fā)送消息的能力。在點到點系統(tǒng)中,客戶機可以是發(fā)
送器(消息生成器)、接收器(消息使用者)或二者都是。
271
8.5.2 點到點消息發(fā)送的編程方法
圖8-14 顯示了在JMS 中開發(fā)點到點消息發(fā)送程序的高級步驟:
圖8-14 JMS PTP 編程方法概述
272
點到點模型中的JMS 接口如下:
.. QueueConnection
.. QueueSession
.. QueueSender
.. QueueReceiver
當(dāng)使用JMS 時,連接不是直接生成的,而是利用連接庫構(gòu)建的。這些庫對象可以儲存
在JNDI 名稱空間中,從而隱藏了銷售商的特定實施。
在點到點消息發(fā)送中,一般有兩種消息發(fā)送模式,如圖8-15 所示。第一種方法是發(fā)送
-遺忘模型,第二種方法為請求/回復(fù)模式。
圖8-15 點到點消息發(fā)送模型
8.5.3 發(fā)送-遺忘
利用發(fā)送-遺忘模式(或發(fā)射-忘記模式)時,不期待從(數(shù)據(jù)報)消息接收器獲取回
復(fù)。
簡單消息生成器應(yīng)用程序
我們的第一臺PTP 客戶機將創(chuàng)建一條文本消息,并將其發(fā)送到MQSeries 隊列。我們還
將講解處理發(fā)送器發(fā)送的應(yīng)用程序的接收器程序。有關(guān)步驟如下:
273
.. 在JNDI 名稱空間查找QueueConnectionFactory 和Queue;
.. 獲取Queue Connection 對象;
.. 創(chuàng)建QueueSession;
.. 創(chuàng)建QueueSender;
.. 創(chuàng)建TextMessage;
.. 發(fā)送消息到Queue;
.. 關(guān)閉以及斷開與連接對象的連接。
程序PtpSender.java 顯示了有關(guān)開發(fā)點到點消息發(fā)送應(yīng)用程序的有關(guān)步驟。程序發(fā)送一
條簡單文本消息至MQSeries 隊列。
例8-1 PtpSender.java
Step 1 Import Necessary Packages(步驟一,導(dǎo)入所需的軟件包)
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 里創(chuàng)建的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(從隊列連接庫創(chuàng)建隊列連接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/**
Step 6 Start the QueueConnection.(步驟六,啟動隊列連接)
*/
queueConnection.start();
/**
Step 7 Create a QueueSession object from the QueueConnection(步驟七,從隊列連接創(chuàng)建隊列會話)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
Step 8 Create a QueueSender object for sending messages from the queue session.(步驟八,創(chuàng)建隊列發(fā)送器
以便從隊列會話發(fā)送消息)
*/
queueSender = queueSession.createSender(queue);
/**
Step 9 prepare a message object from the queuesession. we will create a
textMessage message object.(步驟九,從隊列會話準(zhǔn)備消息對象。我們將創(chuàng)建textMessage 消息對象。)
*/
message = queueSession.createTextMessage();
/**
Step 10 Set the message you want, to the message object.(步驟十,設(shè)置您想要的消息到消息對象。)
*/
message.setText("This is a Test Message from PtpSender Class " ) ;
/**
Step 11 Now we are ready to send the message.(步驟十一,現(xiàn)在我們已準(zhǔn)備好發(fā)送消息。)
*/
queueSender.send(message);
System.out.println(“\n The Message has been sent”);
/**
Step 12 Close the Queue Connection Before exiting from the program.(步驟十二,在從程序退出前關(guān)閉隊列
連接)
*/
queueConnection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
275
簡單消息使用者應(yīng)用程序
我們的下一個客戶機應(yīng)用程序是消息接收器應(yīng)用程序,它獲取PtpSender 應(yīng)用程序發(fā)送
的消息并打印出控制臺上的消息。有關(guān)步驟如下:
.. 在JNDI 名稱空間中查找QueueConnectionFactory 和Queue;
.. 獲取Queue Connection 對象;
.. 創(chuàng)建QueueSession;
.. 創(chuàng)建QueueReceiver;
.. 接收消息并顯示它;
.. 關(guān)閉以及斷開與連接對象的連接。
程序PtpReceiver.java 顯示了創(chuàng)建點到點消息使用者應(yīng)用程序的有關(guān)步驟。應(yīng)用程序從
MQSeries 隊列獲取消息并顯示消息。
例8-2 PtpReceiver.java
//Step 1 Import the Necessary Packages(步驟一,導(dǎo)入所需的軟件包)
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(步驟五,利用隊列連接庫創(chuàng)建隊列連
接)
*/
queueConnection = queueConnectionFactory.createQueueConnection();
/*
*Step 6 Connections are always created in stopped mode. You have to Explicitly
start them. Start the queueConnection(步驟六,在停止模式里創(chuàng)建連接。您必須明確的啟動他們。啟動隊
列連接。)
*/
queueConnection.start();
/**
* Step 7 Create a queueSession object from the QueueConnection(步驟七,從隊列連接創(chuàng)建隊列會話對象)
*/
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
/**
* Step 8 Create a QueueReceiver from the queueSession(步驟八,從隊列會話創(chuàng)建隊列接收器)
*/
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(步驟九,接收消息。在此我們實施同步消息接收。循環(huán)進行接收調(diào)用從而
能在隊列中處理所有可用消息。)
*/
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(步驟十,關(guān)閉連接)
*/
queueConnection.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
8.5.4 請求/回復(fù)
在請求/回復(fù)消息發(fā)送中,接收器接受請求消息后將發(fā)送回復(fù)到發(fā)送器。在接收器方,
準(zhǔn)備消息與PTP 中一樣,但除此之外JMSReplyToQueue 值將設(shè)置發(fā)送器期待從接收器
回復(fù)的Queue Name。當(dāng)獲取消息時,接收器可以使用回復(fù)到隊列名來發(fā)送回復(fù)消息至
發(fā)送器。
JMS 提供JMSReplyTo 消息標(biāo)題字段,指定應(yīng)當(dāng)發(fā)送消息回復(fù)的目的地。JMSCorrelationId
標(biāo)題字段可以用在回復(fù)消息中,提供到請求消息的引用。
請求/回復(fù)消息發(fā)送的應(yīng)用程序設(shè)計考慮
MQSeries 是種異步通訊機制。在請求/回復(fù)消息發(fā)送中,客戶機將消息放入隊列并等
待回復(fù)時,在應(yīng)用程序設(shè)計層次上必須小心。您不會愿意在隊列上無限制地等待回復(fù)消
息的到達,因為您并不知道您的請求獲取回復(fù)要花多長時間。應(yīng)用程序設(shè)計應(yīng)當(dāng)考慮到
延遲回復(fù)和根本無回復(fù)的情況。您也可能希望在等待回復(fù)消息時考慮設(shè)置合適的超時
值。
如果在等待回復(fù)消息時使用超時的話,那么您可能希望在生成回復(fù)消息的遠程客戶機方
考慮采用合適的過期時間值。在許多請求/回復(fù)情況下,您可以考慮為提高效能而使用
非持久消息。使用非持久消息將會帶來顯著的效能改善。
278
非持久消息可以用三種方法發(fā)送。持久屬性可以用以下方法之一設(shè)置:
.. 在創(chuàng)建MQSeries 隊列時直接在隊列管理器中的隊列對象上設(shè)置,或者您也可以在
現(xiàn)有MQSeries Queue 上改變它。
.. 在JNDI 中利用JMSAdmin 工具在隊列JMS 管理對象上設(shè)置。參數(shù)PERSISTENCE
可用來指定您希望設(shè)置的持久值。不同的值如下:
- APP:應(yīng)用程序定義的持久值(這是默認的)
- QDEF:MQSeries 隊列定義的持久值
- PERS:持久的消息
- NON:非持久的消息
如果您希望以持久NON 定義隊列對象名,那么您可以使用JMSAdmin 來指定命令:
DEFINE Q(ptpQueue) PERSISTENCE(NON)
.. 在JMS 應(yīng)用程序中對每條消息都使用DelieveryMode.NON.PERSISTENT 字段。消
息持久性可以在QueueSender 上調(diào)整,或者也可以在發(fā)送方法調(diào)用上指定,但不能
直接在消息上指定,請參見如下的例子:
QueueSender sender = queueSession.createSender(queue) ;
sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
或者您也可以在發(fā)送消息時通過方法調(diào)用指定送達模式、優(yōu)先級和使用期限等來設(shè)
置送達模式。
8.5.5 JMS發(fā)布/預(yù)訂模型
與點到點通訊模型不同,發(fā)布/預(yù)訂模型可以讓一條消息送達多個接收器。發(fā)送客戶機
指引或發(fā)布消息至多個客戶機可以預(yù)訂的主題。一個主題可以有多個發(fā)布器和多個預(yù)訂
器。持久預(yù)訂或興趣在客戶機關(guān)閉和重啟時都存在。當(dāng)客戶機關(guān)閉時,將儲存所有已送
達主題的對象并在客戶機重新預(yù)訂時發(fā)送到客戶機。在發(fā)布/預(yù)訂系統(tǒng)中,客戶機可以
是發(fā)布器(消息生成器)、預(yù)訂器(消息使用者)或二者都是。
JMS 發(fā)布/預(yù)訂模型定義了在基于內(nèi)容的層次中JMS 客戶機如何發(fā)布消息至已知的節(jié)
點以及如何從已知的節(jié)點預(yù)訂消息。JMS 將這些節(jié)點稱作“主題”。
279
在本節(jié)中,我們使用的是發(fā)布和預(yù)訂這兩個術(shù)語,而不采用前面用到的生成和使用這兩
個一般術(shù)語。我們可以把主題看作微型消息中介,它收集并分配指向它的消息。消息發(fā)
送器依靠主題作為中間體從而獨立于預(yù)訂器,反過來也是如此。主題自動適應(yīng)發(fā)布器和
預(yù)訂器到來和離去。當(dāng)代表發(fā)布器和預(yù)訂器的Java 對象存在時,發(fā)布器和預(yù)訂器是活動
的。
JMS 也支持預(yù)訂器可選的持久性,并在其不活動時“記住”其存在。MQSeries 發(fā)布/
預(yù)訂使得應(yīng)用程序不必知道有關(guān)目標(biāo)應(yīng)用程序的任何信息。它需要做的就是把它希望分
享的信息發(fā)送到MQSeries 發(fā)布/預(yù)訂管理的標(biāo)準(zhǔn)目的地,并讓MQSeries 發(fā)布/預(yù)訂處
理分配。同樣,目標(biāo)應(yīng)用程序不必了解其接收的信息來源的任何信息。
圖8-16 顯示了用JMS 開發(fā)發(fā)布/預(yù)訂應(yīng)用程序的有關(guān)步驟。
280
圖8-16 JMS 發(fā)布/預(yù)訂編程概述
在JMS 實施發(fā)布/預(yù)訂消息發(fā)送模型中,所有特定銷售商實施都可以通過在java.jms
中的以下接口引用。所有這些均包括在以下接口的實施中:
.. QueueConnectionFactory
.. TopicConnectionFactory
.. Queue
.. Topic
在JMS 發(fā)布/預(yù)訂模型中,預(yù)訂主題時就可以實施主題異步預(yù)訂。
281
簡單JMS 發(fā)布/預(yù)訂應(yīng)用程序
在本節(jié)中,我們將用樣例程序JMSPublisher.java 來講解開發(fā)發(fā)布應(yīng)用程序的有關(guān)步驟。
編寫JMS 發(fā)布應(yīng)用程序的有關(guān)步驟如下:
1. 定義JMS 管理對象:
a. 創(chuàng)建JNDI 上下文。
我們使用以下命令來創(chuàng)建用于樣例程序中的名為psCtx 的JNDI 上下文:
DEF CTX(psCtx)
b. 利用以下命令改變到您剛剛創(chuàng)建的上下文中:
CHG CTX(psCtx)
c. 創(chuàng)建TopicConnectionFactory。
以下命令將創(chuàng)建指向QueueManager ITSOG.QMGR1(它位于名為ITSOG 的主
機上)的名為psTcf 的TopicConnectionFactory,它在默認端口1414 上進行監(jiān)
聽。用于客戶機連接的服務(wù)器連接為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 的主題創(chuàng)建名為psTopic 的
Topic。
DEF CTX(psCtx) TOPIC(JMSTest/SampleTopic)
2. 在JNDI 名稱空間查找TopicConnectionFactory 和Topic;
3. 創(chuàng)建Topic Connection;
4. 創(chuàng)建TopicSession;
5. 創(chuàng)建TopicPublisher;
6. 創(chuàng)建TextMessage;
7. 發(fā)布消息至Topic;
8. 關(guān)閉以及斷開與連接對象的連接。
282
我們的第一個JMS 發(fā)布/預(yù)訂客戶機程序JMSPublisher.java 將創(chuàng)建一條文本消息并發(fā)
送它到“SampleTopic”主題,它位于根主題JMSTest 之下。我們也將講解預(yù)訂主題
“TestTopic”的預(yù)訂器程序。
例8-3 JMSPublisher.java
//Step 1 Import the necessary packages(步驟一,導(dǎo)入所需的軟件包。)
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(步驟四,使用連接庫對象創(chuàng)建主題連
接。)
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(步驟七,從主題連接創(chuàng)建主題會話。)
283
topicSession = topicConnection.createTopicSession(false,
Session.AUTO_ACKNOWLEDGE);
//Step 8 Create a topic publisher for the topic from the session.(步驟八,從會話為主題創(chuàng)建主題發(fā)布者。)
publisher = topicSession.createPublisher(topic);
//Step 9 Create a message object(步驟九,創(chuàng)建消息對象。)
message = topicSession.createTextMessage();
//Step 10 prepare the body of the message(步驟十,準(zhǔn)備消息主體。)
message.setText("This is a Test Message from JMSPublisher Class " ) ;
//Step 11 Publish the message.(步驟十一,發(fā)布消息。)
publisher.publish(message);
//Step 12 Close the connections.(步驟十二,關(guān)閉連接。)
publisher.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
簡單的JMS 預(yù)訂器應(yīng)用程序
在本節(jié)中,我們將通過創(chuàng)建一個樣例程序來講解開發(fā)JMS 預(yù)訂器應(yīng)用程序的有關(guān)步驟。
編寫JMS 預(yù)訂器應(yīng)用程序的有關(guān)步驟如下:
1. 在JNDI 名稱空間查找TopicConnectionFactory 和Topic;
2. 創(chuàng)建Topic Connection;
3. 創(chuàng)建TopicSession;
4. 創(chuàng)建TopicSubscriber;
5. 從Topic 接收預(yù)訂;
6. 關(guān)閉并斷開與連接對象的連接。
程序JMSSubscriber.java 是從“SampleTopic”主題(它位于根主題JMSTest 下)預(yù)訂消
息的簡單預(yù)訂器應(yīng)用程序。我們在例子中使用的是非可持續(xù)性預(yù)訂。
例8-4 JMSSubscriber.java
//Step 1 Import the necessary packages.(步驟一,導(dǎo)入所需軟件包。)
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(步驟四,創(chuàng)建主題連接。)
topicConnection = topicConnectionFactory.createTopicConnection();
//Step 5 Start The topic connection(步驟五,啟動主題連接。)
topicConnection.start();
//Step 6 Create a topic session from the topic connection(步驟六,從主題連接創(chuàng)建主題會話。)
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.(步驟八,為主題創(chuàng)建主題訂戶。)
subscriber = topicSession.createSubscriber(topic);//Non durable subscriber(非持久預(yù)定者)
//Step 9 Receive Subscription(步驟九,接收預(yù)訂。)
message = (TextMessage)subscriber.receive();
System.out.println("\n *** The Message is " + message.getText());
//Step 10Close the connection and other open resources(步驟十,關(guān)閉連接和其它打開資源。)
subscriber.close();
topicSession.close();
topicConnection.close();
}
catch(Exception e ) {
e.printStackTrace();
}
}
}
285
8.6 異步處理
MQSeries JMS 提供了人們廣泛期待的MessageListener 接口。利用這個消息偵聽器功能,
客戶機就可以注冊偵聽器對象到消息使用者。當(dāng)消息到達使用者時,它通過調(diào)用
onMessage 方法送達至客戶機。
這是除利用其他方法(如觸發(fā)器、以等待發(fā)出接收或預(yù)訂調(diào)用或者在應(yīng)用程序代碼中采
用登記機制)外檢查新消息或預(yù)訂的另一種方法。當(dāng)用偵聽器使用異步送達模式時,與
消息使用者相關(guān)聯(lián)的整個會話都標(biāo)志為異步的。相同的會話不能用來發(fā)出任何顯式接收
調(diào)用。
因為應(yīng)用程序不發(fā)出顯式接收調(diào)用,因此在異步消息送達中,應(yīng)用程序代碼可能不能獲
取接受消息失敗所產(chǎn)生的例外。MQSeries JMS 提供了利用ExceptionListener 接口獲取異
常的功能。當(dāng)出現(xiàn)異常時,將調(diào)用onException 方法,而JMSException 將作為其參數(shù)傳
遞到方法。
8.6.1 消息偵聽器
我們通過實施MessageListener 接口并在偵聽器的onMessage 方法中提供應(yīng)用程序特定處
理來創(chuàng)建消息偵聽器。我們將討論如何在消息使用者應(yīng)用程序中實施簡單的消息偵聽
器。
我們首先創(chuàng)建偵聽器類(PTPListener.java),其擴展了JMSMessageListener 類并對
onMessage 方法進行了實施。當(dāng)消息到達時,消息作為自變量被送達至onMessage 方法。
當(dāng)消息到達時,我們就顯示消息。
例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 中使用偵聽器來實施異步處理
消息。您可以與我們在簡單發(fā)送器應(yīng)用程序中介紹的PtpSender 應(yīng)用程序一起來運行
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 異常偵聽器
當(dāng)以消息偵聽器利用異步消息送達模型時,應(yīng)用程序代碼不能接到接收消息失敗產(chǎn)生的
異常。這是因為應(yīng)用程序代碼沒有顯式調(diào)用receive()或subscribe()方法。為了在異
步處理中接到錯誤,您可以注冊ExceptionListener,它是實施onException()方法類的
實例。異常偵聽器使得客戶機可以異步接收錯誤通知。如果注冊了異常偵聽器的話,那
么當(dāng)出現(xiàn)錯誤時,就會調(diào)用onMessage()方法,JMSException 對象則作為自變量被傳
遞到該方法。異常偵聽器是在連接上設(shè)置的。
288
我們將用個非常簡單的應(yīng)用程序來講解如何實施異常偵聽器。
例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。我們將打印異常情況并且從程序的執(zhí)行中退出。根據(jù)您的使用情況添加適當(dāng)?shù)腻e誤
處理和恢復(fù)邏輯。)
*/
public void onException( JMSException e) {
e.printStackTrace();
System.exit(1);
}
}
我們現(xiàn)在已經(jīng)擁有了異常偵聽器,接下來就可以用JMSExceptionListener 實例、如下以
setExceptionListener 方法注冊異常偵聽器到連接對象,從而在異步使用者中利用偵聽器:
JMSExceptionListener exListener = new JMSExceptionListener( );
queueConnection.setExceptionListener(exListener);
setExceptionListner 方法為連接對象queueConnection 設(shè)置了異常偵聽器。
289
8.7 消息選擇器
JMS 提供在隊列上查詢消息的功能,因此我們可以根據(jù)給定的標(biāo)準(zhǔn)選擇消息子集。我們
可以把這看作是數(shù)據(jù)庫中的SQL 查詢功能。事實上,這種查找的語法就是以SQL92 條
件表達的標(biāo)準(zhǔn)為基礎(chǔ)的。消息選擇器可以指向JMS 消息標(biāo)題中的字段,也可以指向消
息屬性中的字段,即應(yīng)用程序定義的字段。
計算消息選擇器的順序是在優(yōu)先等級內(nèi)從左至右。您可以利用插句來改變計算順序。選
擇器字面值和操作器名是區(qū)分大小寫的。
選擇器可能包括:
.. 字面值:
- 字符串字面值包括在單一引用中,一個被包括的單一引用由復(fù)式單一引用代
表,如“literal”和“literal’s”。與Java String 字面值一樣,他們必須使用unicode
字符編碼;
- 精確數(shù)字字面值是沒有小數(shù)點的數(shù)字值,如57、-957、+62 等。支持Java 長
型范圍內(nèi)的數(shù)字。精確數(shù)字字面值使用Java 整數(shù)字面值語法;
- 近似數(shù)字字面值是科學(xué)記數(shù)法表示的數(shù)字值(如7E3, -57.9E2)或帶有小數(shù)
點的數(shù)字值(如7.、-95.7、+6.2 等)。支持Java 雙精度型范圍內(nèi)的數(shù)字。近
似字面值使用Java 浮點字面值語法;
- Boolean 字面值真和假。
.. 標(biāo)識符
- 標(biāo)識符是必須以Java 標(biāo)識符起始字符開始的無限長度字符序列,所有后續(xù)字符
必須為Java 標(biāo)識符部件字符。標(biāo)識符起始字符是Character.isJavaIdentifierStart
方法返回為真的任意字符。這包括‘_’ 和‘$’ 。標(biāo)識符部件字符是
Character.isJavaIdentifierPart 方法返回為真的任意字符。
- 標(biāo)識符不能是NULL, TRUE 或FALSE 名;
- 標(biāo)識符不能為NOT, AND, OR, BETWEEN, LIKE, IN 和IS;
- 標(biāo)識符可以是標(biāo)題子段引用,也可以是屬性引用;
- 標(biāo)識符是區(qū)分大小寫的;
- 消息標(biāo)題子段引用限于JMSDelieveryMode, JMSPriority, JMSMessageID,
JMSTimestamp, JMSCorrelationID 和JMSType 。JMSMessageID,
290
JMSCorrelationID 和JMSType 值可能為空,如果如此的話,那么它們將被作為
NULL 值對待;
- 任何以“JMSX”開始的名都是JMS 定義的屬性名;
- 任何以“JMS_”開始的名都是供應(yīng)方特定屬性名;
- 任何不以“JMS”開始的名都是應(yīng)用程序特定屬性名。如果引用消息中不存在
的屬性,它的值則為NULL。如果它確實存在的話,那么它的值就是相應(yīng)的屬
性值。
.. 空白空間與Java 中的定義一樣:空間、水平標(biāo)簽、表單反饋和行終止符。
.. 表達:
- 選擇器是條件表達。計算為真的選擇器匹配。計算為假或未知的選擇器不匹配;
- 算術(shù)表達由其自身、算術(shù)操作、帶數(shù)字值的標(biāo)識符和數(shù)字字面值構(gòu)成;
- 條件表達由其自身、比較操作、邏輯操作、由boolean 值的標(biāo)識符和boolean
字面值構(gòu)成;
- 為表達計算排序的標(biāo)準(zhǔn)括號()被支持。
.. 邏輯操作器的優(yōu)先順序為:NOT, AND, OR
.. 比較操作器:=, >, >=, <, <=, <> (不等于)
- 只有相同類型的值才可以比較。這里有一個例外,就是我們可以比較精確數(shù)字
值和近似數(shù)字值(要求的類型轉(zhuǎn)換由Java 數(shù)字升級定義)。如果嘗試比較不同
類型的值的話,選擇器總為假;
- 字符串和Boolean 比較限于=和<>。只有在兩個字符串包含相同的字符順序時,
它們才是相等的;
.. 算術(shù)操作器的優(yōu)先順序如下:
- +,- 一元的
- *,/ 乘和除
- +,- 加和減
- 算術(shù)操作必須使用Java 數(shù)字提升。
.. Arithmetic-expr1 [NOT] BETWEEN arithmetic-expr2 和arithmetic-expr3 比較操
作器
- 年齡介于15 到19 歲等于age >= 15 AND age <= 19;
- 年齡不介于15 至19 之間等于age < 15 OR age > 19。
291
.. 標(biāo)識符[NOT] IN (string-literal1, string-literal2,...)比較操作器,且標(biāo)識符有一
個字符串或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 操作的標(biāo)識符為NULL,那么操作的值為未知;
- 標(biāo)識符[NOT] LIKE pattern-value [ESCAPE escape-character]比較操作器,這
里標(biāo)識符有字符串值。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 操作標(biāo)識符為NULL,那么操作的值為未知。
.. 標(biāo)識符IS NULL 比較操作器檢測空標(biāo)題字段值或丟失屬性值。
- prop_name IS NULL
.. 標(biāo)識符IS NOT NULL 比較操作器檢測非空標(biāo)題字段值或?qū)傩灾档拇嬖凇?br /> - prop_name IS NOT NULL
我們要求JMS 供應(yīng)方在消息選擇器出現(xiàn)時校驗消息選擇器的語法正確性。提供語法不
正確選擇器的方法必須以JMS InvalidSelectionException 為結(jié)果。
以下消息選擇器選擇類型為汽車、顏色為藍、重量大于2500 磅的消息:
“JMSType = ‘car’ AND color = ‘blue’ AND weight > 2500”
292
空值:標(biāo)題字段和屬性值可能為NULL。計算選擇器表達的規(guī)則是,如果值為NULL,
那么SQL 就把NULL 值作為未知。因此,任何含有NULL 值的算術(shù)操作比較,其結(jié)果
都是未知值。
8.7.1 用消息選擇器庫
消息選擇器可以在消息上設(shè)置為用戶定義的屬性。在發(fā)送或發(fā)布方,我們可以通過利用
名值對的設(shè)置屬性方法來設(shè)置屬性名和它的值。
為了設(shè)置名為“testProperty”、值為100 以及數(shù)據(jù)類型為整數(shù)的屬性,我們可以在對象
消息上如下設(shè)置屬性:
messaget.setIntProperty(“testProperty”, 100) ;
設(shè)置屬性方法用到兩個自變量。第一個自變量是類型字符串,也就是屬性值的名。第二
個自變量是屬性值。我們應(yīng)當(dāng)根據(jù)希望設(shè)置值的數(shù)據(jù)類型選擇利用合適的設(shè)置屬性方法
(setIntProperty 用于整數(shù)值,setStringProperty 用于字符串類型值等)。
在消息使用者方,我們應(yīng)當(dāng)使用具有合適選擇標(biāo)準(zhǔn)的消息選擇器字符串。選擇標(biāo)準(zhǔn)是在
創(chuàng)建消息使用者的時候指定的。
我們使用的選擇標(biāo)準(zhǔn)是選擇屬性名為“testProperty”和值為100 的消息發(fā)送。我們可以
用如下方法達到此目的:
String selector = “testProperty = 100 “ ;
queueReceiver = session.createReceiver(queueName, selector).
用選擇器創(chuàng)建的消息使用者獲取屬性名為“testProperty”且值為100 的消息。
一旦創(chuàng)建與消息使用者相關(guān)聯(lián)的選擇器,JMS 規(guī)范就不允許它改變。如果您需要不同標(biāo)
準(zhǔn)的消息接收器的話,那么您可能需要創(chuàng)建另外的消息接收器。
一旦用消息選擇器創(chuàng)建了消息接收器,您就可以利用消息使用者上的getMessageSelector
()方法來檢查選擇器值。
在我們前面講解所用的queueReceiver 中,getMessageSelector 方法返回帶有選擇器值
queReceiver.getMessageSelector()的字符串。這將返回testProperty=100。