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