程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 使用Spring JMS輕松實現異步消息傳遞

使用Spring JMS輕松實現異步消息傳遞

編輯:關於JAVA

異步進程通信是面向服務架構(SOA)一個重要的組成部分,因為企業裡很多系統通信,特別是與外部組織間的通信,實質上都是異步的。Java消息服務(JMS)是用於編寫使用異步消息傳遞的JEE應用程序的API。傳統的使用JMS API進行消息傳遞的實現包括多個步驟,例如JNDI查詢隊列連接工廠和Queue資源,在實際發送和接收消息前創建一個JMS會話。

Spring框架則簡化了使用JEE組件(包括JMS)的任務。它提供的模板機制隱藏了典型的JMS實現的細節,這樣開發人員可以集中精力放在處理消息的實際工作中,而不用擔心如何去創建,訪問或清除JMS資源。

本文將對Spring JMS API作一個概述,並通過一個運行在JBoss MQ服務器上的web例程來介紹如何使用Spring JMS API來異步處理(發送和接收)消息。我將通過傳統JMS實現和Spring JMS實現兩者間的比較,來展示使用Spring JMS處理消息是如何的簡單和靈活。

異步消息傳遞和面向服務架構

在現實中,大多數web請求都是同步處理的。例如,當用戶要登入一個網站,首先輸入用戶名和密碼,然後服務器驗證登錄合法性。如果驗證成功,程序將允許該用戶進入網站。這裡,登錄請求在從客戶端接收以後被即時處理了。信用卡驗證是另一個同步處理的例子;只有服務器證實輸入的信用卡號是有效的,同時客戶在帳戶上有足夠的存款,客戶才被允許繼續操作。但是讓我們思考一下在順序處理系統上的支付結算步驟。一旦系統證實該用戶信用卡的信息是准確的,並且在帳戶上有足夠的資金,就不必等到所有的支付細節落實、轉賬完成。支付結算可以異步方式進行,這樣客戶可以繼續進行核查操作。

需要比典型同步請求耗費更長時間的請求,可以使用異步處理。另一個異步處理的例子是,在本地貸款處理程序中,提交至自動承銷系統(AUS)的信用請求處理過程。當借方提交貸款申請後,抵押公司會向AUS發送請求,以獲取信用歷史記錄。由於這個請求要求得到全面而又詳細的信用報告,包括借方現今和過去的帳戶,最近的付款和其他財務資料,服務器需要耗費較長的時間(幾小時或著有時甚至是幾天)來對這些請求作出響應。客戶端程序(應用)要與服務器連接並耗費如此長的時間來等待結果,這是毫無意義的。因此通信應該是異步發生的;也就是,一旦請求被提交,它就被放置在隊列中,同時客戶端與服務器斷開連接。然後AUS服務從指定的隊列中選出請求進行處理,並將處理得到的消息放置在另一個消息隊列裡。最後,客戶端程序從這個隊列中選出處理結果,緊接著處理這個信用歷史數據。

JMS

如果您使用過JMS代碼,您會發現它與JDBC或JCA很像。它所包含的樣本代碼創建或JMS資源對象回溯,使得每一次您需要寫一個新類來發送和接收消息時,都具有更好的代碼密集性和重復性。以下序列顯示了傳統JMS實現所包括的步驟:

創建JNDI初始上下文(context)。

從JNDI上下文獲取一個隊列連接工廠。

從隊列連接工廠中獲取一個Quene。

創建一個Session對象。

創建一個發送者(sender)或接收者(receiver)對象。

使用步驟5創建的發送者或接收者對象發送或接收消息。

處理完消息後,關閉所有JMS資源。

您可以看到,步驟6是處理消息的唯一地方。其他步驟都只是管理與實際業務要求無關的JMS資源,但是開發人員必須編寫並維護這些額外步驟的代碼。

Spring JMS

Spring框架提供了一個模板機制來隱藏Java APIs的細節。JEE開發人員可以使用JDBCTemplate和JNDITemplate類來分別訪問後台數據庫和JEE資源(數據源,連接池)。JMS也不例外。Spring提供JMSTemplate類,因此開發人員不用為一個JMS實現去編寫樣本代碼。接下來是在開發JMS應用程序時Spring所具有一些的優勢。

