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