Message-driven Bean
Message-driven Bean是EJB2.0 規范中提出的的Enterprise Bean。
Message-driven Bean 的產生原因
效率原因
在JavaEE™平臺中,客戶端對Session Bean 和Entity Bean 的方法調用通過RMI或RMI-IIOP協議進行,這是傳統的通過網絡進行遠程調用的方法,當調用請求通過網絡傳播到容器,容器則將客戶端請求變成一序列的方法調用依次進行。客戶端只有在容器處理完請求并返回結果后方可繼續進行。
可靠性的原因
當客戶端對Session Bean或Entity Bean進行調用時,必須保證服務器容器處于運行狀態,如容器或網絡出現錯誤,客戶端調用將無法進行。
事件的廣播
傳統的RMI 或RMI-IIOP機制中,客戶端在某一時刻只能與某一具體的服務器通訊, 沒有任何內置的機制來將事件廣播到多個服務器。
Message-driven Bean作為遠程方法調用的一種替代方法,在客戶端和服務器的直接方法調用之間放置了一個中間層,接收一個或多個客戶端的消息,并將消息轉發給一個或多個消息的使用者(Message Consumer)。
通過消息機制而非直接的方法調用,客戶端可以繼續執行而不必等待服務器的運行結果,服務器可以選擇在方法調用完成后通知客戶,而消息機制本身保證了信息傳輸的可靠性,同時使用消息域(Message Domain)中的消息類型模型以達到事件廣播的機制。
但是,對Message-Driven Bean 的使用也有一定的限制,如不適用于依賴于方法調用、要求具有明確的返回值才能繼續的客戶端程序。另外,如果在一個應用中過多的使用了Message-Driven Bean, 對應用的執行效率將會產生影響,所以不適用于對時間因素敏感的客戶端程序(如在下午兩點定購下午四點的機票,而在四點后才得到訂購是否成功的結果,這時結果已毫無用處)。
Message-driven Bean 作為一般的JMS 使用者(consumer)
作為一種具有JMS使用者(consumer)功能的Enterprise Bean組件模型,Message-Driven Bean由EJB容器進行管理,具有一般的JMS使用者(consumer)所不具有的優點,如對于一個Message-driven Bean,容器可創建多個實例來處理大量的并發消息,而一般的JMS使用者 (consumer)開發時則必須對此進行處理才能獲得類似的功能。同時Message-Driven Bean可取得EJB所能獲得的標準服務,如容器管理事務等服務。
由于與Message-driven Bean相關的主題(Topic)或隊列(Queue)可以在部署時配置,因此,Message-driven Bean具有更多的靈活性。
但注意,一個Message-driven Bean在部署時只可與一個具體的主題(Topic)或隊列(Queue) 建立關聯。如有多個主題(Topic)或隊列(Queue)需要與一個Message-driven Bean關聯,則 可以在部署時部署多個Message-driven Bean類,或使用一般的JMS使用者(consumer) 。
Message-driven Bean 與其他Enterprise Bean
作為Enterprise Bean組件模型之一,Message-driven Bean,具有一些與Session Bean 和Entity Bean相同的方法,但由于Message-driven Bean本身不處理客戶端調用,也無會話狀態,客戶只能通過向與Message Driven Bean關聯的隊列或主題發送消息從而與Message-driven Bean 進行交互,因此,Message-driven Bean 與Session Bean 和Entity Bean之間最大的不同之處在于Message-Driven Bean不具有組件接口及Home接口。
另外,Message-driven Bean異步地處理隊列(Queue)或主題(Topic)中的消息,而非方法調用。
Message-driven Bean
Message-driven Bean是JMS消息驅動的JavaEE™平臺服務器端組件,具備無狀態、支持事務的特點。當從JMS隊列(Queue)或主題(Topic)中接收到JMS消息后,由容器對組件進行調用。一般,可理解為消息的監聽器(Listener)及接收者(Consumer)。
Message-driven Bean組件對于客戶端是不可見的。客戶端如希望調用封裝在組件中的業務邏輯,只能通過向組件監聽的JMS隊列(Queue)或主題(Topic)發送消息,然后容器以事件的形式向組件實例發送消息,組件實例根據消息的內容調用相應的業務邏輯或其他組件。
因此,任何向組件監聽的特定JMS隊列(Queue)或主題(Topic)發送JMS消息的客戶端,即可視為Message-driven Bean的客戶端。
Message-driven Bean組件模型不具備會話狀態,也就是說,當組件實例在沒有對客戶端的JMS消息提供處理的時候,所有的實例間沒有差別。
Message-driven Bean的運行和客戶端的運行是異步的。同時,對于客戶端不可見。其實例由容器創建,其生存周期由容器控制。
Message-driven Bean與EJB容器、客戶端、消息系統
下圖是EJB容器、客戶端、消息系統與EJB之間的關系:
EJB容器、客戶端、消息系統與EJB之間的關系
客戶端發送消息到JMS消息系統中的隊列或主題。Message-driven Bean在部署到容器中時,指定的隊列或主題,容器對其進行監聽;當容器從隊列或主題中接收到消息之后,將消息作為事件的一部分通知容器中相應的Message-driven Bean實例。
組件模型單元
Message-driven Bean由容器控制其生存周期,容器提供安全、并發、事務等等服務,對于客戶端來說,Message-driven Bean是不可見的。因此,Message-driven Bean不同于Session Bean和Entity Bean,不具有組件接口和Home接口。
Message-driven Bean組件模型包含兩個單元,即組件類和部署描述。下面分別對開發這些單元時,涉及的普遍過程、規則及注意事項進行描述。
組件類
javax.ejb.MessageDrivenBean接口
EJB2.1規范中的Message-driven Bean組件中的組件類必須實現MessageDrivenBean接口。EJB3.0規范不強制Message-driven Bean實現該接口,而通過@MessageDriven注解進行標記并通過依賴注入與注解實現類似功能。
MessageDrivenBean接口中定義了兩個容器管理回調的方法:
· setMessageDrivenContext方法,容器創建Bean實例后,容器將調用該方法將由容器維護的Bean實例的上下文(context)與Bean實例進行關聯。在EJB3.0規范中,可使用@Resource注解通知容器注入MessageDrivenContext實例。
· ejbRemove方法,在實例被容器清除時,容器將調用此方法。一般,實例會在此方法中對實例占用的資源進行釋放。在EJB3.0規范中,可使用@PreDestroy注解標記此方法。
javax.jms.MessageListener接口
Message-driven Bean組件中的組件類必須實現MessageListener接口。
在消息到達Message-driven Bean指定的監聽隊列或主題時,容器將調用javax.jms.MessageListener接口中定義的onMessage方法。開發者在此方法中提供對消息進行處理的業務邏輯。
Session Bean和Entity Bean不可實現javax.jms.MessageListener接口。
javax.ejb.MessageDrivenContext接口
容器將提供一個MessageDrivenContext對象,使實例可以訪問由容器維護的實例的上下文環境。在此接口中,定義了如下方法:
· getEJBHome、getEJBLocalHome方法,從EJBContext接口繼承的方法,Message-driven Bean實例不可調用此方法;
· getCallerPrincipal方法,從EJBContext接口繼承的方法,Message-driven Bean實例不可調用此方法;
· isCallerInRole方法,從EJBContext接口繼承的方法,Message-driven Bean實例不可調用此方法;
· setRollbackOnly方法,當前事務將被永久標記為回滾,不會被提交。只有容器管理事務的Message-driven Bean可被允許使用此方法;
· getRollbackOnly方法,檢查當前事務是否已被標記為回滾。例如,EJB實例可以通過此方法,決定是否繼續在當前事務邊界內繼續進行計算。只有容器管理事務的Message-driven Bean可被允許使用此方法;
· getUserTransaction方法,返回javax.transaction.UserTransaction接口。EJB實例可通過此接口對事務邊界進行劃分,并取得事務的狀態。只有容器管理事務的Message-driven Bean可被允許使用此方法;
串行化的調用
Apusic應用服務器中的EJB容器支持Message-driven Bean的多個實例的并發運行,但是每個實例只“看到”一個串行的方法調用過程,因此開發Message-driven Bean時,不需要將其以可重入(reentrant)的方式進行編寫。
消息處理的并發
Apusic應用服務器允許Message-driven Bean的多個實例并發執行,提供對流(Stream)消息并發處理。
Message-driven Bean方法的事務上下文
onMessage方法在何種事務范圍內被調用,取決于部署描述中指定的事物屬性,如Bean被指定使用容器管理的事務的方式,則必須將事務屬性設置為“Required”或“NotSupported”。
當Bean采用Bean管理的事務的方式,即使用javax.transaction.UserTransaction接口進行事務劃分時,導致Bean實例被調用的消息接收操作并非是事務中的一部分。如果希望消息接收操作是事務中的一部分,則Bean必須使用容器管理事務的方式,并且設置事務屬性為“Required”。
消息接收確認(Message Acknowledgement)
Message-driven Bean不能使用JMS API中提供的消息接收確認操作。消息接收確認操作由容器自動完成。如Bean采用了容器管理事務的方式,則消息接收確認操作作為事務提交的一部分自動進行。如使用了Bean管理事務的方式,消息接收確認操作不能作為事務提交的一部分,開發者可通過在部署描述中的acknowledge-mode元素指定消息接收確認操作的方式為AUTO_ACKNOWLEDGE或DUPS_OK_ACKNOWLEDGE,如未指定acknowledge-mode元素,容器將使用AUTO_ACKNOWLEDGE方式進行消息接收確認操作。
指定隊列(Queue)或主題(Topic)
當Message-driven Bean被部署到容器時,必須關聯到某個消息隊列(Queue)或主題(Topic),以便容器對此隊列或主題進行監聽。
開發者可通過@MessageDriven注解的mappedName屬性或部署描述文件中的message-driven-destination元素指定關聯的隊列或主題。
如Bean關聯的是一個消息主題,則通過部署描述中的subscription-durability元素指定對隊列進行的是持久還是非持久訂閱,如此元素未指定,則使用非持久訂閱的方式。
異常處理
Message-driven Bean中的onMessage方法不能聲明拋出java.rmi.RemoteException異常。
一般來說,Message-driven Bean在運行期間不應向容器拋出RuntimeException異常。RuntimeException異常是指會導致Message-driven Bean進入“不存在”狀態的非應用級異常。如果Bean使用了Bean-managed事務并拋出了RuntimeException異常,容器不應確認收到了這條消息。
從發信端看來,收信端一直存在,若發信端繼續對該目的地發送消息,容器會自動把消息轉向到其他Message-driven Bean實例。
遺漏的PreDestroy調用
在系統發生異常的情況下,不能保證容器總會調用Bean的PreDestroy方法,因此,如果Bean在PostConstruct方法中打開了一些資源,并在PreDestroy方法中釋放這些資源,在這種情況下,則這些資源不能被釋放。
鑒于以上原因,使用Message-driven Bean的應用需要提供一種機制,以便周期性的清除這些未釋放的資源占用。
必須遵守的規則
在開發Message-driven Bean時,開發者必須遵守如下規則:
組件類
· 使用EJB2.1規范時,必須間接或直接實現javax.ejb.MessageDrivenBean接口;使用EJB3.0規范時,可改為使用@MessageDriven注解對組件類進行標記。
· 必須間接或直接實現javax.jms.MessageListener接口;
· 類必須聲明為public,不可被聲明為final或abstract類;
· 必須擁有一個無參數的public構造函數(constructor);
· 類不能定義finalize()方法;
· 在原EJB2.1規范中,類必須實現ejbCreate()方法用來創建組件實例,在EJB3.0中,這一要求已被移除了。EJB3.0的兼容規則規定,如果Message-driven Bean類實現了ejbCreate()方法,將看作被@PostConstruct注解標記的方法處理。此時若同時使用@PostConstruct注解,則只能標記ejbCreate()方法。
onMessage方法
· 方法必須被聲明為public;
· 方法不能被聲明為final或static;
· 返回值必須為void;
· 方法只能有一個javax.jms.Message類型的參數;
· 不能拋出java.rmi.RemoteException異常
· 運行期間一般來說不應拋出RuntimeException。請參考:
ejbRemove方法
· 方法名必須是ejbRemove;
· 方法必須被聲明為public;
· 方法不能被聲明為final或static;
· 返回值必須為void;
· 方法不能有參數;
· 不能拋出java.rmi.RemoteException異常。
· 在EJB3.0規范中,可使用@PreDestroy注解實現同樣效果。若實現javax.ejb.MessageDrivenBean接口同時使用注解,則只能把ejbRemove()方法注解為@PreDestroy
生存周期
下圖表示Message-driven Bean的生存周期。
Message-driven Bean的生存周期
posted on 2007-10-22 12:27
???MengChuChen 閱讀(2706)
評論(0) 編輯 收藏 所屬分類:
EJB3.0