提供JMS抽象API,簡化了訪問目標(隊列或主題)和向指定目標發布消息時JMS的使用。

JEE開發人員不需要關心JMS不同版本(例如JMS 1.0.2與JMS 1.1)之間的差異。

開發人員不必專門處理JMS異常,因為Spring為所有JMS異常提供了一個未經檢查的異常,並在JMS代碼中重新拋出。

一旦您在JMS應用程序中開始使用Spring,您將會欣賞到它在處理異步消息傳遞上的簡便。Spring JMS框架提供多種Java類,可以輕松實現JMS應用。表1列出了這些類的一部分。

表1. Spring JMS類

在接下來的部分,我將詳細解釋表1所列的一部分類(例如JmsTemplate,DestinationResolver和MessageConverter)。

JMSTemplate

JmsTemplate提供了幾種輔助方法,用來執行一些基本操作。要開始使用JmsTemplate前,您需要知道JMS供應商支持哪個JMS規范,JBoss AS 4.0.2和WebLogic 8.1服務器支持JMS 1.0.2規范。WebLogic Server 9.0包括了對JMS 1.1規范的支持。JMS 1.1統一了點對點(PTP)和發布/訂閱(Pub/Sub)域的編程接口。這種改變的結果就是,開發人員可以創建一個事務會話,然後在這同一個JMS會話裡,可以從一個Queue(PTP)中接收消息,同時發送另一個消息到一個Topic(Pub/Sub)。JMS 1.1向後兼容JMS 1.0,應此根據JMS 1.0編寫的代碼仍可以適用於JMS 1.1。

JmsTemplate提供多種發送和接收消息的方法。表2列出了這些方法的一部分。

表2. JMS template方法

目標可以通過JNDI上下文保存和獲取。當配置Spring程序上下文(application context)時,我們可以用JndiObjectFactoryBean類取得對JMS的引用。DestinationResolver接口是用來把目標名稱解析成JMS目標,當應用程序存在大量目標時,這是非常有用的。DynamicDestinationResolver(DestinationResolver的默認實現)是用來解析動態目標的。

MessageConverter接口定義了將Java對象轉換為JMS消息的約定。通過這個轉換器,應用程序代碼可以集中於處理事務對象,而不用為對象如何表示為JMS消息這樣的內部細節所困饒。SimpleMessageConverter(和SimpleMessageConverter102)是MessageConverter的默認實現。可使用它們分別將String轉換為JMS TextMessage,字節數組(byte[])轉換為JMS BytesMessage,Map轉換為JMS MapMessage,和Serializable對象轉換為JMS ObjectMessage。您也可以編寫自定義的MessageConverter實例,通過XML綁定框架(例如JAXB, Castor,Commons Digester,XMLBeans或XStream),來實現XML文檔到TextMessage對象的轉換。

示例程序

我將用一個貸款申請處理系統(命名為LoanProc)示例來演示如何在JMS應用程序中使用Spring。作為貸款申請的一部分,LoanProc通過發送貸款詳情(貸款ID,借方名字,借方的SSN,貸款期限和貸款數額),從AUS系統獲得信用歷史詳情。為了簡便起見,我們基於兩個基本參數來表示信用歷史詳情:信用分數(又名FICO得分)和貸款數額。讓我們假設處理信用檢查請求是按以下業務規則進行的:

如果貸款數額等於或低於,000,借方必須至少有一個"好"的信用(也就是,借方的FICO得分在680到699之間)。

如果貸款數額高於,000,借方必須至少有"很好"的信用,意味著借方的信用得分要高於700。

貸款申請使用案例

信用請求處理使用案例包括以下幾個步驟:

用戶在貸款申請頁面輸入貸款詳情並提交貸款申請。

發送請求到一個名為CreditRequestSendQueue的消息隊列。然後程序發送貸款詳情到AUS系統,獲取信用歷史詳情。

