异步进程通信是面向服务架构(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类

 类名  包  功能
 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资源变得容易,特别是对于系统管理员来说。

使用Spring JMS轻松实现异步消息传递的更多相关文章

  1. JMS是一种应用于异步消息传递的标准API

    JMS是一种应用于异步消息传递的标准API,作为Java平台的一部分,JMS可以允许不同应用.不同模块之间实现可靠.异步数据通信.一些概念 JMS provider    An implementat ...

  2. ActiveMQ学习总结(6)——ActiveMQ集成Spring和Log4j实现异步日志

    我的团队和我正在创建一个由一组RESTful JSON服务组成的服务平台,该平台中的每个服务在平台中的作用就是分别提供一些独特的功能和/或数据.由于平台中产生的日志四散各处,所以我们想,要是能将这些日 ...

  3. Spring JMS 官方文档学习

    最后部分的XML懒得写了,因为个人更倾向于JavaConfig形式. 为知笔记版本见这里,带格式~ 做了一个小demo,放到码云上了,有兴趣的点我. 说明:需要先了解下JMS的基础知识. 1.介绍 S ...

  4. IBM Mq Spring JMS 的xml配置

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...

  5. ActiveMQ消息队列从入门到实践(4)—使用Spring JMS收发消息

    Java消息服务(Java Message Service ,JMS)是一个Java标准,定义了使用消息代理的通用API .在JMS出现之前,每个消息代理都有私有的API,这就使得不同代理之间的消息代 ...

  6. ActiveMQ学习笔记(5)——使用Spring JMS收发消息

      摘要 ActiveMQ学习笔记(四)http://my.oschina.net/xiaoxishan/blog/380446 中记录了如何使用原生的方式从ActiveMQ中收发消息.可以看出,每次 ...

  7. Spring JMS ActiveMQ整合(转)

    转载自:http://my.oschina.net/xiaoxishan/blog/381209#comment-list ActiveMQ学习笔记(四)http://my.oschina.net/x ...

  8. ActiveMQ第二弹:使用Spring JMS与ActiveMQ通讯

    本文章的完整代码可从我的github中下载:https://github.com/huangbowen521/SpringJMSSample.git 上一篇文章中介绍了如何安装和运行ActiveMQ. ...

  9. Android异步消息传递机制源码分析

    1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.p ...

随机推荐

  1. 阅读build to win的个人感想

    一个程序员要向各个方面学习,向市场.向用户学习等,不能局限于一方面.除此以外还要有自己的想法,要懂得创新,也需要在各个方面都有所突破,有所超越,实力才是取得胜利的根关键.

  2. Jquery练习1

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  3. centos 默认php 版本太低移到高版本的办法

    // centos 默认有php 版本太低转移到高版本的解决办法 php -v 版本低 ln -s /usr/local/php/bin/php /usr/bin/php

  4. TortoiseGit 安装与配置

    2. TortoiseGit安装与配置 标签: TortoiseGit安装配置Windows 2014-12-01 15:25 135739人阅读 评论(10) 收藏 举报 .embody{ padd ...

  5. “数据接口请求异常:error”,Layui-table使用遇到的最大一个坑

    后台返回的json格式必须和官方给的一模一样 code必须为0, code必须为0, code必须为0, code必须为0

  6. vbs 入门

    dim  定义变量名 dim name------------- dim name,agename = "worf"age = 20 msgbox   输出 msgbox name ...

  7. 各颜色LED压降

    一下是参考1.直插LED压降红:2.0-2.2V黄:1.8-2.0V绿:3.0-3.2V 额定电流约20mA.2.贴片LED压降红:1.82-1.88V,电流5-8mA绿:1.75-1.82V,3-5 ...

  8. R语言 which() 、 which.min() 、 which.max() 函数

    函数 which() 可以用来找到满足条件的下标,如 x <- c(3, 4, 3, 5, 7, 5, 9) which(x > 5) 5 7 seq(along=x)[x > 5] ...

  9. SpringMVC配置没有任何问题根据请求却怎么都找不到映射(tomcat所导致的问题)

    本人在做SpringMVC练习的时候,配置文件反复检查了不下十几遍,没有任何问题,然后就招了个之前的项目的源码复制进去,原来的项目没有任何问题,这个项目却怎么都不能跳转路径,然后有找了一个Spring ...

  10. Centos7 更新配置为阿里源步骤

    一.yum更换配置源过程 1.备份原有的.repo源文件 首先需要将之前的源进行备份(一般重要的配置文件都需要有备份的意识) # 进入源配置目录 cd /etc/yum.repos.d # 创建备份文 ...