[转载]使用 Apache Geronimo 和 JMS 构建事件驱动的框架
使用 Apache Geronimo 和 JMS 構建事件驅動的框架
能夠及時響應實時信號和事件成為企業框架的最重要方面之一。本文講述了 Apache Geronimo 框架提供的技術和工具,使應用程序和服務能夠有效地對這些信號和事件作出響應,并將其作為消息傳播到駐留在平臺虛擬層之間的相關組件中。這些技術(包括面 向服務的架構 (SOA) 和使用 Java? 反射的有效事件驅動交互框架)有助于降低設計有效事件驅動的軟件系統的復雜性,同時可以增加靈活性。
當實時更改和事件發生時,對其作出響應是企業框架的重要需求。本文介紹了 Apache Geronimo 框架中采用的技術和機制,它們使應用程序和服務能夠有效地響應實時刺激,然后跨架構虛擬層發送和接收事件。
開發人員試圖使用傳統順序處理方法設計和構建動態工作流和集成系統時會遇到麻煩,因此,急需更適合的、事件感知的技術和工具。SOA 和事件驅動的編程可以解決這一復雜的難題。
SOA 給出一個松散耦合的開發模型和運行時環境。它使服務提供者和服務消費者能夠使用動態組件交互來構建交互模型,這些交互模型能夠利用該開發模型靈活性和強大 功能。事件驅動的交互模型比傳統同步機制能更及時地對動態事件作出響應,部分原因是 SOA 中事件驅動的編程利用分布式系統本身需要的許多相同特性,包括專門化、模塊化和適應性。
事件驅動的架構
2003 年,Gartner Group 引入事件驅動的架構 (EDA) 作為一種構建系統、服務和應用程序的方法,在這些所構建的東西中,事件在松散耦合的事件接收者之間路由。事件驅動的系統由事件生產者 和事件接收者 組成。事件生產者可以將事件發布到事件通道,后者可以將事件分發到訂閱事件的接收者。 與生產者發布事件一樣,事件通道將事件轉發給接收者。如果沒有可用的接收者,事件通道會將事件存儲起來,然后將其轉發到稍后可用的接收者。此過程稱為存儲和轉發。
EDA 使用傳遞消息概念作為兩個或多個實體之間交互的方法。通過觸發對應于某些業務領域事件的信號和消息,來啟動交互。當每個給定事件發生時,會通知該事件的所有訂閱者。然后訂閱者可以對事件采取行動。
EDA 受益于以下屬性:
- 無耦合的關聯:事件發布者和事件訂閱者預先無需知道彼此的存在。
- 多對多的交互:一個或多個事件會影響一個或多個訂閱者。
- 基于事件的控制流程:當應用程序響應發生的事件時,應用程序流程非常自然。
- 異步消息傳遞:業務邏輯可以隨事件同時發生。
通過圍繞 EDA 構建應用程序和系統,您可以用使其更具響應性的方式構建它們,因為通過設計,事件驅動系統更適用于不可預知且不斷更改的環境。
事件驅動設計和開發的優點
事件驅動的編程有許多優點。例如,此類編程可以:
- 減少開發和維護分布式系統的復雜性。
- 使得應用程序和服務的裝配和配置更加容易且成本更低。
- 促進源代碼和組件重用,從而減少 bug 并促進敏捷的開發和部署。
短期內,事件驅動的設計和開發允許更加快速、容易的定制。長期內,系統狀態更加精確。
|
EDA 和 SOA 的結合
與 順序式或過程式系統中客戶機必須輪詢更改請求不同,EDA 允許系統和組件在事件發生時實時動態地作出響應。EDA 通過引入長時間運行的處理功能來彌補 SOA 的不足。因為事件消費者在事件發生時接收事件,并且會調用松散耦合的服務來向客戶提供更及時更精確的數據,所以對業務有益。
在 EDA 內部,您可以跨 SOA 的各個分段(包括物理層和架構的虛擬層)傳輸事件,這樣系統可以有效地作出響應。圖 1 說明了跨架構堆棧各層傳播的事件。
圖 1. 跨虛擬層的傳播
正如您看到的,事件可以因應用程序、業務、組件、平臺或系統層的任何更改而發生,從技術觀點上講,業務事件的級別自然要比系統事件或組件事件高。
事件的原因(事件因果關系)是理解該事件的重要因素。事件因果關系可分為水平因果關系 和垂直因果關系。當事件發布者與事件接收者位于架構虛擬層中的同一層時,發生水平因果關系。當事件發布者與事件接收者位于不同層時,發生垂直因果關系。
|
EDA 和事件隊列
事件驅動的編程是圍繞事件生產者和事件消費者之間的無耦合關系的概念構造的。也就是說,事件消費者不關心事件發生的地點或原因;而是關注事件發生后它(消費者)將被調用。將事件生產者與事件消費者隔離開來的系統和應用程序通常依賴于事件分配器或通道。此通道包含事件隊列,用作事件生產者和事件處理程序之間的中間層。
圖 2 說明了生產者、消費者、事件通道和主題(或隊列)之間的關系。
圖 2. 事件隊列
事件隊列的角色是存儲從生產者接收的事件,并在每個消費者可用時將這些事件傳輸給消費者 —— 通常是為了事件被接收。
事件隊列和主題
多數事件驅動的系統依賴于預先構建的事件隊列技術,如面向消息的中間件(Message-Oriented Middleware,MOM)框架。MOM 是一種基于消息隊列的異步消息模型框架。
MOM 框架的主要優點是它能夠無限期地存儲消息,并在消費者準備接收消息時,將其路由到消費者。MOM 按照以下消息模型工作:
- 點對點:此模型基于稱為隊列 的消息庫,在該模型中,可以將消息從一個或多個生產者發送給單個消費者。
- 發布/訂閱:此模型基于稱為主題 的消息庫,在該模型中,可以將消息從一個或多個生產者發布給一個或多個已訂閱的消費者。
圖 3 說明了一個發布者、事件通道、主題和多個訂閱了給定消息類型的消費者之間的交互。
圖 3. 一個發布者、多個訂閱者、事件通道和主題之間的交互
Java 消息服務 (JMS) 框架是 Java 應用程序編程接口 (API) 在 MOM 模型上的抽象。
在 EDA 中使用 JMS
Java 技術為 Java 程序提供 JMS 作為一種普通方法,用于創建、發送、接收和讀取消息。JMS 是大多數消息傳遞系統中的常見概念和語義的接口和類抽象的框架。
通過 JMS 接口,消息生產者和消費者能夠以點對點或發布/訂閱模型發送和接收消息。下面的列表顯示了 JMS 中的主要組件:
- ConnectionFactory:該對象用于創建 JMS 連接
- Connection:這是到 JMS 系統的連接
- Destination:消息主題或消息隊列的抽象
- Session:發送或接收消息所在的上下文
- MessageProducer:會話創建的組件,用于將消息發送到目的地
- MessageConsumer:會話創建的組件,用于從目的地接收消息
|
使用 Geronimo 和 JMS 的簡單事件框架
Apache Geronimo 與 Active MQ 開放源碼消息提供程序綁定在一起。Active MQ 支持 JMS,因此為圍繞 Geronimo 框架構建的應用程序提供一種方法來以充分利用 JMS 的消息傳遞功能。
以 下各節定義了使用 Geronimo、Active MQ 和 JMS 的概念和語義構建的簡單事件框架。在這些小節中定義的事件框架包括事件通道、事件發布者和事件接收者。事件通道負責注冊和取消注冊事件接收者,并負責將事 件消息從事件發布者以匿名方式路由到事件接收者。此框架給出的惟一概念是事件通道功能,即根據事件對象實現的 Java 類或接口的類型,來過濾消息,并將消息路由到適當的接收者。
使用類/接口層次結構過濾并路由事件
典型的消息傳遞系統允許消息訂閱者根據點號分隔的字符串(如 travel.flights.seats 或 travel.lodging.rates )來定義將接收的事件類型。本文中給出的事件框架還允許訂閱者訂閱特定類型的事件;不過,該事件類型由 Java 類和接口的層次結構定義。
事件框架可以表示為點號分隔的消息類型層次結構,如 圖 4 所示的類層次結構。
圖 4. 事件應用程序類關系
根據此圖表,訂閱 Event 接口所代表的事件的事件接收者將接收所有事件,而訂閱 FlightEvent 接口所代表的事件的事件接收者將只接收基于該接口或 FlightDelayed 類或 SeatAvailable 類的事件。此設計允許事件接收者一次訂閱多個事件類型。例如,事件接收者通過使用參數 Event.class 調用事件通道的 subscribe() 方法,可以訂閱所有事件。如果添加新的事件類型,則事件接收者會在它們發布時自動接收它們。
事件通道通過確定事件接收者訂閱的事件接口的最具體的子類型,來處理事件層次結構。例如,清單 1 所示的 FlightDelayed 類實現了 FlightEvent 接口;因此,事件通道將首先查找 FlightDelayed 類的訂閱者,然后查找 FlightEvent 接口等,一直沿著類/接口層次結構向上。
清單 1. FlightDelayed 類
| class TravelEvent extends Event {} class FlightEvent extends TravelEvent {} class LodgingEvent extends TravelEvent {} public class FlightDelayed implements FlightEvent { private String message = ""; public FlightDelayed() { } public FlightDelayed(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } |
事件通道
事件通道 是事件發布者用于發布事件,事件接收者用于訂閱和接收事件的組件。簡單事件通道的接口如 清單 2 所示。
清單 2. 簡單事件通道的接口
| public interface Channel { void start(); void stop(); void publish(final Event event); void subscribe(final Receiver receiver, final Class eventClass); void unsubscribe(final Receiver receiver, final Class eventClass); } |
|
注意,subscribe() 和 unsubscribe() 方法需要一個 Class 類型的參數,事件通道使用該參數來確定接收者將訂閱或取消訂閱哪些類型的事件。
為避免事件接收者輪詢事件何時發生,可以通過 Receiver 接口的 receive() 方法調用事件接收者。每當將事件發布到事件通道時,都可以使用 Java 反射確定哪些訂閱者將接收該事件。然后在這些對象上調用 receive() 方法。 清單 3 顯示了一個簡單事件接收者。
清單 3. 一個簡單事件接收者的實現
| public class EventReceiver implements Receiver { private static final transient Log log = LogFactory.getLog(EventReceiver.class); private String id = ""; public EventReceiver() { } public EventReceiver(String id) { this.id = id; } public void setId(String id) { this.id = id; } public String getId() { return id; } public void receive(final Event event) { log.info("EventReceiver [" + id + "] received event [" + event.getMessage() + "]"); } } |
清單 4 顯示了事件通道的一個摘錄。
清單 4. 事件通道的實現
| public class EventChannel implements Channel { private static final String TOPIC_NAME = "java:comp/env/EventTopic"; private static final String MQ_URL = "tcp://localhost:61616"; private HashMap subscribers = new HashMap(); private TopicConnectionFactory factory = null; private Topic eventTopic = null; private TopicConnection topicConn = null; private TopicSession topicSess = null; private TopicSubscriber topicSubscriber = null; private TopicPublisher topicPublisher = null; private EventConsumer eventConsumer = null; private void handleEvent(Event event) { final Set received = new HashSet(); for (Class eventClass = event.getClass(); Event.class.isAssignableFrom(eventClass); eventClass = eventClass.getSuperclass()) { ArrayList receiverList = new ArrayList(); getReceiversForEvent(getEventLeafInterface(eventClass), receiverList); Receiver[] receivers = new Receiver[receiverList.size()]; receiverList.toArray(receivers); for (int i = 0; i < receivers.length; i++) { invokeOnce(received, receivers[i], event); } } } private void invokeOnce(Set received, Receiver receiver, Event event) { received.add(receiver); receiver.receive(event); } private Class getEventLeafInterface(Class cls) { Class retVal = null; if (Event.class.isAssignableFrom(cls)) { retVal = cls; if (cls.isInterface()) { return retVal; } } Class[] interfaces = cls.getInterfaces(); if (interfaces != null) { for (int i = 0; i < interfaces.length; i++) { if (Event.class.isAssignableFrom(interfaces[i])) { retVal = interfaces[i]; break; } retVal = getEventLeafInterface(interfaces[i]); } } return retVal; } public void start() { try { factory = new ActiveMQConnectionFactory(MQ_URL); topicConn = factory.createTopicConnection(); topicSess = topicConn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); eventTopic = topicSess.createTopic(TOPIC_NAME); topicSubscriber = topicSess.createSubscriber(eventTopic); topicPublisher = topicSess.createPublisher(eventTopic); eventConsumer = new EventConsumer(this); Thread consumerThread = new Thread(eventConsumer); consumerThread.setDaemon(false); consumerThread.start(); } catch (Exception e) { e.printStackTrace(); } } public void stop() { // close topic connections, sessions, consumers, etc. } public void publish(final Event event) { try { ObjectMessage eventMessage = topicSess.createObjectMessage(); eventMessage.setObject(event); topicPublisher.publish(eventMessage); } catch (Exception e) { e.printStackTrace(); } } public void subscribe(final Receiver receiver, final Class eventClass) { ArrayList receiverList = null; Class leafCls = getEventLeafInterface(eventClass); if (subscribers.get(leafCls) == null) { receiverList = new ArrayList(); subscribers.put(leafCls, receiverList); } else { receiverList = (ArrayList) subscribers.get(leafCls); } if (receiverList.indexOf(receiver) < 0) { receiverList.add(receiver); } } public void unsubscribe(final Receiver receiver, final Class eventClass) { Class leafCls = getEventLeafInterface(eventClass); if (subscribers.get(leafCls) != null) { ArrayList receiverList = (ArrayList) subscribers.get(leafCls); receiverList.remove(receiverList); } } } |
注意: EventChannel 類的完整源代碼可從本文末尾的 下載 部分通過下載獲得。
清單 5 顯示事件消費者的實現摘錄。
清單 5. 事件消費者的實現
| class EventConsumer implements Runnable, ExceptionListener { private boolean running = false; private boolean stopped = true; private EventChannel eventChannel = null; private EventConsumer(EventChannel eventChannel) { this.eventChannel = eventChannel; } public void run() { log.info("Event Consumer started"); // Create a Topic Connection, Session, and a MessageConsumer for the Topic // loop until stopped and distribute events to the event channel // using the handleEvent method eventChannel.handleEvent(event); stopped = true; log.info("Event Consumer stopped"); } public void shutdown() { running = false; while (stopped == false) { Thread.yield(); } } } |
注意: EventConsumer 類的完整源代碼可以從本文末尾的 下載 部分通過下載獲得。
在 Geronimo 中部署和運行事件框架
事件框架使用部署在 Geronimo 中的 Web 應用程序來測試每個事件類型。除了事件框架外,Web 應用程序還包括一個用于輸入事件消息的 HTML 表單和一個用于接收 HTTP 請求并將內容分派到事件通道的 servlet。
HTML 表單(如 圖 5 所示)只允許將三種類型的事件消息發送到分派 servlet。
圖 5. Web 應用程序的開始屏幕
事件分派 servlet 實例化事件通道對象和三個示例事件接收者。事件接收者然后訂閱給定的事件,servlet 將事件發布給事件通道對象。清單 6 顯示了該 servlet。
清單 6. 分派 servlet 的實現
| public class SenderServlet extends HttpServlet { private EventChannel eventChannel = null; private EventReceiver allTravelEventReceiver = null; private EventReceiver flightEventReceiver = null; private EventReceiver lodgingEventReceiver = null; public void init() throws ServletException { super.init(); eventChannel = new EventChannel(); eventChannel.start(); // create event receivers allTravelEventReceiver = new EventReceiver("allTravelEventReceiver"); flightEventReceiver = new EventReceiver("flightEventReceiver"); lodgingEventReceiver = new EventReceiver("lodgingEventReceiver"); // subscribe to all Travel events eventChannel.subscribe(allTravelEventReceiver, TravelEvent.class); // subscribe to Flight events eventChannel.subscribe(flightEventReceiver, FlightEvent.class); // subscribe to Lodging events eventChannel.subscribe(lodgingEventReceiver, LodgingEvent.class); } public void destroy() { super.destroy(); // unsubscribe all event receivers and stop the event channel } public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // respond with input form } public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { String txtMsg = req.getParameter("txtMsg"); if (txtMsg != null && txtMsg.length() > 0) { String flightDelayed = req.getParameter("FlightDelayed"); String rateIncreased = req.getParameter("RateIncreased"); String seatAvailable = req.getParameter("SeatAvailable"); if (flightDelayed != null) { // send a Flight event eventChannel.publish(new FlightDelayed(txtMsg)); } else if (rateIncreased != null) { // send a Lodging event eventChannel.publish(new RateIncreased(txtMsg)); } else if (seatAvailable != null) { // send a Flight event eventChannel.publish(new SeatAvailable(txtMsg)); } } doGet(req, res); } } |
注意: SenderServlet 類的完整源代碼可以從本文末尾的 下載 部分通過下載獲得。
事件分派 servlet 調用的事件框架的應用程序流程如 圖 6 所示。
圖 6. 事件 Web 應用程序的順序
部署應用程序
事件框架的類和 Web 應用程序打包在 .war 文件中,并放置在 GERONIMO_HOME/deploy 目錄下。對于創建并復制到 deploy 目錄下的 .war 文件,Geronimo 在啟動時會自動部署它。放置在 deploy 目錄下的應用程序是熱加載的,當發生更改時,Geronimo 能夠在運行時重新加載應用程序。這使調試應用程序變得非常便利。
運行應用程序
您可以使用位于 GERONIMO_HOME/bin 目錄下的啟動腳本(startup.bat 或 startup.sh)啟動 Geronimo 應用服務器。當調用 Geronimo 啟動腳本時,會出現 Geronimo 控制臺窗口。對于部署的事件框架的 Web 應用程序,啟動時出現的 Geronimo 控制臺窗口將包含類似于 清單 7 所示的行,確認 Web 應用程序已成功啟動。
清單 7. Web 應用程序的成功啟動
| 00:12:33,921 INFO [EventChannel] Starting EventChannel... 00:12:33,937 INFO [EventChannel] Creating topic connection... 00:12:35,062 INFO [EventChannel] EventChannel started 00:12:35,062 INFO [EventChannel] Event Consumer started 00:12:35,093 INFO [SenderServlet] AllTravelEventReceiver [com.jeffhanson.eda.EventReceiver@f84033] 00:12:35,093 INFO [SenderServlet] FlightEventReceiver [com.jeffhanson.eda.EventReceiver@3ee73b] 00:12:35,093 INFO [SenderServlet] LodgingEventReceiver [com.jeffhanson.eda.EventReceiver@16127f4] |
將事件發送給 servlet 后,servlet 會將它發布給事件通道。如果將包含文本 Flight 2365 to Detroit will be delayed 15 minutes 的 Flight-Delayed 消息發送給 servlet,則 Geronimo 控制臺窗口會顯示 類似于 清單 8 的信息。
清單 8. 成功的事件發布
| 00:12:53,718 INFO [SenderServlet] >>>>> 00:12:53,718 INFO [SenderServlet] >>>>> 00:12:53,734 INFO [EventChannel] Publishing event [com.jeffhanson.eda.events.business.FlightDelayed@863854] 00:12:53,859 INFO [EventReceiver] EventReceiver [flightEventReceiver] received event [Flight 2365 to Detroit will be delayed 15 minutes] 00:12:53,859 INFO [EventReceiver] EventReceiver [allTravelEventReceiver] received event [Flight 2365 to Detroit will be delayed 15 minutes] |
|
結束語
設 計能夠對實時更改和事件作出及時響應的有效事件驅動軟件系統是一項復雜工作。結合使用 SOA 與使用 Java 反射的有效事件驅動的交互框架可以減少復雜性,并增加靈活性。Geronimo 平臺提供了 API 和工具(包括 JMS 提供程序),可以用來構建功能強大的事件驅動的交互框架。
從線性企業編程轉移到面向服務的設計只能帶來適用于 SOA 模型的優勢。重構系統以獲得業務服務會導致服務和組件的模塊化框架。如果服務基礎設施的交互模型敏捷而可擴展,那么您可以將這些組件重用于多種不同的應用 程序。SOA、EDA 和 Apache Geronimo 為功能強大的有效軟件基礎設施提供了基礎。
來自 “ ITPUB博客 ” ,鏈接:http://blog.itpub.net/374079/viewspace-130290/,如需轉載,請注明出處,否則將追究法律責任。
轉載于:http://blog.itpub.net/374079/viewspace-130290/
總結
以上是生活随笔為你收集整理的[转载]使用 Apache Geronimo 和 JMS 构建事件驱动的框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软文写作是什么?如何写软文?软文标题怎样
- 下一篇: clickhouse SSB 性能测试