使用Spring JMS輕松實現(xiàn)異步消息傳遞
Spring框架則簡化了使用JEE組件(包括JMS)的任務(wù)。它提供的模板機(jī)制隱藏了典型的JMS實現(xiàn)的細(xì)節(jié),這樣開發(fā)人員可以集中精力放在處理消息的實際工作中,而不用擔(dān)心如何去創(chuàng)建,訪問或清除JMS資源。
本文將對Spring JMS API作一個概述,并通過一個運(yùn)行在JBoss MQ服務(wù)器上的web例程來介紹如何使用Spring JMS API來異步處理(發(fā)送和接收)消息。我將通過傳統(tǒng)JMS實現(xiàn)和Spring JMS實現(xiàn)兩者間的比較,來展示使用Spring JMS處理消息是如何的簡單和靈活。
異步消息傳遞和面向服務(wù)架構(gòu)
在現(xiàn)實中,大多數(shù)web請求都是同步處理的。例如,當(dāng)用戶要登入一個網(wǎng)站,首先輸入用戶名和密碼,然后服務(wù)器驗證登錄合法性。如果驗證成功,程序?qū)⒃试S該用戶進(jìn)入網(wǎng)站。這里,登錄請求在從客戶端接收以后被即時處理了。信用卡驗證是另一個同步處理的例子;只有服務(wù)器證實輸入的信用卡號是有效的,同時客戶在帳戶上有足夠的存款,客戶才被允許繼續(xù)操作。但是讓我們思考一下在順序處理系統(tǒng)上的支付結(jié)算步驟。一旦系統(tǒng)證實該用戶信用卡的信息是準(zhǔn)確的,并且在帳戶上有足夠的資金,就不必等到所有的支付細(xì)節(jié)落實、轉(zhuǎn)賬完成。支付結(jié)算可以異步方式進(jìn)行,這樣客戶可以繼續(xù)進(jìn)行核查操作。
需要比典型同步請求耗費(fèi)更長時間的請求,可以使用異步處理。另一個異步處理的例子是,在本地貸款處理程序中,提交至自動承銷系統(tǒng)(AUS)的信用請求處理過程。當(dāng)借方提交貸款申請后,抵押公司會向AUS發(fā)送請求,以獲取信用歷史記錄。由于這個請求要求得到全面而又詳細(xì)的信用報告,包括借方現(xiàn)今和過去的帳戶,最近的付款和其他財務(wù)資料,服務(wù)器需要耗費(fèi)較長的時間(幾小時或著有時甚至是幾天)來對這些請求作出響應(yīng)??蛻舳顺绦颍☉?yīng)用)要與服務(wù)器連接并耗費(fèi)如此長的時間來等待結(jié)果,這是毫無意義的。因此通信應(yīng)該是異步發(fā)生的;也就是,一旦請求被提交,它就被放置在隊列中,同時客戶端與服務(wù)器斷開連接。然后AUS服務(wù)從指定的隊列中選出請求進(jìn)行處理,并將處理得到的消息放置在另一個消息隊列里。最后,客戶端程序從這個隊列中選出處理結(jié)果,緊接著處理這個信用歷史數(shù)據(jù)。
JMS
如果您使用過JMS代碼,您會發(fā)現(xiàn)它與JDBC或JCA很像。它所包含的樣本代碼創(chuàng)建或JMS資源對象回溯,使得每一次您需要寫一個新類來發(fā)送和接收消息時,都具有更好的代碼密集性和重復(fù)性。以下序列顯示了傳統(tǒng)JMS實現(xiàn)所包括的步驟:
- 創(chuàng)建JNDI初始上下文(context)。
- 從JNDI上下文獲取一個隊列連接工廠。
- 從隊列連接工廠中獲取一個Quene。
- 創(chuàng)建一個Session對象。
- 創(chuàng)建一個發(fā)送者(sender)或接收者(receiver)對象。
- 使用步驟5創(chuàng)建的發(fā)送者或接收者對象發(fā)送或接收消息。
- 處理完消息后,關(guān)閉所有JMS資源。
?
Spring JMS
Spring框架提供了一個模板機(jī)制來隱藏Java APIs的細(xì)節(jié)。JEE開發(fā)人員可以使用JDBCTemplate和JNDITemplate類來分別訪問后臺數(shù)據(jù)庫和JEE資源(數(shù)據(jù)源,連接池)。JMS也不例外。Spring提供JMSTemplate類,因此開發(fā)人員不用為一個JMS實現(xiàn)去編寫樣本代碼。接下來是在開發(fā)JMS應(yīng)用程序時Spring所具有一些的優(yōu)勢。
- 提供JMS抽象API,簡化了訪問目標(biāo)(隊列或主題)和向指定目標(biāo)發(fā)布消息時JMS的使用。
- JEE開發(fā)人員不需要關(guān)心JMS不同版本(例如JMS 1.0.2與JMS 1.1)之間的差異。
- 開發(fā)人員不必專門處理JMS異常,因為Spring為所有JMS異常提供了一個未經(jīng)檢查的異常,并在JMS代碼中重新拋出。
?
表1. Spring JMS類
類名 | 包 | 功能 |
---|---|---|
JmsException | org.springframework.jms | 只要發(fā)生一個JMS異常,Spring框架就會拋出異常,這個類是這些所拋出的異常的基(抽象)類。 |
JmsTemplate, JmsTemplate102 | org.springframework.jms.core | 這些是輔助類,用于簡化JMS的使用,處理JMS資源(如連接工廠,目標(biāo)和發(fā)送者/接收者對象)的創(chuàng)建和釋放。JmsTemplate102是JmsTemplate的子類,使用JMS1.0.2規(guī)范 |
MessageCreator | org.springframework.jms.core | 這是JmsTemplate類使用的回叫接口,它為指定的會話創(chuàng)建JMS消息。 |
MessageConverter | org.springframework.jms.support.converter | 這個接口充當(dāng)一個抽象,用來在Java對象與JMS消息之間進(jìn)行轉(zhuǎn)換。 |
DestinationResolver | org.springframework.jms.support.destination | 這是JmsTemplate用來解析目標(biāo)名的接口。該接口的默認(rèn)實現(xiàn)是DynamicDestinationResolver和JndiDestinationResolve |
在接下來的部分,我將詳細(xì)解釋表1所列的一部分類(例如JmsTemplate,DestinationResolver和MessageConverter)。
JMSTemplate
JmsTemplate提供了幾種輔助方法,用來執(zhí)行一些基本操作。要開始使用JmsTemplate前,您需要知道JMS供應(yīng)商支持哪個JMS規(guī)范,JBoss AS 4.0.2和WebLogic 8.1服務(wù)器支持JMS 1.0.2規(guī)范。WebLogic Server 9.0包括了對JMS 1.1規(guī)范的支持。JMS 1.1統(tǒng)一了點(diǎn)對點(diǎn)(PTP)和發(fā)布/訂閱(Pub/Sub)域的編程接口。這種改變的結(jié)果就是,開發(fā)人員可以創(chuàng)建一個事務(wù)會話,然后在這同一個JMS會話里,可以從一個Queue(PTP)中接收消息,同時發(fā)送另一個消息到一個Topic(Pub/Sub)。JMS 1.1向后兼容JMS 1.0,應(yīng)此根據(jù)JMS 1.0編寫的代碼仍可以適用于JMS 1.1。
JmsTemplate提供多種發(fā)送和接收消息的方法。表2列出了這些方法的一部分。
表2. JMS template方法
方法名稱 | 功能 |
---|---|
send | 發(fā)送消息至默認(rèn)或指定的目標(biāo)。JmsTemplate包含send方法,它通過javax.jms.Destination或JNDI查詢來指定目標(biāo)。 |
receive | 從默認(rèn)或指定的目標(biāo)接收消息,但只會在指定的時間后傳遞消息。我們可以通過receiveTimeout屬性指定超時時間。 |
convertAndSend | 這個方法委托MessageConverter接口實例處理轉(zhuǎn)換過程,然后發(fā)送消息至指定的目標(biāo)。 |
receiveAndConvert | 從默認(rèn)或指定的目標(biāo)接收消息。并將消息轉(zhuǎn)換為Java對象。 |
目標(biāo)可以通過JNDI上下文保存和獲取。當(dāng)配置Spring程序上下文(application context)時,我們可以用JndiObjectFactoryBean類取得對JMS的引用。DestinationResolver接口是用來把目標(biāo)名稱解析成JMS目標(biāo),當(dāng)應(yīng)用程序存在大量目標(biāo)時,這是非常有用的。DynamicDestinationResolver(DestinationResolver的默認(rèn)實現(xiàn))是用來解析動態(tài)目標(biāo)的。
MessageConverter接口定義了將Java對象轉(zhuǎn)換為JMS消息的約定。通過這個轉(zhuǎn)換器,應(yīng)用程序代碼可以集中于處理事務(wù)對象,而不用為對象如何表示為JMS消息這樣的內(nèi)部細(xì)節(jié)所困饒。SimpleMessageConverter(和SimpleMessageConverter102)是MessageConverter的默認(rèn)實現(xiàn)??墒褂盟鼈兎謩e將String轉(zhuǎn)換為JMS TextMessage,字節(jié)數(shù)組(byte[])轉(zhuǎn)換為JMS BytesMessage,Map轉(zhuǎn)換為JMS MapMessage,和Serializable對象轉(zhuǎn)換為JMS ObjectMessage。您也可以編寫自定義的MessageConverter實例,通過XML綁定框架(例如JAXB, Castor,Commons Digester,XMLBeans或XStream),來實現(xiàn)XML文檔到TextMessage對象的轉(zhuǎn)換。
示例程序
我將用一個貸款申請?zhí)幚硐到y(tǒng)(命名為LoanProc)示例來演示如何在JMS應(yīng)用程序中使用Spring。作為貸款申請的一部分,LoanProc通過發(fā)送貸款詳情(貸款I(lǐng)D,借方名字,借方的SSN,貸款期限和貸款數(shù)額),從AUS系統(tǒng)獲得信用歷史詳情。為了簡便起見,我們基于兩個基本參數(shù)來表示信用歷史詳情:信用分?jǐn)?shù)(又名FICO得分)和貸款數(shù)額。讓我們假設(shè)處理信用檢查請求是按以下業(yè)務(wù)規(guī)則進(jìn)行的:
- 如果貸款數(shù)額等于或低于,000,借方必須至少有一個"好"的信用(也就是,借方的FICO得分在680到699之間)。
- 如果貸款數(shù)額高于,000,借方必須至少有"很好"的信用,意味著借方的信用得分要高于700。
貸款申請使用案例
信用請求處理使用案例包括以下幾個步驟:
- 用戶在貸款申請頁面輸入貸款詳情并提交貸款申請。
- 發(fā)送請求到一個名為CreditRequestSendQueue的消息隊列。然后程序發(fā)送貸款詳情到AUS系統(tǒng),獲取信用歷史詳情。
- AUS系統(tǒng)從隊列中挑出貸款詳情,并使用貸款參數(shù)從它的數(shù)據(jù)庫中獲取信用歷史信息。
- 然后AUS將找到的借方的信用歷史信息創(chuàng)建一個新的消息,發(fā)送到一個新的名為CreditRequestReceiveQueue的消息隊列。
- 最后,LoanProc從接收隊列中選出響應(yīng)消息,處理貸款申請來決定是否批準(zhǔn)或否決申請。
在這個例程中,兩個消息隊列都配置在同一個JBoss MQ server上。使用案例用圖1的序列圖(SequenceDiagram)表示
下面的表3顯示了在例程中我所使用的不同技術(shù)和開源框架,并按應(yīng)用邏輯層排列。
表3. 在JMS應(yīng)用程序中使用的框架
邏輯層 | 技術(shù)/框架 |
---|---|
MVC | Spring MVC |
Service | Spring Framework (version 2.1) |
JMS API | Spring JMS |
JMS Provider | JBoss MQ (version 4.0.2) |
JMS Console | Hermes |
IDE | Eclipse 3.1 |
使用Hermes設(shè)置JMS資源
為了異步處理消息,首先我們需要消息隊列發(fā)送和接收消息。我們可以用Jboss里的配置XML文件創(chuàng)建一個新的消息隊列,然后使用JMS控制臺瀏覽隊列的詳細(xì)情況。清單1顯示了配置JMS的XML配置代碼片斷(這個應(yīng)該加入到j(luò)bossmq-destinations-service.xml文件,位于%JBOSS_HOME%server lldeploy-hasingletonjm文件夾下。)
清單1.JBoss MQ Server上JMS隊列的配置
<!-- Credit Request Send Queue --> <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=CreditRequestSendQueue"> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean> <!-- Credit Request Receive Queue --> <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=CreditRequestReceiveQueue"> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean>
現(xiàn)在,讓我們看看如何使用一個名為Hermes的JMS工具來瀏覽消息隊列。Hermes是一個Java Swing應(yīng)用程序,它可以創(chuàng)建、管理和監(jiān)視JMS提供商(例如JBossMQ,WebSphereMQ,ActiveMQ和Arjuna服務(wù)器)里的JMS目標(biāo)。從它的網(wǎng)站上下載Hermes,解壓縮.zip文件到本地目錄(例如,c:dev oolshermes)。一旦安裝完成,雙擊文件hermes.bat(位于bin文件夾下)啟動程序。
要在Hermes里配置JBossMQ服務(wù)器,請參考Hermes網(wǎng)站上的這個演示。它有著出色的step-by-step可視化指示來配置JBoss MQ。當(dāng)配置一個新的JNDI初始上下文時,請輸入下面的信息。
- providerURL = jnp://localhost:1099
- initialContextFactory = org.jnp.interfaces.NamingContextFactory
- urlPkgPrefixes = org.jnp.interfaces:org.jboss.naming
- securityCredentials = admin
- securityPrincipal = admin
當(dāng)您創(chuàng)建新的目標(biāo)時,請輸入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。圖2顯示了JMS控制臺的主窗口,其中有為JMS例程創(chuàng)建的新的消息隊列。
圖 2. Hermes中所有目標(biāo)的截圖.(單擊截圖來查看完整視圖)
下面的圖3顯示了在從消息發(fā)送者類發(fā)送消息到CreditRequestSendQueue后,Hermes JMS控制臺及消息隊列的截圖。您可以看見有5個消息在隊列中,控制臺顯示了消息詳情,例如消息ID,消息目標(biāo),時間戳和實際的消息內(nèi)容。
圖 3. Hermes中所有隊列的截圖.(單擊截圖來查看完整視圖)
在例程中使用的隊列名稱和其他JMS和JNDI參數(shù)見表 4。
表4. Spring JMS配置參數(shù)
參數(shù)名稱 | 參數(shù)值 |
---|---|
Initial Context Factory | org.jnp.interfaces.NamingContextFactory |
Provider URL | localhost:8080 |
Initial Context Factory URL Packages | org.jnp.interfaces:org.jboss.naming |
Queue Connection Factory | UIL2ConnectionFactory |
Queue Name | queue/CreditRequestSendQueue, queue/CreditRequestReceiveQueue |
Spring配置
既然我們已經(jīng)有了運(yùn)行例程所需要的JMS目標(biāo),現(xiàn)在該了解用XML Spring配置文件(名為spring-jms.xml)來組配JMS組件的具體細(xì)節(jié)了。這些組件是根據(jù)Inversion of Controller (IOC)設(shè)計模式里的設(shè)置方式注入原則(setter injection principle),用JMS對象實例類組配的。讓我們詳細(xì)查看這些組件,并為每一個JMS組件演示一段XML配置代碼。
JNDI上下文是取得JMS資源的起始位置,因此首先我們要配置JNDI模板。清單2顯示了名為jndiTemplate的Spring bean,其中列有JNDI初始上下文所必需的常用參數(shù)。
清單2. JNDI上下文模板
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"> <property name="environment"> <props> <prop key="java.naming.factory.initial"> org.jnp.interfaces.NamingContextFactory </prop> <prop key="java.naming.provider.url"> localhost </prop> <prop key="java.naming.factory.url.pkgs"> org.jnp.interfaces:org.jboss.naming </prop> </props> </property> </bean>
接著,我們配置隊列連接工廠。清單3顯示了隊列連接工廠的配置。
清單3. JMS隊列連接工廠配置
<bean id="jmsQueueConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>UIL2ConnectionFactory</value> </property> </bean>
我們定義2個JMS目標(biāo)來發(fā)送和接收消息。詳情見清單4和5。
清單4. 發(fā)送隊列配置
<bean id="sendDestination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>queue/CreditRequestSendQueue</value> </property> </bean>
清單5. 接收隊列配置
<bean id="receiveDestination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>queue/CreditReqeustReceiveQueue</value> </property> </bean>
然后我們再來配置JmsTemplate組件。在例程中我們使用JmsTemplate102。同時使用defaultDestination屬性來指定JMS目標(biāo)。
清單6. JMS模板配置
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate102"> <property name="connectionFactory"> <ref bean="jmsQueueConnectionFactory"/> </property> <property name="defaultDestination"> <ref bean="destination"/> </property> <property name="receiveTimeout"> <value>30000</value> </property> </bean>
最后我們配置發(fā)送者和接收者組件。清單7和8分別是Sender 和 Receiver對象的配置。
清單7. JMS Sender配置
<bean id="jmsSender" class="springexample.client.JMSSender"> <property name="jmsTemplate"> <ref bean="jmsTemplate"/> </property> </bean>
清單8. JMS Receiver配置
<bean id="jmsReceiver" class="springexample.client.JMSReceiver"> <property name="jmsTemplate"> <ref bean="jmsTemplate"/> </property> </bean>
測試及監(jiān)視
我寫了一個測試類,命名為LoanApplicationControllerTest,用來測試LoanProc程序。我們可以使用這個類來設(shè)定貸款參數(shù)以及調(diào)用信用請求服務(wù)類。
讓我們看一下不使用Spring JMS API而使用傳統(tǒng)JMS開發(fā)途徑的消息發(fā)送者實例。清單9顯示了MessageSenderJMS類里的sendMessage方法,其中包含了使用JMS API處理消息的所有必需步驟。
清單9. 傳統(tǒng)JMS實例
public void sendMessage() { queueName = "queue/CreditRequestSendQueue"; System.out.println("Queue name is " + queueName); /* * Create JNDI Initial Context */ try { Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); env.put("java.naming.provider.url","localhost"); env.put("java.naming.factory.url.pkgs", "org.jnp.interfaces:org.jboss.naming"); jndiContext = new InitialContext(env); } catch (NamingException e) { System.out.println("Could not create JNDI API " + "context: " + e.toString()); } /* * Get queue connection factory and queue objects from JNDI context. */ try { queueConnectionFactory = (QueueConnectionFactory) jndiContext.lookup("UIL2ConnectionFactory"); queue = (Queue) jndiContext.lookup(queueName); } catch (NamingException e) { System.out.println("JNDI API lookup failed: " + e.toString()); } /* * Create connection, session, sender objects. * Send the message. * Cleanup JMS connection. */ try { queueConnection = queueConnectionFactory.createQueueConnection(); queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); queueSender = queueSession.createSender(queue); message = queueSession.createTextMessage(); message.setText("This is a sample JMS message."); System.out.println("Sending message: " + message.getText()); queueSender.send(message); } catch (JMSException e) { System.out.println("Exception occurred: " + e.toString()); } finally { if (queueConnection != null) { try { queueConnection.close(); } catch (JMSException e) {} } } }
現(xiàn)在,我們來看看使用了Spring的消息發(fā)送者實例。清單10顯示了MessageSenderSpringJMS類中send方法的代碼。
清單10. 使用Spring API的JMS實例
public void send() { try { ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] { "spring-jms.xml"}); System.out.println("Classpath loaded"); JMSSender jmsSender = (JMSSender)appContext.getBean("jmsSender"); jmsSender.sendMesage(); System.out.println("Message sent using Spring JMS."); } catch(Exception e) { e.printStackTrace(); } }
如您所見,通過使用配置文件,所有與管理JMS資源有關(guān)的步驟都將交由Spring容器處理。我們只需引用一個JMSSender對象,然后調(diào)用對象里的sendMessage方法。
結(jié)束語
在本文中,我們看到Spring框架是如何使用JMS API簡化異步消息傳遞。Spring去掉了所有使用JMS處理消息所必需的樣本代碼(例如得到一個隊列連接工廠,從Java代碼里創(chuàng)建隊列和會話對象,在運(yùn)行時使用配置文件對它們進(jìn)行組配)。我們可以動態(tài)的交換JMS資源對象,而不必修改任何Java代碼,這要感謝Inversion of Control (IOC) 原則的力量。
既然異步消息傳遞是SOA框架的整體構(gòu)成部分,Spring很適合納入到SOA工具集。此外,JMS管理工具(如Hermes)使得創(chuàng)建、管理和監(jiān)督JMS資源變得容易,特別是對于系統(tǒng)管理員來說。
參考資料
原文出處:http://www.onjava.com/pub/a/onjava/2006/02/22/asynchronous-messaging-with-spring-jms.html
posted on 2006-12-03 10:11 BPM 閱讀(836) 評論(0) 編輯 收藏 所屬分類: JMS