Java消息發(fā)送服務(wù)(Java Messaging Service,JMS)是提供商無(wú)關(guān)的一套API,用于在程序間進(jìn)行可靠的消息發(fā)送。在客戶端-服務(wù)器計(jì)算中,客戶端程序與服務(wù)器與服務(wù)器建立聯(lián)系并請(qǐng)求服務(wù)。相反,消息發(fā)送應(yīng)用在相互協(xié)作的程序之間發(fā)送消息。有些程序(在所謂的“對(duì)等(peer-to-peer)”應(yīng)用中)則相互之間直接交換信息(JXTA使用的就是這種模型)。
這兩種類型的連網(wǎng)方式如下圖所示:
|
JMS提供了一個(gè)中間件消息代理(message broker),后者提供了程序間可靠的、事務(wù)性的消息發(fā)送。
下面的圖演示了點(diǎn)到點(diǎn)的消息發(fā)送,這正是本期中要講到的一種消息發(fā)送方式。
|
雖然這些圖中顯示的消息提供者和消費(fèi)者都是物理上的機(jī)器,實(shí)際上它們也可以是運(yùn)行在一臺(tái)或者多臺(tái)機(jī)器上的相互協(xié)作的一些進(jìn)程。
JMS提供者是一個(gè)程序,它實(shí)現(xiàn)了JMS公共接口中的JMS服務(wù)約定。J2EE平臺(tái)規(guī)范要求平臺(tái)實(shí)現(xiàn)包含一個(gè)JMS提供者。
大多數(shù)人都比較熟悉客戶端-服務(wù)器模型的消息發(fā)送,在這種模型中,客戶端程序與服務(wù)器建立聯(lián)系,請(qǐng)求服務(wù)、數(shù)據(jù),或者同時(shí)請(qǐng)求服務(wù)和數(shù)據(jù)。相反,JMS提供了一種更豐富的消息發(fā)送模型,這種模型具有以下高級(jí)特性:
- 可靠的消息發(fā)送。當(dāng)消息發(fā)送者正在發(fā)送消息時(shí),消息接受者無(wú)需處于運(yùn)行狀態(tài)。而是等等接受者下次做好準(zhǔn)備時(shí),再將消息發(fā)送到接受者手上。
- 點(diǎn)到點(diǎn)或者發(fā)布/訂閱式的消息發(fā)送模型。消息的傳送可能是一對(duì)一的,也可能是一對(duì)多的。
- 事務(wù)。消息發(fā)送可作為一個(gè)分布式事務(wù)的一部分。
- 同步的或異步的消息發(fā)送。消息生產(chǎn)者可能會(huì)等待接受者的確認(rèn),也可能不會(huì)。
- 面向?qū)ο蟮南l(fā)送。JMS允許在客戶端之間發(fā)送對(duì)象,而不是通過(guò)使用一些協(xié)議來(lái)發(fā)送結(jié)構(gòu)化的數(shù)據(jù)。
- 遺留整合。JMS可以與底層的第三方消息發(fā)送系統(tǒng)進(jìn)行整合。
本期解釋了如何使用JMS消息隊(duì)列來(lái)實(shí)現(xiàn)兩個(gè)進(jìn)程之間簡(jiǎn)單、可靠的點(diǎn)到點(diǎn)的消息發(fā)送。發(fā)布/訂閱式的消息發(fā)送將在后面的期“Enterprise Java Technologies Tech Tips”中講述。
JMS隊(duì)列術(shù)語(yǔ)
JMS使用消息隊(duì)列的概念來(lái)實(shí)現(xiàn)點(diǎn)到點(diǎn)的消息發(fā)送。在點(diǎn)到點(diǎn)的消息發(fā)送中,總是有一個(gè)明確的消息生產(chǎn)者和一個(gè)消息消費(fèi)者。在點(diǎn)到點(diǎn)的消息發(fā)送中,與時(shí)間沒(méi)有多大的關(guān)系,除非消息發(fā)送者為消息定義了一個(gè)期限。消息接受這可以接收由消息生產(chǎn)者在過(guò)去任何一個(gè)時(shí)候發(fā)送的消息,即使在該消息被編入隊(duì)列時(shí)消息消費(fèi)者沒(méi)有處于運(yùn)行狀態(tài)。
下面的圖顯示了一個(gè)點(diǎn)到點(diǎn)的消息發(fā)送場(chǎng)景。
|
JMS提供者是一個(gè)消息發(fā)送服務(wù)器,它負(fù)責(zé)處理消息的持久性、超時(shí)、重發(fā)、事務(wù)回滾以及由JMS提供的其他服務(wù)。對(duì)于J2EE SDK這種情況,JMS提供者是J2EE服務(wù)器程序的一部分。消息生產(chǎn)者發(fā)送對(duì)象到由JMS維護(hù)的一個(gè)隊(duì)列中。消息消費(fèi)者則從該隊(duì)列接收消息,并發(fā)出確認(rèn),表示已經(jīng)收到消息。
JMS規(guī)范定義了一些可用于在進(jìn)程間發(fā)送消息的對(duì)象:
- JMS管理的對(duì)象。有兩種JMS管理的對(duì)象:目的地和連接工廠。這兩種對(duì)象都是由系統(tǒng)管理員通過(guò)環(huán)境管理工具創(chuàng)建的。
- 目的地。這是一種服務(wù)器端的對(duì)象,通過(guò)這個(gè)對(duì)象進(jìn)行消息的發(fā)送和接收。JMS隊(duì)列就是一種目的地對(duì)象。
- 連接工廠。這是一種服務(wù)器端的對(duì)象,負(fù)責(zé)配置和創(chuàng)建到一個(gè)特定目的地的連接。JMS 隊(duì)列的JMS連接工廠就是一種QueueConnectionFactory。
- 連接。這是一種到一個(gè)JMS提供者(而不是到一個(gè)目的地)的虛擬連接。它被用來(lái)創(chuàng)建會(huì)話。用于訪問(wèn)隊(duì)列的一個(gè)連接就是一種QueueConnection。
- 會(huì)話。這是一個(gè)潛在的消息傳送和接收的事務(wù)性的工作單元。會(huì)話用于創(chuàng)建消息、消息生產(chǎn)者和消息消費(fèi)者。用于訪問(wèn)隊(duì)列的一個(gè)會(huì)話就是一種QueueSession。
- 消息。這是一種可以從消息目的地發(fā)送到消息消費(fèi)者的對(duì)象。消息的類型隨要發(fā)送的對(duì)象類型的不同而不同。例如,為本期提供的示例代碼就使用了TextMessage對(duì)象。
- 消息生產(chǎn)者。這是一種由JMS客戶端程序使用的、用于發(fā)送消息到目的地的對(duì)象。JMS客戶端程序從一個(gè)會(huì)話中獲取消息生產(chǎn)者對(duì)象。程序可以使用QueueSender這種類型的消息生產(chǎn)者對(duì)象來(lái)發(fā)送消息到一個(gè)隊(duì)列中。
- 消息消費(fèi)者。這是一種由JMS客戶端程序使用的、用于從一個(gè)目的地接收消息的對(duì)象。JMS客戶端程序從一個(gè)會(huì)話中獲取消息接受者對(duì)象。程序可以使用QueueReceiver這種類型的消息消費(fèi)者對(duì)象來(lái)從一個(gè)隊(duì)列中接收消息。
這里頗有幾個(gè)新的術(shù)語(yǔ)。現(xiàn)在讓我們看看如何發(fā)送消息。以下步驟展示了從與本期一起提供的示例代碼中抽出的一些例子。(想知道如何下載和運(yùn)行該示例代碼,請(qǐng)參考運(yùn)行示例代碼一節(jié)。)不過(guò),在運(yùn)行示例代碼之前,你需要配置一下服務(wù)器(參見(jiàn) 配置服務(wù)器一節(jié))。
發(fā)送消息
J2EE參考實(shí)現(xiàn)預(yù)先配有一個(gè)隊(duì)列連接工廠(名為QueueConnectionFactory)和一個(gè)隊(duì)列(名為jms/Queue)。如果你使用的是JMS服務(wù)器,而不是參考實(shí)現(xiàn),或者如果你想試著更改隊(duì)列連接工廠和/或隊(duì)列隊(duì)列的名稱,請(qǐng)參考配置服務(wù)器一節(jié)。
下面是通過(guò)一個(gè)JMS隊(duì)列發(fā)送消息的步驟。從示例程序TestQueue中抽出的代碼片段也穿插在這些步驟中。
- 通過(guò)按名字在JNDI中進(jìn)行查找,獲得一個(gè)指向QueueConnectionFactory 的引用:
protected static String qfactoryName =3.
"jms/queue/TechTipsQueueConnectionFactory";4.
...
try {
// 獲得JNDI上下文
InitialContext ctx = new InitialContext();
// 獲得連接工廠
QueueConnectionFactory qcf =
(QueueConnectionFactory)ctx.lookup(qfactoryName);
2.從 QueueConnectionFactory獲得一個(gè)QueueConnection,再?gòu)倪@個(gè)連接獲得一個(gè)QueueSession 。按名字在JNDI中查找:
// 獲得一個(gè)到隊(duì)列的連接
qc = qcf.createQueueConnection();
//從該連接獲得一個(gè)會(huì)話
QueueSession qs = qc.createQueueSession(
false, Session.AUTO_ACKNOWLEDGE);
// 獲得一個(gè)隊(duì)列
Queue q = (Queue)ctx.lookup(queueName);
3. 使用QueueSession創(chuàng)建一個(gè)QueueSender,將該隊(duì)列作為一個(gè)參數(shù)來(lái)傳遞。(QueueSender是QueueSession與某個(gè)特定隊(duì)列之間的一個(gè)關(guān)聯(lián)。):
// 使用這個(gè)會(huì)話創(chuàng)建一個(gè)QueueSender
// and a TextMessage.
QueueSender qsnd = qs.createSender(q);
現(xiàn)在可以用QueueSender來(lái)發(fā)送消息到隊(duì)列。
4. 創(chuàng)建一個(gè)消息對(duì)象(Message的子類),然后使用QueueSender的發(fā)送方法將它們發(fā)送至目的地。示例程序從Session中獲得一個(gè)TextMessage對(duì)象。接著示例程序?qū)⒚總€(gè)程序參數(shù)打包到TextMessage 中,然后使用QueueSender將其發(fā)送至隊(duì)列。注意,同一個(gè)TextMessage 可以使用多次。
TextMessage tm = qs.createTextMessage();
// 為第一個(gè)參數(shù)之后的每個(gè)參數(shù)進(jìn)行一次循環(huán)
// 以文本消息的形式發(fā)送參數(shù)字符串
for (int i = 2; i < args.length; i++) {
tm.setText(args[i]);
qsnd.send;
}
5.關(guān)閉QueueConnection。在try/finally程序塊的最后一條語(yǔ)句中關(guān)閉連接是一個(gè)好習(xí)慣。這一步很重要:忘記關(guān)閉 QueueConnections 將可能導(dǎo)致服務(wù)器上的資源泄漏:
} finally {
if (qc != null) {
qc.close();
}
}
要發(fā)送消息到一個(gè)消息隊(duì)列,可以使用TestQueue程序(在缺省的包中),加上一個(gè)參數(shù)“send”,例如:
$ java TestQueue send jms/queue/MyTestQueue a b c d
Java Message Service 1.0.2 Reference
Implementation (build b14)
Sent: 'a'
Sent: 'b'
Sent: 'c'
Sent: 'd'
接收消息
TestQueue程序按照以下步驟接收消息:
- 和2從一個(gè)消息隊(duì)列接收消息的起先兩步與發(fā)送消息是一樣的:先是查找連接工廠,再獲得一個(gè)QueueConnection,然后查找Queue。
- 從QueueSession 獲得一個(gè)QueueReceiver :
QueueReceiver qrcv = qs.createReceiver(q);
3. 從Queue接收消息。示例程序進(jìn)入一個(gè)循環(huán),在這個(gè)循環(huán)中從隊(duì)列獲取消息并將它們打印到標(biāo)準(zhǔn)輸出設(shè)備。
qc.start();
Message m = qrcv.receive(10000);
while (m != null) {
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage)m;
System.out.println("Received text: '" +
tm.getText() + "'");
} else {
System.out.println("Received a " +
m.getClass().getName());
}
m = qrcv.receive(100);
} finally {
if (qc != null) {
qc.close();
}
}
對(duì)QueueConnection的啟動(dòng)方法的調(diào)用將告訴連接開(kāi)始接收消息。QueueReceiver接收方法帶有一個(gè)參數(shù),該參數(shù)表明了等待一條消息的毫秒數(shù)。如果消息沒(méi)有在規(guī)定時(shí)間內(nèi)到達(dá),該方法將返回null值。直到消息隊(duì)列在100毫秒的時(shí)間內(nèi)都保持為空,程序才開(kāi)始讀取和打印收到的消息。在try/finally程序塊的結(jié)尾有一個(gè)finally子句,該子句像前面例子中一樣地關(guān)閉QueueConnection。
要接收消息隊(duì)列中的消息,可以使用TestQueue程序,再帶上一個(gè)參數(shù)“recy”,例如:
$ java TestQueue recv jms/queue/MyTestQueue
Java Message Service 1.0.2 Reference
Implementation (build b14)
Received text: 'a'
Received text: 'b'
Received text: 'c'
Received text: 'd'
配置服務(wù)器
如果你使用的不是參考引用,或者你想更改隊(duì)列和/或隊(duì)列連接工廠的名字,你就需要通過(guò)使用平臺(tái)的管理工具配置JMS提供者。隊(duì)列或者連接工廠被創(chuàng)建之后,便留在服務(wù)器中,直到服務(wù)器重新啟動(dòng)。有些平臺(tái)實(shí)現(xiàn)可能會(huì)為客戶端提供擴(kuò)展API,以便程序化地創(chuàng)建目的地和連接工廠。
用來(lái)在J2EE SDK下創(chuàng)建受管理的對(duì)象的工具是。要配置服務(wù)器,需:
- 啟動(dòng)應(yīng)用服務(wù)器。
- Create a message queue, giving it a JNDI name. (Do this only once.) To create a new message queue in the J2EE SDK, first decide on a name for your message queue. It can be convenient for administration purposes to choose a name that indicates that the queue is used by JMS, for example, jms/MyTestQueue. Then execute the command:創(chuàng)建一個(gè)消息隊(duì)列,并為它取一個(gè)JNDI名。(只做一次。)要用J2EE SDK創(chuàng)建一個(gè)新的消息隊(duì)列,首先需要為消息隊(duì)列決定一個(gè)名字。為了便于管理, 應(yīng)該選擇一個(gè)可以表明該隊(duì)列是JMS使用的隊(duì)列,例如jms/MyTestQueue。然后執(zhí)行命令:
j2eeadmin -addJmsDestination <queuename> queue
用消息隊(duì)列的實(shí)際名字替換<queuename> 。
- 由于連接工廠名QueueConnectionFactory被硬性地放在示例程序中,因此要告訴程序使用一個(gè)不同的連接工廠名,就必須修改源代碼,并且重新編譯。例如,在TestQueue.java中:
// Change this variable's value to change the
// connection factory name
protected static String qfactoryName =
"QueueConnectionFactory"
重新編譯之后,使用j2eeadmin(或者你自己的服務(wù)器的工具)創(chuàng)建一個(gè)connection factory,給它取個(gè)JNDI名,如:
j2eeadmin -addJmsFactory jms/MyQueueConnectionFactory
要列出連接工廠,使用:
j2eeadmin -listJmsFactory
要列出目的地,使用:
j2eeadmin -listJmsDestination
對(duì)于不是J2EE SDK的平臺(tái),可以在該平臺(tái)上使用J2EE產(chǎn)品的部署工具來(lái)創(chuàng)建所需的消息隊(duì)列和連接工廠。
運(yùn)行示例代碼
下載這些期的示例存檔。這個(gè)JAR文件包含了示例程序的完整源代碼,包括Java程序文件和HTML格式的文件。你可以使用命令jar xvf ttmar2003.jar 將示例jar中的內(nèi)容解壓縮到你的工作目錄下。在運(yùn)行示例程序前,要確保J2EE服務(wù)器正在運(yùn)行。
多次運(yùn)行示例程序。試著發(fā)送一些成批的消息,但是不接收這些消息,檢查一下輸出的順序。
這個(gè)示例程序?yàn)槟阍囍褂肑MS隊(duì)列提供了切入點(diǎn)。探索一下JMS接口使用的API。例如,可以更改一下程序,試著使用三種不同的接收方式。探索一下事務(wù)性的消息發(fā)送,或者試著發(fā)送各種類型的消息對(duì)象。JMS有許多特性,熟練使用它可以提高你的應(yīng)用設(shè)計(jì)水平。
要得到更多關(guān)于JMS的信息,請(qǐng)參考Java Message Service Tutorial。