异步进程通讯是面向服务架构(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资源变得容易,特别是对于系统管理员来讲。