J2EE 組件開發:什麼是消息驅動的EJB

消息服務是一種在分佈式應用之間提供消息傳遞服務的軟件,具有可靠、異步、寬鬆結
合、語言中立、平臺中立的特點,而且通常是可配置的。它的實現原理是:對發送者和
接收者之間傳遞的消息進行封裝,並在分佈式消息客戶程序結合的位置加上一個軟件處
理層。消息服務爲消息的客戶程序提供了一個接口,這個接口隔離了底層的消息服務,
使得各種不同的客戶程序能夠通過一個友好的編程接口方便地通信。
Java消息服務(Java Message Service,JMS)是一個Java API,它定義了消息的客戶程
序如何以一種標準化的形式與底層的消息服務提供者交互。JMS提供了一種接口,底層消
息服務提供者通過該接口向客戶程序提供JMS消息服務。JMS提供了點對點消息模式(Po
int-to-Point)和發佈-訂閱消息模式(Publish-Subscribe)。點對點消息模式通過一
個消息隊列實現,消息的生產者向隊列寫入消息,消息的消費者從隊列提取消息。發佈
-訂閱消息模式通過一個話題(Topic)節點構成的層次結構實現,消息的生產者向這個
層次結構發佈消息,消息的消費者向這個結構訂閱消息。
點對點消息模式具有如下特點:
每一個消息只有一個消費者。
消息的接收者和發送者之間不存在時間上的依賴關係。不論發送者發送消息時接收者是
否在運行,接收者都可以提取信息。
接收者對於成功處理的消息給出回執。
發佈-訂閱消息模式具有如下特點:
每一個消息可以有多個消費者。
向某個話題訂閱的客戶程序只能收到那些在它訂閱之後發佈的消息。爲了接收到消息,
訂閱者必須保持活動狀態。因此,發佈者和訂閱者之間存在時間上的依賴關係。
JMS API在一定程度上放寬了對這種依賴關係的要求,允許創建持久性訂閱(Durable S
ubscription)。有了持久性訂閱,當訂閱者不活動時發送的消息也能接收到。
EJB 2.0規範定義了一種新的EJB類型,即消息驅動的EJB(Message-Driven EJB,簡稱M
DB),它能夠以EJB的形式實現JMS消息的接收者。消息驅動的EJB實現一組新的接口,這
組接口使得EJB能夠異步地接收和處理JMS消息生產者發送到隊列或話題的消息。EJB客戶
程序的構造方式與普通JMS消息生產者的構造方式完全一樣,也就是說,JMS消息生產者
不必知道消息的消費者是一個EJB。
相對於會話Bean和實體Bean而言,消息驅動的Bean最大的特點是客戶程序不通過接口訪
問Bean。與會話Bean和實體Bean不同,消息驅動的Bean只有一個Bean類。從某些方面看
,消息驅動的Bean類似於無狀態會話Bean:
消息驅動的Bean不爲特定的客戶保留數據或對話狀態。
一個消息驅動Bean的所有的實例都是等價的,這使得容器能夠把消息指派給任意一個消
息驅動Bean的實例。容器能夠建立消息驅動Bean的緩衝池,實現消息的併發處理。
一個消息驅動的Bean能夠處理來自多個客戶程序的消息。
消息驅動Bean的實例變量可以在處理客戶消息期間包含一些狀態信息,例如JMS連接、打
開的數據庫連接,或者是對EJB對象的引用。當一個消息到達,容器調用消息驅動Bean的
onMessage()方法處理消息。onMessage()方法通常把消息定型(cast)成爲五種JMS消息
類型之一,然後按照應用的業務邏輯的要求處理消息。
傳遞給消息驅動Bean的消息可能處於一個事務之內,這時,onMessage()方法內的所有操
作都屬於該事務的一部分。如果消息處理結果被回退,則系統將再次投遞該消息。
哪些時候應該使用消息驅動的Bean呢?會話Bean和實體Bean能夠發送JMS消息,能夠同步
接收消息,但不能異步接收。一些時候,爲防止過多地佔用服務器資源,在服務器端的
組件中,我們想要避免阻塞,這時,我們可以用消息驅動的Bean異步接收消息。
二、MDB體系結構
圖一描述了消息驅動的Bean組件的基本體系結構。
在圖一中,位於頂端的是javax.ejb.EnterpriseBean接口,它是所有EJB的基礎接口。E
nterpriseBean接口派生出了javax.ejb.MessageDrivenBean接口,所有消息驅動的EJB類
必須實現javax.ejb.MessageDrivenBean接口。此外,消息驅動的Bean必須實現javax.j
ms.MessageListener接口。公用的、非最終的、非抽象的消息驅動的EJB,比如圖一顯示
的MyMessageDrivenEJBean,必須同時實現MessageListener接口和MessageDrivenBean接
口。消息驅動的EJB與其他類型的EJB不同,它們不把業務方法導出給客戶程序,它們關
心的只是遵從EJB容器的接口要求。由於這個原因,消息驅動的Bean必須有一個不需要參
數的公用構造方法(ejbCreate()方法),而且不應該實現finalize()方法。
2.1 MDB接口
在消息驅動的Bean中,setMessageDrivenContext()方法用來把一個MessageDrivenCont
ext的對象實例傳遞給EJB,它是MessageDrivenBean接口定義中容器調用的第一個方法。
MessageDrivenContext對象封裝了一個EJB消息驅動容器上下文的接口,支持消息驅動的
EJB實例訪問容器提供的運行時消息驅動上下文
對於消息驅動的EJB來說,關鍵之一是要實現一個沒有參數的ejbCreate()方法。當EJB容
器準備創建消息驅動EJB的實例時,它將調用這個方法。容器之所以決定創建某個EJB的
實例,可能是因爲它要構造一個Bean實例的緩衝池,也可能是因爲它接收到了客戶的請
求。這個ejbCreate()方法和其他Bean上的EJB構造方法類似,屬於EJB實現的一種特殊的
構造函數或初始化方法。
當EJB容器準備不讓Bean實例繼續處理客戶程序的請求時,它就會調用消息驅動Bean的e
jbRemove()方法。何時在消息驅動的Bean上調用ejbRemove()方法由EJB容器單獨決定,
不受EJB客戶程序的任何約束。應當注意的是,容器並不保證一定調用ejbRemove()方法
。在正常操作時,容器會調用ejbRemove()方法;但是,當消息驅動的Bean向容器拋出了
系統異常時,不能保證ejbRemove()方法一定會被調用。由於這個原因,Bean開發者必須
按時檢查和清除Bean分配的所有資源。
對於Bean開發者來說,最重要的任務也許是實現onMessage()方法。當一個異步消息必須
由Bean實例處理時,容器將調用onMessage()方法。onMessage()方法的參數是一個普通
的JMS javax.jms.Message的實例,消息驅動的EJB實例從這個Message的實例提取待處理
的數據完成消息處理。
2.2 JMS消息接口
那麼,在onMessage()方法調用傳入的 JMS消息中,消息驅動的Bean如何提取信息,可以
提取哪些信息呢?圖二描述了基本JMS消息類型的核心接口和概念。在一個以JMS爲基礎
的消息系統中,Message接口是在系統中傳遞的所有消息的最基本的接口(或稱之爲根接
口,Root Interface)。Destination接口描述了消息傳遞的一個終端;類似地,由於消
息有一個傳遞模式,所以圖二還顯示了Message接口與DeliveryMode接口的概念上的關係

