程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> J2EE組件開發 - 消息驅動的EJB

J2EE組件開發 - 消息驅動的EJB

編輯:關於JAVA

一、概述

消息服務是一種在分布式應用之間提供消息傳遞服務的軟件,具有可靠、異步、寬松結合、語言中立、平台中立的特點,而且通常是可配置的。它的實現原理是:對發送者和接收者之間傳遞的消息進行封裝,並在分布式消息客戶程序結合的位置加上一個軟件處理層。消息服務為消息的客戶程序提供了一個接口,這個接口隔離了底層的消息服務,使得各種不同的客戶程序能夠通過一個友好的編程接口方便地通信。

Java消息服務(Java Message Service,JMS)是一個Java API,它定義了消息的客戶程序如何以一種標准化的形式與底層的消息服務提供者交互。JMS提供了一種接口,底層消息服務提供者通過該接口向客戶程序提供JMS消息服務。JMS提供了點對點消息模式(Point-to-Point)和發布-訂閱消息模式(Publish-Subscribe)。點對點消息模式通過一個消息隊列實現,消息的生產者向隊列寫入消息,消息的消費者從隊列提取消息。發布-訂閱消息模式通過一個話題(Topic)節點構成的層次結構實現,消息的生產者向這個層次結構發布消息,消息的消費者向這個結構訂閱消息。

點對點消息模式具有如下特點:

每一個消息只有一個消費者。

消息的接收者和發送者之間不存在時間上的依賴關系。不論發送者發送消息時接收者是否在運行,接收者都可以提取信息。接收者對於成功處理的消息給出回執。

發布-訂閱消息模式具有如下特點:

每一個消息可以有多個消費者。

向某個話題訂閱的客戶程序只能收到那些在它訂閱之後發布的消息。為了接收到消息,訂閱者必須保持活動狀態。因此,發布者和訂閱者之間存在時間上的依賴關系。

JMS API在一定程度上放寬了對這種依賴關系的要求,允許創建持久性訂閱(Durable Subscription)。有了持久性訂閱,當訂閱者不活動時發送的消息也能接收到。

EJB 2.0規范定義了一種新的EJB類型,即消息驅動的EJB(Message-Driven EJB,簡稱MDB),它能夠以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的基礎接口。EnterpriseBean接口派生出了javax.ejb.MessageDrivenBean接口,所有消息驅動的EJB類必須實現javax.ejb.MessageDrivenBean接口。此外,消息驅動的Bean必須實現javax.jms.MessageListener接口。公用的、非最終的、非抽象的消息驅動的EJB,消息驅動的EJB與其他類型的EJB不同,它們不把業務方法導出給客戶程序,它們關心的只是遵從EJB容器的接口要求。由於這個原因,消息驅動的Bean必須有一個不需要參數的公用構造方法(ejbCreate()方法),而且不應該實現finalize()方法。

2.1 MDB接口

在消息驅動的Bean中,setMessageDrivenContext()方法用來把一個MessageDrivenContext的對象實例傳遞給EJB,它是MessageDrivenBean接口定義中容器調用的第一個方法。

MessageDrivenContext對象封裝了一個EJB消息驅動容器上下文的接口,支持消息驅動的EJB實例訪問容器提供的運行時消息驅動上下文對於消息驅動的EJB來說,關鍵之一是要實現一個沒有參數的ejbCreate()方法。當EJB容器准備創建消息驅動EJB的實例時,它將調用這個方法。容器之所以決定創建某個EJB的實例,可能是因為它要構造一個Bean實例的緩沖池,也可能是因為它接收到了客戶的請求。這個ejbCreate()方法和其他Bean上的EJB構造方法類似,屬於EJB實現的一種特殊的構造函數或初始化方法。

當EJB容器准備不讓Bean實例繼續處理客戶程序的請求時,它就會調用消息驅動Bean的ejbRemove()方法。何時在消息驅動的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消息的頭信息可以通過一組標准的方法設置或提取,這組標准方法的名字為getJMSXXX()或setJMSXXX()形式(下面我們分別稱之為get方法和set方法),其中XXX是消息頭信息中的屬性名字,例如getJMSDeliveryMode()方法。在Message接口中,通過get方法和set方法操作的標准頭信息屬性包括:唯一的消息ID,時標(Timestamp),答復和目標地址,消息傳遞模式,消息類型,以及消息的優先級。

