Spring框架则简化了使用JEElg(包括JMS)的Q务。它提供的模板机刉藏了典型的JMS实现的细节,q样开发h员可以集中精力放在处理消息的实际工作中,而不用担心如何去创徏Q访问或清除JMS资源?/p>
本文对Spring JMS API作一个概qͼq过一个运行在JBoss MQ服务器上的web例程来介l如何用Spring JMS API来异步处理(发送和接收Q消息。我通过传统JMS实现和Spring JMS实现两者间的比较,来展CZ用Spring JMS处理消息是如何的单和灉|?/p>
在现实中Q大多数webh都是同步处理的。例如,当用戯d一个网站,首先输入用户名和密码Q然后服务器验证d合法性。如果验证成功,E序允许该用户q入|站。这里,dh在从客户端接收以后被x处理了。信用卡验证是另一个同步处理的例子Q只有服务器证实输入的信用卡h有效的,同时客户在帐户上有够的存款Q客h被允许l操作。但是让我们思考一下在序处理pȝ上的支付l算步骤。一旦系l证实该用户信用卡的信息是准的Qƈ且在帐户上有_的资金,׃必等到所有的支付l节落实、{账完成。支付结可以异步方式进行,q样客户可以l箋q行核查操作?/p>
需要比典型同步h耗费更长旉的请求,可以使用异步处理。另一个异步处理的例子是,在本地贷Ƒ֤理程序中Q提交至自动扉KpȝQAUSQ的信用h处理q程。当借方提交h甌后,抉|公司会向AUS发送请求,以获取信用历史记录。由于这个请求要求得到全面而又详细的信用报告,包括借方C和过ȝ帐户Q最q的付款和其他胦务资料,服务器需要耗费较长的时_几小时或着有时甚至是几天)来对q些h作出响应。客LE序Q应用)要与服务器连接ƈ耗费如此长的旉来等待结果,q是毫无意义的。因此通信应该是异步发生的Q也是Q一旦请求被提交Q它p攄在队列中Q同时客L与服务器断开q接。然后AUS服务从指定的队列中选出hq行处理Qƈ处理得到的消息攄在另一个消息队列里。最后,客户端程序从q个队列中选出处理l果Q紧接着处理q个信用历史数据?/p>
如果您用过JMS代码Q您会发现它与JDBC或JCA很像。它所包含的样本代码创建或JMS资源对象回溯Q得每一ơ您需要写一个新cL发送和接收消息Ӟ都具有更好的代码密集性和重复性。以下序列显CZ传统JMS实现所包括的步骤:
Spring框架提供了一个模板机制来隐藏Java APIs的细节。JEE开发h员可以用JDBCTemplate和JNDITemplatecL分别讉K后台数据库和JEE资源Q数据源Q连接池Q。JMS也不例外。Spring提供JMSTemplatec,因此开发h员不用ؓ一个JMS实现ȝ写样本代码。接下来是在开发JMS应用E序时Spring所h一些的优势?/p>
?. Spring JMSc?/p>
cd | ?/th> | 功能 |
---|---|---|
JmsException | org.springframework.jms | 只要发生一个JMS异常QSpring框架׃抛出异常Q这个类是这些所抛出的异常的基(抽象Q类?/td> |
JmsTemplate, JmsTemplate102 | org.springframework.jms.core | q些是辅助类Q用于简化JMS的用,处理JMS资源Q如q接工厂Q目标和发送?接收者对象)的创建和释放。JmsTemplate102是JmsTemplate的子c,使用JMS1.0.2规范 |
MessageCreator | org.springframework.jms.core | q是JmsTemplatecM用的回叫接口Q它为指定的会话创徏JMS消息?/td> |
MessageConverter | org.springframework.jms.support.converter | q个接口充当一个抽象,用来在Java对象与JMS消息之间q行转换?/td> |
DestinationResolver | org.springframework.jms.support.destination | q是JmsTemplate用来解析目标名的接口。该接口的默认实现是DynamicDestinationResolver和JndiDestinationResolve |
在接下来的部分,我将详细解释?所列的一部分c(例如JmsTemplateQDestinationResolver和MessageConverterQ?/p>
JmsTemplate提供了几U辅助方法,用来执行一些基本操作。要开始用JmsTemplate前,您需要知道JMS供应商支持哪个JMS规范Q?a target="_blank">JBoss AS 4.0.2?a target="_blank">WebLogic 8.1服务器支持JMS 1.0.2规范。WebLogic Server 9.0包括了对JMS 1.1规范?a target="_blank">支持。JMS 1.1l一了点对点QPTPQ和发布/订阅QPub/SubQ域的编E接口。这U改变的l果是Q开发h员可以创Z个事务会话,然后在这同一个JMS会话里,可以从一个Queue(PTP)中接收消息,同时发送另一个消息到一个Topic(Pub/Sub)。JMS 1.1向后兼容JMS 1.0Q应此根据JMS 1.0~写的代码仍可以适用于JMS 1.1?/p>
JmsTemplate提供多种发送和接收消息的方法。表2列出了这些方法的一部分?/p>
?. JMS templateҎ
Ҏ名称 | 功能 |
---|---|
send | 发送消息至默认或指定的目标。JmsTemplate包含sendҎQ它通过javax.jms.Destination或JNDI查询来指定目标?/td> |
receive | 从默认或指定的目标接收消息,但只会在指定的时间后传递消息。我们可以通过receiveTimeout属性指定超时时间?/td> |
convertAndSend | q个Ҏ委托MessageConverter接口实例处理转换q程Q然后发送消息至指定的目标?/td> |
receiveAndConvert | 从默认或指定的目标接收消息。ƈ消息{换ؓJava对象?/td> |
目标可以通过JNDI上下文保存和获取。当配置SpringE序上下文(application contextQ时Q我们可以用JndiObjectFactoryBeancd得对JMS的引用。DestinationResolver接口是用来把目标名称解析成JMS目标Q当应用E序存在大量目标Ӟq是非常有用的。DynamicDestinationResolver(DestinationResolver的默认实?是用来解析动态目标的?/p>
MessageConverter接口定义了将Java对象转换为JMS消息的约定。通过q个转换器,应用E序代码可以集中于处理事务对象,而不用ؓ对象如何表示为JMS消息q样的内部细节所困饶。SimpleMessageConverter(和SimpleMessageConverter102)是MessageConverter的默认实现。可使用它们分别String转换为JMS TextMessageQ字节数l?byte[])转换为JMS BytesMessageQMap转换为JMS MapMessageQ和Serializable对象转换为JMS ObjectMessage。您也可以编写自定义的MessageConverter实例Q通过XMLl定框架Q例?a target="_blank">JAXBQ?CastorQ?a target="_blank">Commons DigesterQ?a target="_blank">XMLBeans?a target="_blank">XStreamQ,来实现XML文档到TextMessage对象的{换?/p>
我将用一个贷Ƅ请处理系l(命名为LoanProcQ示例来演示如何在JMS应用E序中用Spring。作ƄL一部分QLoanProc通过发送贷ƾ详情(hIDQ借方名字Q借方的SSNQ贷ƾ期限和h数额Q,从AUSpȝ获得信用历史详情。ؓ了简便v见,我们Z两个基本参数来表CZ用历史详情:信用分数Q又名FICO得分Q和h数额。让我们假设处理信用查请求是按以下业务规则进行的Q?/p>
信用h处理使用案例包括以下几个步骤Q?/p>
在这个例E中Q两个消息队列都配置在同一个JBoss MQ server上。用案例用?的序列图QSequenceDiagramQ表C?/p>
下面的表3昄了在例程中我所使用的不同技术和开?/a>框架Qƈ按应用逻辑层排列?/p>
?. 在JMS应用E序中用的框架
逻辑?/th> | 技?框架 |
---|---|
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 |
Z异步处理消息Q首先我们需要消息队列发送和接收消息。我们可以用Jboss里的配置XML文g创徏一个新的消息队列,然后使用JMS控制台浏览队列的详细情况。清?昄了配|JMS的XML配置代码片断Q这个应该加入到jbossmq-destinations-service.xml文gQ位?JBOSS_HOME%server lldeploy-hasingletonjm文g夹下。)
清单1.JBoss MQ Server上JMS队列的配|?/p>
<!-- 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>
现在Q让我们看看如何使用一个名为Hermes的JMS工具来浏览消息队列?a target="_blank">Hermes是一个Java Swing应用E序Q它可以创徏、管理和监视JMS提供商(例如JBossMQQ?a target="_blank">WebSphereMQQ?a target="_blank">ActiveMQ?a target="_blank">Arjuna服务器)里的JMS目标。从它的|站上下载HermesQ解压羃.zip文g到本地目录(例如Qc:dev oolshermesQ。一旦安装完成,双击文ghermes.batQ位于bin文g夹下Q启动程序?/p>
要在Hermes里配|JBossMQ服务器,请参考Hermes|站上的q个演示。它有着的step-by-step可视化指C来配置JBoss MQ。当配置一个新的JNDI初始上下文时Q请输入下面的信息?/p>
当您创徏新的目标Ӟ误入queue/CreditRequestSendQueue和queue/CreditRequestReceiveQueue。图2昄了JMS控制台的ȝ口,其中有ؓJMS例程创徏的新的消息队列?/p>
?2. Hermes中所有目标的截图.Q单L图来查看完整视图Q?/p>
下面的图3昄了在从消息发送者类发送消息到CreditRequestSendQueue后,Hermes JMS控制台及消息队列的截图。您可以看见?个消息在队列中,控制台显CZ消息详情Q例如消息IDQ消息目标,旉戛_实际的消息内宏V?/p>
?3. Hermes中所有队列的截图.Q单L图来查看完整视图Q?/p>
在例E中使用的队列名U和其他JMS和JNDI参数见表 4?/p>
?. Spring JMS配置参数
参数名称 | 参数?/th> |
---|---|
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 |
既然我们已经有了q行例程所需要的JMS目标Q现在该了解?a target="_blank">XML Spring配置文gQ名为spring-jms.xmlQ来l配JMSlg的具体细节了。这些组件是ҎInversion of Controller (IOC)设计模式里的讄方式注入原则Qsetter injection principleQ,用JMS对象实例cȝ配的。让我们详细查看q些lgQƈ为每一个JMSlg演示一DXML配置代码?/p>
JNDI上下文是取得JMS资源的v始位|,因此首先我们要配|JNDI模板。清?昄了名为jndiTemplate的Spring beanQ其中列有JNDI初始上下文所必需的常用参数?/p>
清单2. JNDI上下文模?/p>
<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>
接着Q我们配|队列连接工厂。清?昄了队列连接工厂的配置?/p>
清单3. JMS队列q接工厂配置
<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??/p>
清单4. 发送队列配|?/p>
<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>
然后我们再来配置JmsTemplatelg。在例程中我们用JmsTemplate102。同时用defaultDestination属性来指定JMS目标?/p>
清单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>
最后我们配|发送者和接收者组件。清??分别是Sender ?Receiver对象的配|?/p>
清单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>
我写了一个测试类Q命名ؓLoanApplicationControllerTestQ用来测试LoanProcE序。我们可以用这个类来设定贷Ƒ֏C及调用信用请求服务类?/p>
让我们看一下不使用Spring JMS API而用传lJMS开发途径的消息发送者实例。清?昄了MessageSenderJMSc里的sendMessageҎQ其中包含了使用JMS API处理消息的所有必需步骤?/p>
清单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) {} } } }
现在Q我们来看看使用了Spring的消息发送者实例。清?0昄了MessageSenderSpringJMScMsendҎ的代码?/p>
清单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(); } }
如您所见,通过使用配置文gQ所有与理JMS资源有关的步骤都交由Spring容器处理。我们只需引用一个JMSSender对象Q然后调用对象里的sendMessageҎ?/p>
在本文中Q我们看到Spring框架是如何用JMS API化异步消息传递。SpringL了所有用JMS处理消息所必需的样本代码(例如得到一个队列连接工厂,从Java代码里创建队列和会话对象Q在q行时用配|文件对它们q行l配Q。我们可以动态的交换JMS资源对象Q而不必修改Q何Java代码Q这要感谢Inversion of Control (IOC) 原则的力量?/p>
既然异步消息传递是SOA框架的整体构成部分,Spring很适合U_到SOA工具集。此外,JMS理工具Q如HermesQ得创建、管理和监督JMS资源变得ҎQ特别是对于pȝ理员来说?/p>
原文出处:http://www.onjava.com/pub/a/onjava/2006/02/22/asynchronous-messaging-with-spring-jms.html