使用Spring JMS輕松實(shí)現(xiàn)異步消息傳遞(轉(zhuǎn))
Posted on 2007-03-08 18:07 毛里求斯的化石 閱讀(977) 評(píng)論(0) 編輯 收藏 所屬分類: jmsSpring框架則簡(jiǎn)化了使用JEE組件(包括JMS)的任務(wù)。它提供的模板機(jī)制隱藏了典型的JMS實(shí)現(xiàn)的細(xì)節(jié),這樣開發(fā)人員可以集中精力放在處理消息的實(shí)際工作中,而不用擔(dān)心如何去創(chuàng)建,訪問或清除JMS資源。
本文將對(duì)Spring JMS API作一個(gè)概述,并通過一個(gè)運(yùn)行在JBoss MQ服務(wù)器上的web例程來介紹如何使用Spring JMS API來異步處理(發(fā)送和接收)消息。我將通過傳統(tǒng)JMS實(shí)現(xiàn)和Spring JMS實(shí)現(xiàn)兩者間的比較,來展示使用Spring JMS處理消息是如何的簡(jiǎn)單和靈活。
異步消息傳遞和面向服務(wù)架構(gòu)
在現(xiàn)實(shí)中,大多數(shù)web請(qǐng)求都是同步處理的。例如,當(dāng)用戶要登入一個(gè)網(wǎng)站,首先輸入用戶名和密碼,然后服務(wù)器驗(yàn)證登錄合法性。如果驗(yàn)證成功,程序?qū)⒃试S該用戶進(jìn)入網(wǎng)站。這里,登錄請(qǐng)求在從客戶端接收以后被即時(shí)處理了。信用卡驗(yàn)證是另一個(gè)同步處理的例子;只有服務(wù)器證實(shí)輸入的信用卡號(hào)是有效的,同時(shí)客戶在帳戶上有足夠的存款,客戶才被允許繼續(xù)操作。但是讓我們思考一下在順序處理系統(tǒng)上的支付結(jié)算步驟。一旦系統(tǒng)證實(shí)該用戶信用卡的信息是準(zhǔn)確的,并且在帳戶上有足夠的資金,就不必等到所有的支付細(xì)節(jié)落實(shí)、轉(zhuǎn)賬完成。支付結(jié)算可以異步方式進(jìn)行,這樣客戶可以繼續(xù)進(jìn)行核查操作。
需要比典型同步請(qǐng)求耗費(fèi)更長(zhǎng)時(shí)間的請(qǐng)求,可以使用異步處理。另一個(gè)異步處理的例子是,在本地貸款處理程序中,提交至自動(dòng)承銷系統(tǒng)(AUS)的信用請(qǐng)求處理過程。當(dāng)借方提交貸款申請(qǐng)后,抵押公司會(huì)向AUS發(fā)送請(qǐng)求,以獲取信用歷史記錄。由于這個(gè)請(qǐng)求要求得到全面而又詳細(xì)的信用報(bào)告,包括借方現(xiàn)今和過去的帳戶,最近的付款和其他財(cái)務(wù)資料,服務(wù)器需要耗費(fèi)較長(zhǎng)的時(shí)間(幾小時(shí)或著有時(shí)甚至是幾天)來對(duì)這些請(qǐng)求作出響應(yīng)。客戶端程序(應(yīng)用)要與服務(wù)器連接并耗費(fèi)如此長(zhǎng)的時(shí)間來等待結(jié)果,這是毫無意義的。因此通信應(yīng)該是異步發(fā)生的;也就是,一旦請(qǐng)求被提交,它就被放置在隊(duì)列中,同時(shí)客戶端與服務(wù)器斷開連接。然后AUS服務(wù)從指定的隊(duì)列中選出請(qǐng)求進(jìn)行處理,并將處理得到的消息放置在另一個(gè)消息隊(duì)列里。最后,客戶端程序從這個(gè)隊(duì)列中選出處理結(jié)果,緊接著處理這個(gè)信用歷史數(shù)據(jù)。
JMS
如果您使用過JMS代碼,您會(huì)發(fā)現(xiàn)它與JDBC或JCA很像。它所包含的樣本代碼創(chuàng)建或JMS資源對(duì)象回溯,使得每一次您需要寫一個(gè)新類來發(fā)送和接收消息時(shí),都具有更好的代碼密集性和重復(fù)性。以下序列顯示了傳統(tǒng)JMS實(shí)現(xiàn)所包括的步驟:
- 創(chuàng)建JNDI初始上下文(context)。
- 從JNDI上下文獲取一個(gè)隊(duì)列連接工廠。
- 從隊(duì)列連接工廠中獲取一個(gè)Quene。
- 創(chuàng)建一個(gè)Session對(duì)象。
- 創(chuàng)建一個(gè)發(fā)送者(sender)或接收者(receiver)對(duì)象。
- 使用步驟5創(chuàng)建的發(fā)送者或接收者對(duì)象發(fā)送或接收消息。
- 處理完消息后,關(guān)閉所有JMS資源。
Spring JMS
Spring框架提供了一個(gè)模板機(jī)制來隱藏Java APIs的細(xì)節(jié)。JEE開發(fā)人員可以使用JDBCTemplate和JNDITemplate類來分別訪問后臺(tái)數(shù)據(jù)庫(kù)和JEE資源(數(shù)據(jù)源,連接池)。JMS也不例外。Spring提供JMSTemplate類,因此開發(fā)人員不用為一個(gè)JMS實(shí)現(xiàn)去編寫樣本代碼。接下來是在開發(fā)JMS應(yīng)用程序時(shí)Spring所具有一些的優(yōu)勢(shì)。
- 提供JMS抽象API,簡(jiǎn)化了訪問目標(biāo)(隊(duì)列或主題)和向指定目標(biāo)發(fā)布消息時(shí)JMS的使用。
- JEE開發(fā)人員不需要關(guān)心JMS不同版本(例如JMS 1.0.2與JMS 1.1)之間的差異。
- 開發(fā)人員不必專門處理JMS異常,因?yàn)镾pring為所有JMS異常提供了一個(gè)未經(jīng)檢查的異常,并在JMS代碼中重新拋出。
表1. Spring JMS類
類名 | 包 | 功能 |
---|---|---|
JmsException | org.springframework.jms | 只要發(fā)生一個(gè)JMS異常,Spring框架就會(huì)拋出異常,這個(gè)類是這些所拋出的異常的基(抽象)類。 |
JmsTemplate, JmsTemplate102 | org.springframework.jms.core | 這些是輔助類,用于簡(jiǎn)化JMS的使用,處理JMS資源(如連接工廠,目標(biāo)和發(fā)送者/接收者對(duì)象)的創(chuàng)建和釋放。JmsTemplate102是JmsTemplate的子類,使用JMS1.0.2規(guī)范 |
MessageCreator | org.springframework.jms.core | 這是JmsTemplate類使用的回叫接口,它為指定的會(huì)話創(chuàng)建JMS消息。 |
MessageConverter | org.springframework.jms.support.converter | 這個(gè)接口充當(dāng)一個(gè)抽象,用來在Java對(duì)象與JMS消息之間進(jìn)行轉(zhuǎn)換。 |
DestinationResolver | org.springframework.jms.support.destination | 這是JmsTemplate用來解析目標(biāo)名的接口。該接口的默認(rèn)實(shí)現(xiàn)是DynamicDestinationResolver和JndiDestinationResolve |
在接下來的部分,我將詳細(xì)解釋表1所列的一部分類(例如JmsTemplate,DestinationResolver和MessageConverter)。
JMSTemplate
JmsTemplate提供了幾種輔助方法,用來執(zhí)行一些基本操作。要開始使用JmsTemplate前,您需要知道JMS供應(yīng)商支持哪個(gè)JMS規(guī)范,JBoss AS 4.0.2和WebLogic 8.1服務(wù)器支持JMS 1.0.2規(guī)范。WebLogic Server 9.0包括了對(duì)JMS 1.1規(guī)范的支持。JMS 1.1統(tǒng)一了點(diǎn)對(duì)點(diǎn)(PTP)和發(fā)布/訂閱(Pub/Sub)域的編程接口。這種改變的結(jié)果就是,開發(fā)人員可以創(chuàng)建一個(gè)事務(wù)會(huì)話,然后在這同一個(gè)JMS會(huì)話里,可以從一個(gè)Queue(PTP)中接收消息,同時(shí)發(fā)送另一個(gè)消息到一個(gè)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)接收消息,但只會(huì)在指定的時(shí)間后傳遞消息。我們可以通過receiveTimeout屬性指定超時(shí)時(shí)間。 |
convertAndSend | 這個(gè)方法委托MessageConverter接口實(shí)例處理轉(zhuǎn)換過程,然后發(fā)送消息至指定的目標(biāo)。 |
receiveAndConvert | 從默認(rèn)或指定的目標(biāo)接收消息。并將消息轉(zhuǎn)換為Java對(duì)象。 |
目標(biāo)可以通過JNDI上下文保存和獲取。當(dāng)配置Spring程序上下文(application context)時(shí),我們可以用JndiObjectFactoryBean類取得對(duì)JMS的引用。DestinationResolver接口是用來把目標(biāo)名稱解析成JMS目標(biāo),當(dāng)應(yīng)用程序存在大量目標(biāo)時(shí),這是非常有用的。DynamicDestinationResolver(DestinationResolver的默認(rèn)實(shí)現(xiàn))是用來解析動(dòng)態(tài)目標(biāo)的。
MessageConverter接口定義了將Java對(duì)象轉(zhuǎn)換為JMS消息的約定。通過這個(gè)轉(zhuǎn)換器,應(yīng)用程序代碼可以集中于處理事務(wù)對(duì)象,而不用為對(duì)象如何表示為JMS消息這樣的內(nèi)部細(xì)節(jié)所困饒。SimpleMessageConverter(和SimpleMessageConverter102)是MessageConverter的默認(rèn)實(shí)現(xiàn)。可使用它們分別將String轉(zhuǎn)換為JMS TextMessage,字節(jié)數(shù)組(byte[])轉(zhuǎn)換為JMS BytesMessage,Map轉(zhuǎn)換為JMS MapMessage,和Serializable對(duì)象轉(zhuǎn)換為JMS ObjectMessage。您也可以編寫自定義的MessageConverter實(shí)例,通過XML綁定框架(例如JAXB, Castor,Commons Digester,XMLBeans或XStream),來實(shí)現(xiàn)XML文檔到TextMessage對(duì)象的轉(zhuǎn)換。
示例程序
我將用一個(gè)貸款申請(qǐng)?zhí)幚硐到y(tǒng)(命名為L(zhǎng)oanProc)示例來演示如何在JMS應(yīng)用程序中使用Spring。作為貸款申請(qǐng)的一部分,LoanProc通過發(fā)送貸款詳情(貸款I(lǐng)D,借方名字,借方的SSN,貸款期限和貸款數(shù)額),從AUS系統(tǒng)獲得信用歷史詳情。為了簡(jiǎn)便起見,我們基于兩個(gè)基本參數(shù)來表示信用歷史詳情:信用分?jǐn)?shù)(又名FICO得分)和貸款數(shù)額。讓我們假設(shè)處理信用檢查請(qǐng)求是按以下業(yè)務(wù)規(guī)則進(jìn)行的:
- 如果貸款數(shù)額等于或低于$500,000,借方必須至少有一個(gè)"好"的信用(也就是,借方的FICO得分在680到699之間)。
- 如果貸款數(shù)額高于$500,000,借方必須至少有"很好"的信用,意味著借方的信用得分要高于700。
貸款申請(qǐng)使用案例
信用請(qǐng)求處理使用案例包括以下幾個(gè)步驟:
- 用戶在貸款申請(qǐng)頁面輸入貸款詳情并提交貸款申請(qǐng)。
- 發(fā)送請(qǐng)求到一個(gè)名為CreditRequestSendQueue的消息隊(duì)列。然后程序發(fā)送貸款詳情到AUS系統(tǒng),獲取信用歷史詳情。
- AUS系統(tǒng)從隊(duì)列中挑出貸款詳情,并使用貸款參數(shù)從它的數(shù)據(jù)庫(kù)中獲取信用歷史信息。
- 然后AUS將找到的借方的信用歷史信息創(chuàng)建一個(gè)新的消息,發(fā)送到一個(gè)新的名為CreditRequestReceiveQueue的消息隊(duì)列。
- 最后,LoanProc從接收隊(duì)列中選出響應(yīng)消息,處理貸款申請(qǐng)來決定是否批準(zhǔn)或否決申請(qǐng)。
在這個(gè)例程中,兩個(gè)消息隊(duì)列都配置在同一個(gè)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資源
為了異步處理消息,首先我們需要消息隊(duì)列發(fā)送和接收消息。我們可以用Jboss里的配置XML文件創(chuàng)建一個(gè)新的消息隊(duì)列,然后使用JMS控制臺(tái)瀏覽隊(duì)列的詳細(xì)情況。清單1顯示了配置JMS的XML配置代碼片斷(這個(gè)應(yīng)該加入到j(luò)bossmq-destinations-service.xml文件,位于%JBOSS_HOME%\server\all\deploy-hasingleton\jm文件夾下。)
清單1.JBoss MQ Server上JMS隊(duì)列的配置
<!-- 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)在,讓我們看看如何使用一個(gè)名為Hermes的JMS工具來瀏覽消息隊(duì)列。Hermes是一個(gè)Java Swing應(yīng)用程序,它可以創(chuàng)建、管理和監(jiān)視JMS提供商(例如JBossMQ,WebSphereMQ,ActiveMQ和Arjuna服務(wù)器)里的JMS目標(biāo)。從它的網(wǎng)站上下載Hermes,解壓縮.zip文件到本地目錄(例如,c:\dev\tools\hermes)。一旦安裝完成,雙擊文件hermes.bat(位于bin文件夾下)啟動(dòng)程序。
要在Hermes里配置JBossMQ服務(wù)器,請(qǐng)參考Hermes網(wǎng)站上的這個(gè)演示。它有著出色的step-by-step可視化指示來配置JBoss MQ。當(dāng)配置一個(gè)新的JNDI初始上下文時(shí),請(qǐng)輸入下面的信息。
- 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)時(shí),請(qǐng)輸入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。圖2顯示了JMS控制臺(tái)的主窗口,其中有為JMS例程創(chuàng)建的新的消息隊(duì)列。
圖 2. Hermes中所有目標(biāo)的截圖.(單擊截圖來查看完整視圖)
下面的圖3顯示了在從消息發(fā)送者類發(fā)送消息到CreditRequestSendQueue后,Hermes JMS控制臺(tái)及消息隊(duì)列的截圖。您可以看見有5個(gè)消息在隊(duì)列中,控制臺(tái)顯示了消息詳情,例如消息ID,消息目標(biāo),時(shí)間戳和實(shí)際的消息內(nèi)容。
圖 3. Hermes中所有隊(duì)列的截圖.(單擊截圖來查看完整視圖)
在例程中使用的隊(duì)列名稱和其他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è)計(jì)模式里的設(shè)置方式注入原則(setter injection principle),用JMS對(duì)象實(shí)例類組配的。讓我們?cè)敿?xì)查看這些組件,并為每一個(gè)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>
接著,我們配置隊(duì)列連接工廠。清單3顯示了隊(duì)列連接工廠的配置。
清單3. JMS隊(duì)列連接工廠配置
<bean id="jmsQueueConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>UIL2ConnectionFactory</value> </property> </bean>
我們定義2個(gè)JMS目標(biāo)來發(fā)送和接收消息。詳情見清單4和5。
清單4. 發(fā)送隊(duì)列配置
<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. 接收隊(duì)列配置
<bean id="receiveDestination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>queue/CreditReqeustReceiveQueue</value> </property> </bean>
然后我們?cè)賮砼渲肑msTemplate組件。在例程中我們使用JmsTemplate102。同時(shí)使用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對(duì)象的配置。
清單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>
測(cè)試及監(jiān)視
我寫了一個(gè)測(cè)試類,命名為L(zhǎng)oanApplicationControllerTest,用來測(cè)試LoanProc程序。我們可以使用這個(gè)類來設(shè)定貸款參數(shù)以及調(diào)用信用請(qǐng)求服務(wù)類。
讓我們看一下不使用Spring JMS API而使用傳統(tǒng)JMS開發(fā)途徑的消息發(fā)送者實(shí)例。清單9顯示了MessageSenderJMS類里的sendMessage方法,其中包含了使用JMS API處理消息的所有必需步驟。
清單9. 傳統(tǒng)JMS實(shí)例
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ā)送者實(shí)例。清單10顯示了MessageSenderSpringJMS類中send方法的代碼。
清單10. 使用Spring API的JMS實(shí)例
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容器處理。我們只需引用一個(gè)JMSSender對(duì)象,然后調(diào)用對(duì)象里的sendMessage方法。
結(jié)束語
在本文中,我們看到Spring框架是如何使用JMS API簡(jiǎn)化異步消息傳遞。Spring去掉了所有使用JMS處理消息所必需的樣本代碼(例如得到一個(gè)隊(duì)列連接工廠,從Java代碼里創(chuàng)建隊(duì)列和會(huì)話對(duì)象,在運(yùn)行時(shí)使用配置文件對(duì)它們進(jìn)行組配)。我們可以動(dòng)態(tài)的交換JMS資源對(duì)象,而不必修改任何Java代碼,這要感謝Inversion of Control (IOC) 原則的力量。
既然異步消息傳遞是SOA框架的整體構(gòu)成部分,Spring很適合納入到SOA工具集。此外,JMS管理工具(如Hermes)使得創(chuàng)建、管理和監(jiān)督JMS資源變得容易,特別是對(duì)于系統(tǒng)管理員來說。