用Spring JMS使異步消息變得簡單

 
用Spring JMS使異步消息變得簡單
 
 

異步處理通信是面向服務架構(SOA)的重要部分,因爲企業中的許多系統通信,尤其是跟外部系統通信本來就是異步的。Java消息服務(JMS)就是用來編寫異步消息J2EE應用的API。使用JMS API的傳統消息實現涉及到象這樣的一些步驟:查找對列連接工廠、隊列資源以及在實際發送和接受消息前,創建JMS會話(JMS session)。

Spring framework簡化了用JEE組件(包括JMS)開發JMS應用的工作。它提供了一個模板機制來隱藏典型的JMS實現細節,所以開發者可以專注於消息處理任務而不用擔心怎樣創建、訪問和釋放JMS資源。

本文用一個運行在JBoss MQ server上的簡單Web應用概述了Spring JMS API和怎樣使用它異步處理(發送和接受)消息。我將對比JMS實現的傳統方法和Spring JMS實現方法,以顯示使用Spring JMS來處理消息是多麼的簡單和靈活。

異步消息和SOA

現實世界中,大多數Web請求是同步處理的。例如,當用戶登陸一個站點,他或她輸入用戶名和口令以及服務器識別登陸憑證。如果身份驗證成功,程序讓用戶進入站點。這裏,登陸請求從客戶端被接受後,立即被處理。信用卡授權也是一個同步處理的例子;僅當服務器覈實了發送進來的信用卡號是有效並且該客戶的帳號有足夠的信用額度後,才允許客戶繼續進行下一步動作。讓我們來考察一下訂單處理系統中的支付結算步驟。一旦系統覈實了那個用戶的信用卡信息是正確的,而且帳戶上有足夠的資金,那麼不需要等到支付細節和轉帳最終完成。支付結算用異步方式處理,如此客戶便可以繼續進行結帳處理。

與典型的同步請求相比,異步處理用於需要長時間來處理的請求。異步處理的另外一個例子是住房貸款處理應用中,處理提交到AUS(Automated Underwriting System)的貸款請求。貸款人提交貸款申請後,抵押公司發送請求到AUS取得信用歷史信息。因爲該請求要取得綜合詳細的信用報告如貸款人當前和過去的信用帳戶,最近的支付以及其它金融詳細信息,所以從請求到獲得響應常常需要很長時間。對客戶端程序來說開一個到服務器的連接並且長時間等待響應是沒有意義的。於是就有了異步通信;也就是,一旦請求被提交,它就被放入隊列裏面並且客戶斷開服務器連接。然後,AUS服務從特定隊列摘取請求,處理它,把結果消息放入另外一個消息隊列。最後客戶程序從消息隊列摘取響應結果繼續處理信用歷史結果信息。

JMS

如果用過JMS的話,會發現它類似寫JDBC或JCA代碼。它有創建或檢索JMS資源的樣板代碼,每當你需要編寫一個新類來發送或接受消息時,都得重複編寫那個樣本代碼。下面列出了傳統JMS實現涉及的步驟:

1、創建JNDI初始上下文context;
2、從JNDI上下文獲得隊列連接工廠;
3、從隊列連接工廠取得隊列Queue;
4、創建一個Session對象;
5、創建一個發送或接受對象;
6、利用第5部創建的發送或接受對象發送或接受消息;
7、處理完消息後,關閉所有JMS資源。

如你所見,只有第6步是處理消息的步驟。其他步驟都只是管理JMS資源,與實際業務需求無關,但開發者不得不編寫和維護那些附加步驟代碼。

Spring JMS

Spring框架提供一個模板機制來隱藏Java API細節。JEE開發者可用JDBCTemplate和JNDITemplate類來分別訪問後端數據庫和JEE資源(數據源,連接池)。JMS沒有異常。Spring提供了JMSTemplate類,所以開發者不必爲JMS實現編寫樣本代碼。當開發JMS應用時,Spring提供了一下一些優勢:

1、提供了一個JMS的抽象API,簡化了JMS的使用。如:訪問目的地(隊列或主體)和出版消息到特定目的地。
2、JEE開發者不必關心JMS不同版本之間的差異(如JMS 1.0.2 同 JMS 1.1);
3、開發者不必特定地處理JMS異常,因爲Spring爲JMS代碼拋出的任何JMS異常提供了一個unchecked異常。

一旦你在JMS應用中開始使用Spring,你將會欣賞到異步消息處理的簡易性。Spring JMS框架提供了各種java類使JMS開發變得簡單。

image

表1:Spring JMS類

隨後的部分,我將詳細解釋表1中的類(如JmsTemplate, DestinationResolver,和 MessageConverter)。

#p#

JMSTemplate

JmsTemplate提供了幾個helper方法來執行基本操作。開始使用JmsTemplate前,有必要知道JMS提供者支持哪種JMS規範。JBoss AS 4.0.2 和 WebLogic 8.1服務器支持JMS1.0.2規範。WebLogic 服務器 9.0包含JMS1.1支持。JMS1.1統一了PTP和Pub/Sub編程接口。有了這個改變,開發者可以創建一個事務會話,然後在同一個JMS事務中,從Queue(PTP)接受消息和發送一個消息到Topic(Pub/Sub)。JMS1.1向後兼容JMS1.0,因此基於JMS1.0編寫的代碼仍然能跟JMS1.1工作。

JmsTemplate提供各種方法來接收和發送消息。表2是方法列表。

image

表2:JMS模板方法

使用JNDI上下文存儲和檢索目的地。當配置Spring應用上下文時,我們用JndiObjectFactoryBean獲得JMS目的地引用。DestinationResolver用來解析目的地名稱到一個JMS目的地,當應用有許多目的地時,那是很有幫助的。DynamicDestinationResolver(缺省DestinationResolver實現)用於解析動態目的地。

MessageConverter接口定義了java對象和JMS消息之間轉換的契約。使用轉換器,應用代碼可以專注於業務對象,不用操心它是如何代表JMS消息的。SimpleMessageConverter(和SimpleMessageConverter102)是缺省MessageConverter實現。它們用於將String、字節數組((byte[])、Map、Serializable對象分別轉換成JMS TextMessage、JMS BytesMessage,JMS MapMessage,JMS ObjectMessage。你可以編寫MessageConverter接口的定製實現並結合XML綁定框架如JAXB, Castor, Commons Digester, XMLBeans, 或 XStream來轉換XML文檔到TextMessage。

樣本應用

我將用一個樣本貸款應用處理系統(叫LoanProc)來說明怎樣在JMS應用中使用Spring。作爲貸款處理的一部分,LoanProc發送貸款詳細資料(loan ID, borrower name, borrower's SSN, loan expiration date, and loan amount)從AUS系統請求信貸歷史。爲讓例子簡單一點,我們將基於兩個參數:信用評分和貸款數量來獲得信貸歷史詳細資料。讓我們假定處理信用檢查請求的業務規則如下:

1、如果貸款數量等於或小於$500,000,那麼貸款人必須至少有一個“good”信用(例如,貸款人的信用評分在680到699之間);
2、如果貸款數量超過$500,000,那麼貸款人必須至少要有一個“very good”信用,這意味他/她的信用評分超過700。

貸款應用Use Case

貸款請求處理Use Case由下列步驟組成:

1、用戶在貸款申請web頁面輸入貸款詳細資料並提交貸款申請;
2、然後程序發送貸款詳細資料到AUS系統取得信用歷史詳細資料。用發送請求到名叫CreditRequestSendQueue的消息隊列來完成。
3、AUS系統從隊列摘取貸款詳細資料並用貸款參數來從數據庫檢索信用歷史信息;
4、然後AUS系統用找到的貸款人信用歷史信息創建一個新的消息併發送到名叫CreditRequestReceiveQueue的消息隊列;
5、最後LoanProc從接收消息隊列摘取響應消息並處理貸款申請,決定申請是被覈准還是拒絕。

應用中,在同樣的JBoss MQ server中配置了兩個消息隊列。Use Case用序列圖1表示如下:

image

圖1:貸款處理應用的序列圖

技術

表3列出了例子應用中用到的一些技術和開源框架

image

表3:JMS應用中用到的框架

#p#

使用Hermes 的JMS資源設置

爲異步處理消息,首先,我們需要消息隊列來發送和接收消息。我們在JBoss中使用xml配置文件創建消息隊列並且用JMS控制檯來瀏覽隊列詳細資料。清單1顯示了XML配置文件JMS配置片斷。(這個片斷可以被添加到%JBOSS_HOME%\server\all\deploy-hasingleton\jms目錄下的jbossmq-destinations-service.xml文件中。)

清單1:JBoss MQ服務器中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>  

現在,讓我們看看怎麼使用一個叫做Hermes的JMS工具瀏覽消息隊列。Hermes是一個Java Swing應用,它能創建、管理和監控JMS提供者中的JMS destination。這樣的JMS提供者有JBossMQ, WebSphereMQ, ActiveMQ 和 Arjuna。從website下載Hermes並解壓zip文件到一個本地目錄(如c:\dev\tools\hermes)。一旦安裝,雙擊hermes.bat (在bin目錄)啓動程序。

爲在Hermes中配置JBossMQ服務器,參考Hermes站點上的demo。它一步一步形象說明了JBoss MQ的配置。

當配置一個新的JNDI初始上下文時,輸入下面的配置信息。

providerURL = jnp://localhost:1099 initialContextFactory = org.jnp.interfaces.NamingContextFactory urlPkgPrefixes = org.jnp.interfaces:org.jboss.naming securityCredentials = admin securityPrincipal = admin

當創建一個新destinations時,輸入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。圖2顯示了JMS控制檯主屏幕,它顯示了爲樣本JMS應用創建的新消息。

image

圖2:Hermes中所有destinations的截屏

圖3是消息發送者發送一些消息到CreditRequestSendQueue後,Hermes JMS控制檯顯示的消息隊列詳細資料。你能看到這裏隊列中有5個消息並且控制檯顯示了消息的詳細信息如message ID、message destination、time stamp、和實際的消息。

image

圖3:Hermes中隊列詳細資料截屏

使用Spring JMS,異步消息變得簡單

用在樣本應用中的這些消息隊列名和其它JMS以及JNDI參數如下表4所示。

image

表4:Spring JMS配置參數

#p#

Spring配置

已經有了運行樣本應用所需的JMS destinations,現在該是進入用XML Spring配置文件(叫做spring-jms.xml)裝配JMS組件的細節的時候了。用IOC設計模式中的setter依賴注入原理裝入這些組件。讓我們仔細看看組件,爲每一個JMS組件顯示了一個XML配置片斷。

JNDI上下文是獲取JMS資源的入口,所以我們首先配置一個JNDI模板。清單2顯示了一名爲jndiTemplate的Spring bean,它具有取得JNDI初始上下文必須的常用參數。

清單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> 

我們定義了兩個JMS destinations來發送和接收消息。清單4和清單5顯示了這些細節。

清單4:發送隊列配置

<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 destination。

清單6:JMS template配置

<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>

最後,我們配置發送和接收者組件。清單7和清單8顯示了Sender 和Receiver對象配置。

清單7:JMS Sender配置

Listing 7. JMS Sender configuration <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>

#p#

測試和監控

我借了一個叫做LoanApplicationControllerTest的測試類來測試LoanProc應用。我們使用這個類來設置貸款參數和調用那個信用請求服務類。

讓我們看看使用傳統JMS而不用Spring JMS API的消息發送者實現。清單9顯示了MessageSenderJMS這個類的sendMessage方法,這個類具備使用JMS API處理消息的所有必須步驟。

清單9:傳統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 connction.    */     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) {}         }     } }

現在,讓我們看看用Spring實現的消息發送者。清單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資源相關的資源步驟都由Spring容器用配置文件處理。我們僅需要獲取JMSSender引用並且調用它上面的sendMessage即可。

結論

本文中,我們看到Spring框架如何簡化了使用JMS API的異步消息應用開發的工作。Spring移走了所有JMS消息處理必須的樣板代碼,如獲取連接工廠,從Java代碼創建隊列和會話對象並在運行時用配置文件裝配它們。由於這個強大的IOC原理,我們可以不必修改Java代碼便可動態交換JMS資源對象。

因爲異步消息是構成SOA框架整體所需的一部分,Spring非常適合放入SOA工具集。同樣,JMS管理工具如Hermes使得創建,管理和控制JMS資源變得簡單,尤其對系統管理員。

(責任編輯 火鳳凰 [email protected]  TEL:(010)68476636-8007)

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章