JMS消息的頭信息可以通過一組標準的方法設置或提取,這組標準方法的名字爲getJMSX
XX()或setJMSXXX()形式(下面我們分別稱之爲get方法和set方法),其中XXX是消息頭
信息中的屬性名字,例如getJMSDeliveryMode()方法。在Message接口中,通過get方法
和set方法操作的標準頭信息屬性包括:唯一的消息ID,時標(Timestamp),答覆和目
標地址,消息傳遞模式,消息類型,以及消息的優先級。
在JMS消息中,JMS容器提供者特有的屬性可以通過getXXXProperty()方法提取,或通過
setXXXProperty()方法設置,其中XXX表示屬性的類型,例如byte getByteProperty(ja
va.lang.String name)。每一個屬性有一個通過String對象指定的名字和相應的值。名
字以JMSX前綴開頭的屬性作爲標準JMS屬性保留。
與消息正文數據(或稱之爲消息體,與消息頭相對而言)的五種類型對應,五種消息類
型擴展了Message接口,如圖三所示。Byte數據由BytesMessage封裝,Serializable對象
由ObjectMessage封裝,String消息由TextMessage封裝,鍵-值對由MapMessage封裝,I
/O流由StreamMessage封裝。
這些派生消息類型上的方法爲特定類型的消息正文定義了get和set操作,而在Message基
礎接口內,有一個通用的clearBody()方法,這個方法清除消息的正文,並把它置入只寫
模式。clearBody()方法只清除消息正文,不清除消息頭或屬性。如果消息正文是隻讀的
,調用該消息後,消息正文的狀態將和新消息的空白正文狀態一樣。
2.3 MDB客戶程序接口
消息驅動Bean的客戶程序並不知道接收端實際處理消息的將是一個EJB。事實上,消息驅
動Bean的客戶程序的構造方法與普通JMS客戶程序的構造方法完全一樣。
JMS的核心體系如圖四所示。從圖中我們可以看出,JMS ConnectionFactory(連接工廠
)初始上下文通過Java名稱和目錄接口(Java Naming and Directory Interface,JND
I)創建。隨後,連接工廠將用來創建與JMS服務提供者的連接。有了JMS連接,我們就可
以獲得創建消息生產者和消息消費者的會話(Session)。實際上,消息驅動Bean的客戶
程序就是消息的生產者,它發送的消息將由消息驅動的Bean(即消息消費者)接收。
三、點對點消息隊列模式
圖五顯示了在一個支持點對點消息隊列的系統中JMS的基本體系結構。消息隊列體系實際
上是核心JMS體系的一個擴展,特別地,它加入了對消息隊列功能的支持。連接工廠、連
接、會話、消息生產者、消息消費者等都用點對點消息隊列形式的接口進行了擴展。
JMS客戶程序利用JNDI獲得一個QueueConnectionFactory對象的引用。隨後,我們用Que
ueConnectionFactory.createQueueConnection()方法之一創建一個QueueConnection對
象的實例。調用createQueueConnection()方法時可以指定一個用戶名字和密碼,或者,
我們也可以使用該方法不帶參數的版本,此時假定使用默認用戶身份。
QueueConnection接口是Connection接口的一種子類型,它代表着一個與JMS點對點消息
隊列服務的連接。JMS客戶程序調用createQueueSession()方法創建QueueSession的實例
,createQueueSession()方法調用中一個boolean類型的參數指定了QueueSession對象是
否要提供事務支持。另外,回執的模式也在createQueueSession()調用中通過參數指定
,這個參數的值可以是三個靜態的標識符之一:AUTO_ACKNOWLEDGE,CLIENT_ACKNOWLED
GE,DUPS_OK_ACKNOWLEDGE。
QueueSession.createQueue()方法返回一個Queue對象的實例,調用Queue.getQueueNam
e()方法可以返回隊列的名字。
QueueSession.createSender()方法創建一個QueueSender消息生產者,利用QueueSende
r可以把消息發送到Queue。消息可以通過各種不同的QueueSender.send()方法發送到Qu
eue,這些不同的send()方法能夠把消息發送給QueueSender對象關聯的Queue對象,或者
發送給send()方法調用中指定的Queue對象。消息遞送模式、優先級、消息的有效時間都
可以在調用QueueSender.send()方法時指定。
發送給Queue的消息可以用Session接口中定義的各種消息構造方法創建。
四、發佈-訂閱消息模式
圖六顯示了在一個支持發佈-訂閱消息模式的系統中JMS的基本體系結構。發佈-訂閱消息
機制也是核心JMS機制的一種擴展,增加了一些適合發佈-訂閱消息模式的功能。連接工
廠、連接、會話、消息生產者、消息消費者等都用發佈-訂閱形式的接口進行了擴展。
JMS客戶程序通過JNDI獲得一個TopicConnectionFactory對象的引用。TopicConnection
Factory.createTopicConnection()方法用來創建TopicConnection對象的實例。調用cr
eateTopicConnection()方法時可以指定一個用戶名字和密碼,或者,我們也可以使用該
方法不帶參數的版本,此時假定使用默認用戶身份。
TopicConnection接口是Connection接口的一種子類型,它代表着一個與JMS發佈-訂閱消
息服務的連接。JMS客戶程序調用TopicConnection.createTopicSession()方法創建Top
icSession的實例。會話的事務支持和回執模式也在創建TopicSession時指定。
TopicSession.createTopic()方法返回一個Topic對象的實例。Topic接口封裝了一個話
題目的地,發佈者向該目的地發送消息,訂閱者從該目的地接收消息。不同的服務提供
者按照不同的方式實現話題名稱的層次結構,調用Topic.getTopicName()方法可以獲得
話題的String形式的描述。
TopicSession.createPublisher()方法創建一個TopicPublisher消息生產者,它用來把
消息發佈到Topic。消息可以通過各種不同的TopicPublisher.publish()方法發佈到Top
ic,這些不同的publish()方法能夠把消息發送給TopicPublisher對象關聯的Topic對象
,或者發送給publish()方法調用中指定的Topic對象。消息遞送模式、優先級、消息的
有效時間都可以在調用TopicPublisher.publish()方法時指定。
發送給Topic的消息可以用Session接口中定義的各種消息構造方法創建。
五、實例
本示例應用是一個消息驅動Bean應用的簡單例子,由以下兩部分構成:
SimpleMessageClient:J2EE應用客戶程序,向隊列發送消息。
SimpleMessageEJB:一個消息驅動的Bean,異步地接收和處理由客戶程序發送到隊列的
消息。
圖七描述了這個應用的結構。客戶端應用把消息發送到隊列,隊列由管理員通過j2eead
min命令創建。JMS提供者(這裏是J2EE服務器)把消息傳遞給消息驅動Bean的實例,由
Bean的實例處理消息。
該示例應用的完整代碼可以從本文最後下載。
5.1 客戶端
SimpleMessageClient把消息發送到SimpleMessageBean監聽的隊列。客戶程序首先確定
連接工廠和隊列:
queueConnectionFactory = (QueueConnectionFactory) jndiContext.lookup
("java:comp/env/jms/MyQueueConnectionFactory");
queue = (Queue) jndiContext.lookup("java:comp/env/jms/QueueName");
接下來,客戶程序創建隊列連接、會話和一個消息發送器:
queueConnection = queueConnectionFactory.createQueueConnection();
queueSession = queueConnection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
queueSender = queueSession.createSender(queue);
最後,客戶程序把幾個消息發送到隊列:
message = queueSession.createTextMessage();
for (int i = 0; i < NUM_MSGS; i++) {
message.setText("我是" + msgArray[i] );
System.out.println("Sending message: " +
message.getText());
queueSender.send(message);
}
5.2 MDB組件
SimpleMessageEJB類闡明瞭編寫消息驅動Bean類的要求:
實現MessageDrivenBean接口和MessageListener接口。
類定義爲public類型。
類不能定義成abstract或final。
實現一個onMessage()方法。
實現一個ejbCreate()方法和一個ejbRemove()方法。
包含一個public類型的不需要參數的構造方法。
不能定義finalize()方法。
與會話Bean和實體Bean不同,消息驅動的Bean不定義客戶程序訪問的接口。客戶程序不
是先定位消息驅動的Bean,再調用這些Bean上的方法。雖然消息驅動的Bean沒有業務方
法,但它們可以包含由onMessasge()方法內部調用的輔助方法。
當隊列接收到一個消息,EJB容器將調用消息驅動Bean的onMessage()方法。在SimpleMe
ssageBean類中,onMessage()方法把接收到的消息定型(cast)成爲TextMessage類型,
然後顯示出文本信息:
public void onMessage(Message inMessage) {
TextMessage msg = null;
try {
if (inMessage instanceof TextMessage) {
msg = (TextMessage) inMessage;
System.out.println("MESSAGE BEAN:收到消息: "
+ msg.getText());
} else {
System.out.println("消息類型錯誤: "
+ inMessage.getClass().getName());
}
} catch (JMSException e) {
e.printStackTrace();
mdc.setRollbackOnly();
} catch (Throwable te) {
te.printStackTrace();
}
}
消息驅動Bean的ejbCreate()方法和ejbRemove()方法必須符合以下要求:
訪問控制修飾符必須是public。
返回值類型必須是void。
不能有static和final修飾符。
throws子句不能定義任何應用自定義的異常。
不能帶有參數。
在SimpleMessageBean類中,ejbCreate()方法和ejbRemove()方法都是空的,不執行任何
有實際意義的操作。
5.3 打包
接下來我們要把上面的應用打包成一個J2EE EAR文件。首先要把SimpleMessageEJB打包
成Jar文件。通常,打包過程可以通過工具完成,但理解模塊部署描述器仍是必要的。在
EJB應用模塊部署描述器中,頂級元素下面包含元素。下面可以包含一組元素(按照EJB
2.0新規範),每一個元素描述一個消息驅動Bean的配置和部署。
SimpleMessageEJB的ejb-jar.xml文件如下所示。元素內定義了消息驅動Bean的配置和部
署信息,例如唯一的Bean名字、Bean類的名字、配置參數、安全信息、事務信息、消息
目的地類型等。
......

SimpleMessageJAR


SimpleMessageEJB
SimpleMessageEJB
SimpleMessageBean
Container

javax.jms.Queue













SimpleMessageEJB
Bean
onMessage

javax.jms.Message


Required



除了ejb-jar.xml部署描述器之外,通常還要有面向特定平臺和環境的部署描述器。大多
數時候,這種描述器可以用GUI工具編寫。請參見下載代碼中提供的例子。
打包好各個模塊之後,接着還要把J2EE應用打包成EAR文件。有關這一步驟的詳細說明,
請參見開發平臺的相關文檔。本文以後有關部署和運行的說明,就以打包後的EAR文件爲
基礎。
5.4 部署和運行
假設我們在Sun的J2EE參考實現上部署和測試這個示例應用。爲便於查看消息驅動Bean的
輸出,我們必須以-verbose模式啓動服務器:
j2ee -verbose
用下面的j2eeadmin命令創建隊列:
j2eeadmin -addJmsDestination jms/MyQueue queue
驗證隊列已經創建成功:
j2eeadmin -listJmsDestination
啓動deploytool,選擇菜單“File-->Open”,打開SimpleMessageApp.ear文件。接着,
選擇菜單“Tools --> Deploy”,部署應用。出現部署提示時,選中“Return Client
JAR”檢查框。
在一個命令窗口中,進入EAR文件(SimpleMessageAppClient.jar文件)所在目錄,把環
境變量APPCPATH設置爲SimpleMessageAppClient.jar。然後,執行下面的命令:
runclient -client SimpleMessageApp.ear -name SimpleMessageClient -textauth

在登錄提示中,輸入用戶名字j2ee,輸入密碼j2ee。此時,客戶程序將輸出以下內容:

Sending message: 我是老大孫悟空
Sending message: 我是老二豬八戒
Sending message: 我是老三沙和尚
在啓動j2ee服務器的命令窗口,我們可以看到如下輸出:

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章