在JMS消息中,JMS容器提供者特有的屬性可以通過getXXXProperty()方法提取,或通過setXXXProperty()方法設置,其中XXX表示屬性的類型,例如byte getByteProperty(java.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,JNDI)創建。隨後,連接工廠將用來創建與JMS服務提供者的連接。有了JMS連接,我們就可以獲得創建消息生產者和消息消費者的會話(Session)。實際上,消息驅動Bean的客戶程序就是消息的生產者,它發送的消息將由消息驅動的Bean(即消息消費者)接收。

三、點對點消息隊列模式

在一個支持點對點消息隊列的系統中JMS的基本體系結構。消息隊列體系實際上是核心JMS體系的一個擴展,特別地,它加入了對消息隊列功能的支持。連接工廠、連接、會話、消息生產者、消息消費者等都用點對點消息隊列形式的接口進行了擴展。

JMS客戶程序利用JNDI獲得一個QueueConnectionFactory對象的引用。隨後,我們用QueueConnectionFactory.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.getQueueName()方法可以返回隊列的名字。

QueueSession.createSender()方法創建一個QueueSender消息生產者,利用QueueSender可以把消息發送到Queue。消息可以通過各種不同的QueueSender.send()方法發送到Queue,這些不同的send()方法能夠把消息發送給QueueSender對象關聯的Queue對象,或者發送給send()方法調用中指定的Queue對象。消息遞送模式、優先級、消息的有效時間都可以在調用QueueSender.send()方法時指定。

發送給Queue的消息可以用Session接口中定義的各種消息構造方法創建。

四、發布-訂閱消息模式

在一個支持發布-訂閱消息模式的系統中JMS的基本體系結構。發布-訂閱消息機制也是核心JMS機制的一種擴展,增加了一些適合發布-訂閱消息模式的功能。連接工廠、連接、會話、消息生產者、消息消費者等都用發布-訂閱形式的接口進行了擴展。

JMS客戶程序通過JNDI獲得一個TopicConnectionFactory對象的引用。TopicConnectionFactory.createTopicConnection()方法用來創建TopicConnection對象的實例。調用createTopicConnection()方法時可以指定一個用戶名字和密碼,或者,我們也可以使用該方法不帶參數的版本,此時假定使用默認用戶身份。

TopicConnection接口是Connection接口的一種子類型,它代表著一個與JMS發布-訂閱消息服務的連接。JMS客戶程序調用TopicConnection.createTopicSession()方法創建Top icSession的實例。會話的事務支持和回執模式也在創建TopicSession時指定。

TopicSession.createTopic()方法返回一個Topic對象的實例。Topic接口封裝了一個話題目的地,發布者向該目的地發送消息,訂閱者從該目的地接收消息。不同的服務提供者按照不同的方式實現話題名稱的層次結構,調用Topic.getTopicName()方法可以獲得話題的String形式的描述。

TopicSession.createPublisher()方法創建一個TopicPublisher消息生產者,它用來把消息發布到Topic。消息可以通過各種不同的TopicPublisher.publish()方法發布到Topic,這些不同的publish()方法能夠把消息發送給TopicPublisher對象關聯的Topic對象,或者發送給publish()方法調用中指定的Topic對象。消息遞送模式、優先級、消息的有效時間都可以在調用TopicPublisher.publish()方法時指定。發送給Topic的消息可以用Session接口中定義的各種消息構造方法創建。

五、實例

本示例應用是一個消息驅動Bean應用的簡單例子,由以下兩部分構成:

SimpleMessageClient:J2EE應用客戶程序,向隊列發送消息。

SimpleMessageEJB:一個消息驅動的Bean,異步地接收和處理由客戶程序發送到隊列的消息。

客戶端應用把消息發送到隊列,隊列由管理員通過j2eeadmin命令創建。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()方法。在SimpleMessageBean類中,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: 我是老三沙和尚

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved