JMS定義了Java中訪問消息中間件的接口,是企業(yè)資源異步訪問的主要形式。JMS可以和EJB技術(shù)集成使用,也就是常說的消息驅(qū)動BEAN。
因為訪問JMS和訪問Entity EJB和SessionEJB的調(diào)用方法有很大的差異,所以Beehive中提供了專門的JMS控件來完成JMS資源的訪問。
創(chuàng)建JMS控件的典型步驟
Beehive中提供的JMS控件無法直接用于訪問JMS資源,使用者必須繼承JMS控件來創(chuàng)建自己的訪問控件,設(shè)置相關(guān)的環(huán)境后才能完成JMS資源的訪問。一個繼承控件中只能對應(yīng)的訪問唯一一個消息隊列/主題,如果需要向多個消息隊列/主題發(fā)送消息,必須針對每個消息隊列/主題提供相應(yīng)的控件。
你可以使用下面的步驟來創(chuàng)建自己的控件實現(xiàn)對JMS資源的訪問:
創(chuàng)建新的Java接口,繼承JMSControl接口
用@ControlExtension關(guān)鍵字注釋新創(chuàng)建的Java接口,通知解析器這個接口繼承了另外的某個控件。
使用@JMSControl.Destination注釋新創(chuàng)建的Java接口,說明控件如何搜索消息隊列/主題,同時使用sendType聲明發(fā)送目標(biāo)是消息隊列還是消息主題。
創(chuàng)建新的業(yè)務(wù)方法,使用JMSControl.Message注釋來聲明被發(fā)送消息的類型,或者使用不同的參數(shù)來區(qū)別不同的消息類型。
使用控件訪問消息隊列的例子
在本節(jié)中,我們將通過簡單的例子演示如何使用JMS控件來訪問監(jiān)聽消息隊列的消息驅(qū)動Bean。我們假設(shè)存在一個消息驅(qū)動Bean,他接受用戶傳遞的文本消息(TextMessage),隨后將該文本消息打印在應(yīng)用服務(wù)器的控制臺上。該消息驅(qū)動Bean監(jiān)聽消息隊列(jndiname=”queue/helloworld”)。
Beehive并不僅僅限于WebLogic平臺使用,我們這里以JBoss為例。我們使用Eclipse+WTP(Web Tools Platform)工具開發(fā)符合要求的消息驅(qū)動Bean以及JMS隊列,發(fā)布在JBoss上。
本文中所有例子的源代碼可以在通過資源下載區(qū)中的連接完成下載。
開發(fā)消息驅(qū)動Bean
現(xiàn)在我們來開發(fā)提供上述功能的消息驅(qū)動Bean,使用WTP的向?qū)瓿上Ⅱ?qū)動Bean的創(chuàng)建工作,剩下的工作就是根據(jù)業(yè)務(wù)的需求完成onMessage方法的編寫了。
根據(jù)演示實例的要求,我們修改onMessage方法,讓它處理TextMessage類型的消息,并且將消息的內(nèi)容打印在控制臺上,清單1中列出了消息驅(qū)動Bean類的源代碼。如何開發(fā)、編譯和部署請參考JBoss的幫助文檔,這里不提供詳細的說明。
接下來的JMS、EJB開發(fā)、部署和控件訪問部分內(nèi)容針對JBoss應(yīng)用服務(wù)器。實際情況下,你可能根據(jù)不同情況選擇其他的J2EE容器比如Websphere 、WebLogic、Geronimo、JOnAS等作為EJB容器,請根據(jù)下面的提示信息進行適當(dāng)?shù)恼{(diào)整。
清單1 ejbsrc\org\vivianj\beehive\controls\examples\ejb\MessageBeanOnQueue.java
1. package org.vivianj.beehive.controls.examples.ejb;
2.
3. import javax.ejb.*;
4. import javax.jms.*;
5.
6. public class MessageBeanOnQueue
7. implements MessageDrivenBean, MessageListener
8. {
9. public void onMessage(Message msg) {
10. TextMessage tm = null;
11. if (msg instanceof TextMessage){
12. tm = (TextMessage) msg;
13. try{
14. System.out.println(tm.getText());
15. }catch(Exception e){
16. e.printStackTrace();
17. }
18. }
19. }
20. }
在部署消息驅(qū)動Bean之前,我們還應(yīng)該在JBoss服務(wù)器上配置一個JNDI名稱為“queue/helloworld”的消息隊列。請大家參考JBoss應(yīng)用服務(wù)器的幫助文檔完成這部分工作。
JMS控件開發(fā)
現(xiàn)在我們來使用JMS控件編寫代碼訪問消息驅(qū)動Bean。由于消息驅(qū)動Bean沒有本地或者遠程訪問方法,只能通過向JMS目標(biāo)發(fā)送消息來完成消息驅(qū)動Bean資源的調(diào)用。
清單2中的代碼是訪問一個JMS目標(biāo)對列的簡單例子,它提供sendTextMessage方法,接收String類型的參數(shù)msn,當(dāng)sendTextMessage方法被調(diào)用時,控件負責(zé)連接到JMS對列(“queue/helloworld”),將接收到的參數(shù)msg發(fā)送到目標(biāo)服務(wù)器上。
清單2 src\org\vivianj\beehive\controls\examples\controls\
HelloWorldQueueJMSControl.java
1. package org.vivianj.beehive.controls.examples.controls;
2.
3. import org.apache.beehive.controls.api.bean.ControlExtension;
4. import org.apache.beehive.controls.system.jms.JMSControl;
5.
6. /**
7. * HelloWorldQueueJMSControl 是訪問JMS資源的控件
8. * 可向JMS對列發(fā)送字符串類型的消息
9. */
10.
11. @ControlExtension
12. @JMSControl.Destination(sendJndiName = “queue/helloworld”,
13. jndiConnectionFactory =
14. “org.jnp.interfaces.NamingContextFactory”,
15. jndiProviderURL=“jnp://localhost:1099”,
16. sendType=DestinationType.Queue)
17. public interface HelloWorldQueueJMSControl
18. extends JMSControl {
19. @JMSControl.Message(MessageType.Text)
20. public void sendTextMessage(String msg);
21. }
JMS控件調(diào)用
現(xiàn)在我們可以使用如下方法來完成上面所創(chuàng)建控件的調(diào)用。
使用聲明式控件實例化定義成員變量_jmsControl.
@Control
HelloWorldQueueJMSControl _jmsControl;
調(diào)用控件的業(yè)務(wù)方法完成向JMS對列發(fā)送消息的功能。由于消息服務(wù)本身是沒有返回內(nèi)容的,所以我們只需要完成業(yè)務(wù)方法的調(diào)用即可。
下面的調(diào)用代碼可以實現(xiàn)向服務(wù)器發(fā)送”Hello World!”字符串的功能。
_jmsControl.sendTextMessage(“Hello World!”);
測試控件
一切準(zhǔn)備就緒后,啟動JBoss服務(wù)器,參考《控件入門》中“使用JUnit測試控件”部分的內(nèi)容,編寫單元測試TestCase測試新創(chuàng)建的JMS控件。
實例分析
從上面的例子中我們可以看到,訪問JMS資源的時候,開發(fā)者的工作被大大的簡化了。開發(fā)者只需要開發(fā)一個繼承自JMS控件的控件,使用JMS控件中規(guī)定的注釋提供訪問JMS資源所需要的一些環(huán)境參數(shù),隨后便可以使用聲明式控件實例化方式完成控件的實例化,通過調(diào)用該控件實例的相關(guān)業(yè)務(wù)方法完成JMS資源的訪問。
現(xiàn)在我們來分析一下上面創(chuàng)建的EJB控件-- HelloWorldQueueJMSControl (參見清單2)中的主要代碼。
11. @ControlExtension
在控件例子的11行,我們通過@ControllerExcention關(guān)鍵詞來說明接下來聲明的這個接口是另外一個控件的擴展。
12. @JMSControl.Destination(sendJndiName = “queue/helloworld”,
13. jndiConnectionFactory =
14. “org.jnp.interfaces.NamingContextFactory”,
15. jndiProviderURL=“iiop://localhost:7001”,
16. sendType=DestinationType.Queue))
在第12~16行代碼中我們使用JMSControl.Destination關(guān)鍵詞和它的sendJndiName、jndiConnectionFactory、jndiProviderURL和sendType屬性來設(shè)置我們要訪問消息隊列/主題在目標(biāo)服務(wù)器中發(fā)布時使用的jndiName和訪問目標(biāo)服務(wù)器需要提供的相關(guān)環(huán)境變量。
17. public interface HelloWorldQueueJMSControl
18. extends JMSControl {
在第17~18行中,我們聲明該控件繼承了JMSControl接口。
19. @JMSControl.Message(MessageType.Text)
在第19行,我們使用@JMSControl.Message關(guān)鍵字來聲明接下來的這個業(yè)務(wù)方法發(fā)送消息的類型是TextMessage。
20. public void sendTextMessage(String msg);
第20行代碼我們聲明了一個方法,這個方法需要向目標(biāo)消息隊列/主題發(fā)送參數(shù)msg提供的字符串。
完成這些工作,我們就可以調(diào)用該控件聲明的方法實現(xiàn)向目標(biāo)消息隊列/主題發(fā)送JMS消息了,如何與目標(biāo)應(yīng)用服務(wù)器交互的工作由控件來輔助完成。
使用注釋定制JMS控件
在上面的內(nèi)容中,我們已經(jīng)新建了一個JMS控件,它提供sendTextMessage方法,調(diào)用該方法能夠向消息對列”queue/helloworld”發(fā)送消息。在JMS控件代碼中,我們使用了@JMSControl.Destination我們使用了@JMSControl.Destination和@JMSControl.Message兩個注釋來提供訪問JMS資源的參數(shù)。
JMS控件支持10個注釋,他們分別是@JMSControl.CorrelationId、@JMSControl.Delivery 、@JMSControl.Destination 、@JMSControl.Expiration 、@JMSControl.Message 、@JMSControl.Priority 、@JMSControl.Properties 、@JMSControl.Property 、@JMSControl.PropertyValue 、@JMSControl.Type
下面我們詳細的介紹其中最常用的@JMSControl.Destination和@JMSControl.Message兩個注釋的屬性和用法,了解如何用這注釋定制JMS控件的更多細節(jié)。
Destination注釋
Destination注釋是類級別的注釋,用于定制JMS控件發(fā)送消息的目標(biāo)消息隊列/主題的jndiName和它們所在目標(biāo)容器的相關(guān)參數(shù)。Destination注釋提供了八個參數(shù)用于用戶定制,下面將介紹常用的四個:sendJndiName、jndiConnectionFactory、 sendType、jndiProviderURL,其中sendJndiName和jndiConnectionFactory是必須的,其他的可以根據(jù)不同的情況選擇性的使用。
sendJndiName
字符串類型的屬性,用于設(shè)置目標(biāo)消息隊列或者主題的JNDI名稱。
sendType
JMSControl.DestinationType類型的屬性,可選值有三個:DestinationType.Auto(自動適配類型),DestinationType.Topic(主題)和DestinationType.Queue(隊列),如果使用DestinationType.AUTO,控件將根據(jù)sendJndiName中指定的目標(biāo)消息類型進行適配。
jndiConnectionFactory
字符串類型的屬性,設(shè)置訪問EJB JNDI上下文環(huán)境需要使用工廠類,和具體的目標(biāo)EJB服務(wù)器相關(guān)。比如訪問JBoss服務(wù)器上的JMS資源時可以設(shè)置jndiConnectionFactory為“org.jnp.interfaces.NamingContextFactory”。
jndiProviderURL
字符串類型的屬性,設(shè)置目標(biāo)EJB容器的相關(guān)屬性,包括訪問JNDI上下文環(huán)境使用的協(xié)議、目標(biāo)服務(wù)器IP地址、服務(wù)端口等,比如訪問JBoss容器中消息隊列時,providerURL可以寫成”jnp://localhost:1099”。
Message注釋
Message注釋是JMS控件中方法級別的注釋,它的參數(shù)為JMSControl.MessageType類型,用于注釋新創(chuàng)建JMS控件的業(yè)務(wù)方法,聲明該業(yè)務(wù)方法發(fā)送消息的類型,可選參數(shù)包括MessageType.Text、MessageType. Bytes(字節(jié)類型)、MessageType.Object(對象類型), MessageType.Map (Map類型)、MessageType.JMSMessage(JMS消息類型)、MessageType.Auto(自動適配類型),默認的消息類型是MessageType.Auto。
如果使用默認的消息類型或者設(shè)置被發(fā)送消息的類型為MessageType.Auto,控件將根據(jù)被發(fā)送消息的類型來決定發(fā)送消息時使用的消息類型:
如果被發(fā)送的消息是字符串內(nèi)容或者是XML對象,控件將發(fā)送TextMessage類型的消息給目標(biāo)消息隊列/主題;
如果被發(fā)送的消息內(nèi)容是字節(jié)數(shù)組,控件將發(fā)送StreamMessage類型的消息給目標(biāo)消息隊列/主題;
如果被發(fā)送的消息內(nèi)容是字節(jié)數(shù)組,控件將發(fā)送StreamMessage類型的消息給目標(biāo)消息隊列/主題;
如果被發(fā)送的消息內(nèi)容是Map對象,控件將發(fā)送MapMessage類型的消息給目標(biāo)消息隊列/主題;
如果被發(fā)送的消息內(nèi)容是JMSMessage對象,控件將發(fā)送JMSMessage類型的消息給目標(biāo)消息隊列/主題;
如果被發(fā)送的消息內(nèi)容實現(xiàn)了Serializable接口,控件將發(fā)送ObjectMessage類型的消息給目標(biāo)消息隊列/主題;
其他的消息內(nèi)容,控件將不發(fā)送消息,而是拋出一個違例。
JMS控件內(nèi)置的方法
JMS控件提供了很多可以在運行時對JMS控件進行定制的Java方法,開發(fā)者使用這些內(nèi)置的方法可以在使用JMS控件的過程中根據(jù)不同的情況靈活的加以應(yīng)用,以便能夠完成各種復(fù)雜的業(yè)務(wù)邏輯。
getSession()
獲取當(dāng)前訪問消息隊列/主題的會話對象。
getDestination()
獲取當(dāng)前訪問消息隊列/主題的目標(biāo)服務(wù)的相關(guān)信息。
getConnection()
獲取當(dāng)前訪問消息隊列/主題和目標(biāo)服務(wù)器之間的連接。
setHeaders(Map)
setHeader(HeaderType,Object)
這兩個方法都用于設(shè)置被發(fā)送消息的頭信息,只有調(diào)用了這兩個方法后接著發(fā)送的消息才使用這部分頭信息,其他的消息不具備這些頭信息。如果發(fā)送的消息中也設(shè)置了同樣的信息,使用setHeaderX方法設(shè)置的參數(shù)將覆蓋消息中設(shè)置的參數(shù)。
被設(shè)置的頭信息包括JMSCorrelationID、JMSExpriation 、Priority 、JMSType,用于發(fā)送消息時標(biāo)識身份或者提供消息發(fā)送所需要的路由。
setProperties(Map)
setProperty(String,Object)
這兩個方法用于在發(fā)送消息的同時提供更多的屬性信息,可以用于在應(yīng)用中作為擴展頭信息而使用,可發(fā)送的類型可以是boolean, byte, short, int, long, float, double, or String等基本Java數(shù)據(jù)類型。
結(jié)束語
JMS是J2EE框架中最重要的部分,也是企業(yè)應(yīng)用中提供異步消息訪問的技術(shù)實現(xiàn),然而JMS的客戶端編寫對于開發(fā)者而言不是一件輕松的事情。控件架構(gòu)中的JMS控件大大的簡化了JMS資源的復(fù)雜性、難度,開發(fā)者只需要通過簡單的繼承org.apache.beehive.controls.system.jms.JMSControl,然后通過提供相應(yīng)的注釋就可以完成JMS資源的訪問。