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

 


 

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

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

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

異步消息傳遞和麪向服務架構數據庫

  在現實中,大多數web請求都是同步處理的。例如,當用戶要登入一個網站,首先輸入用戶名和密碼,而後服務器驗證登陸合法性。若是驗證成功,程序將容許該用戶進入網站。這裏,登陸請求在從客戶端接收之後被即時處理了。信用卡驗證是另外一個同步處理的例子;只有服務器證明輸入的信用卡號是有效的,同時客戶在賬戶上有足夠的存款,客戶才被容許繼續操做。可是讓咱們思考一下在順序處理系統上的支付結算步驟。一旦系統證明該用戶信用卡的信息是準確的,而且在賬戶上有足夠的資金,就沒必要等到全部的支付細節落實、轉帳完成。支付結算能夠異步方式進行,這樣客戶能夠繼續進行覈查操做。編程

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

JMS數組

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

建立JNDI初始上下文(context)。 
從JNDI上下文獲取一個隊列鏈接工廠。 
從隊列鏈接工廠中獲取一個Quene。 
建立一個Session對象。 
建立一個發送者(sender)或接收者(receiver)對象。 
使用步驟5建立的發送者或接收者對象發送或接收消息。 
處理完消息後,關閉全部JMS資源。session

        您能夠看到,步驟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類

 

 

 類名  包  功能
 JmsException  org.springframework.jms  只要發生一個JMS異常,Spring框架就會拋出異常,這個類是這些所拋出的異常的基(抽象)類。
 JmsTemplate, JmsTemplate102  org.springframework.jms.core  這些是輔助類,用於簡化JMS的使用,處理JMS資源(如鏈接工廠,目標和發送者/接收者對象)的建立和釋放。JmsTemplate102是JmsTemplate的子類,使用JMS1.0.2規範
 MessageCreator  org.springframework.jms.core   這是JmsTemplate類使用的回叫接口,它爲指定的會話建立JMS消息。
 MessageConverter  org.springframework. jms.support.converter  這個接口充當一個抽象,用來在Java對象與JMS消息之間進行轉換。
 DestinationResolver  org.springframework. jms.support.destination  這是JmsTemplate用來解析目標名的接口。該接口的默認實現是DynamicDestinationResolver和JndiDestinationResolve

 

 

  在接下來的部分,我將詳細解釋表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方法

 

 

 方法名稱  功能
 send  發送消息至默認或指定的目標。JmsTemplate包含send方法,它經過javax.jms.Destination或JNDI查詢來指定目標。
 receive  從默認或指定的目標接收消息,但只會在指定的時間後傳遞消息。咱們能夠經過receiveTimeout屬性指定超時時間。
 convertAndSend  這個方法委託MessageConverter接口實例處理轉換過程,而後發送消息至指定的目標。
 receiveAndConvert  從默認或指定的目標接收消息。並將消息轉換爲Java對象。

 

  目標能夠經過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應用程序中使用的框架

 

 

 邏輯層  技術/框架
 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設置JMS資源

  爲了異步處理消息,首先咱們須要消息隊列發送和接收消息。咱們能夠用Jboss裏的配置XML文件建立一個新的消息隊列,而後使用JMS控制檯瀏覽隊列的詳細狀況。清單1顯示了配置JMS的XML配置代碼片段(這個應該加入到jbossmq-destinations-service.xml文件,位於%JBOSS_HOME%serverlldeploy-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>

 

 

  如今,讓咱們看看如何使用一個名爲Hermes的JMS工具來瀏覽消息隊列。Hermes是一個Java Swing應用程序,它能夠建立、管理和監視JMS提供商(例如JBossMQ,WebSphereMQ,ActiveMQ和Arjuna服務器)裏的JMS目標。從它的網站上下載Hermes,解壓縮.zip文件到本地目錄(例如,c:dev oolshermes)。一旦安裝完成,雙擊文件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配置參數

 

 

 參數名稱  參數值
 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配置

  既然咱們已經有了運行例程所須要的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資源變得容易,特別是對於系統管理員來講。

 

相關文章
相關標籤/搜索