AUS系統從隊列中挑出貸款詳情,並使用貸款參數從它的數據庫中獲取信用歷史信息。

然後AUS將找到的借方的信用歷史信息創建一個新的消息,發送到一個新的名為CreditRequestReceiveQueue的消息隊列。

最後,LoanProc從接收隊列中選出響應消息,處理貸款申請來決定是否批准或否決申請。

在這個例程中,兩個消息隊列都配置在同一個JBoss MQ server上。使用案例用圖1的序列圖(SequenceDiagram)表示

圖1.貸款處理程序的序列圖 (單擊截圖來查看完整視圖)

下面的表3顯示了在例程中我所使用的不同技術和開源框架,並按應用邏輯層排列。

表3. 在JMS應用程序中使用的框架

使用Hermes設置JMS資源

為了異步處理消息,首先我們需要消息隊列發送和接收消息。我們可以用Jboss裡的配置XML文件創 建一個新的消息隊列,然後使用JMS控制台浏覽隊列的詳細情況。清單1顯示了配置JMS的XML配置代碼片斷(這個應該加入到jbossmq- destinations-service.xml文件,位於%JBOSS_HOME%\server\all\deploy-hasingleton\ jm文件夾下。)

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

現在,讓我們看看如何使用一個名為Hermes的JMS工具來浏覽消息隊列。Hermes是一個Java Swing應用程序,它可以創建、管理和監視JMS提供商(例如JBossMQ,WebSphereMQ,ActiveMQ和Arjuna服務器)裡的JMS目標。從它的網站上下載Hermes,解壓縮.zip文件到本地目錄(例如,c:\dev\tools\hermes)。一旦安裝完成,雙擊文件hermes.bat(位於bin文件夾下)啟動程序。

要在Hermes裡配置JBossMQ服務器,請參考Hermes網站上的這個演示。它有著出色的step-by-step可視化指示來配置JBoss MQ。當配置一個新的JNDI初始上下文時,請輸入下面的信息。

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

當您創建新的目標時,請輸入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。圖2顯示了JMS控制台的主窗口,其中有為JMS例程創建的新的消息隊列。

圖 2. Hermes中所有目標的截圖.(單擊截圖來查看完整視圖)

下面的圖3顯示了在從消息發送者類發送消息到CreditRequestSendQueue後,Hermes JMS控制台及消息隊列的截圖。您可以看見有5個消息在隊列中,控制台顯示了消息詳情,例如消息ID,消息目標,時間戳和實際的消息內容。

圖 3. Hermes中所有隊列的截圖.(單擊截圖來查看完整視圖)

在例程中使用的隊列名稱和其他JMS和JNDI參數見表 4。

表4. Spring JMS配置參數

Spring配置

既然我們已經有了運行例程所需要的JMS目標,現在該了解用XML Spring配置文件(名為spring-jms.xml)來組配JMS組件的具體細節了。這些組件是根據Inversion of Controller (IOC)設計模式裡的設置方式注入原則(setter injection principle),用JMS對象實例類組配的。讓我們詳細查看這些組件,並為每一個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>

我們定義2個JMS目標來發送和接收消息。詳情見清單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目標。

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

最後我們配置發送者和接收者組件。清單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>

測試及監視

我寫了一個測試類,命名為LoanApplicationControllerTest,用來測試LoanProc程序。我們可以使用這個類來設定貸款參數以及調用信用請求服務類。

讓我們看一下不使用Spring JMS API而使用傳統JMS開發途徑的消息發送者實例。清單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 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) {}
}
}
}

現在,我們來看看使用了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代碼裡創建隊列和會話對象,在運行時使用配置文件對它們進行組配)。我們可以動態的交換JMS資源對象,而不必修改任何Java代碼,這要感謝Inversion of Control (IOC) 原則的力量。

既然異步消息傳遞是SOA框架的整體構成部分,Spring很適合納入到SOA工具集。此外,JMS管理工具(如Hermes)使得創建、管理和監督JMS資源變得容易,特別是對於系統管理員來說。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved