用Spring快速开发jms应用(JBOSS服务器)

异步进程通信是面向服务架构(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实现所包括的步骤:

1.创建JNDI初始上下文(context)。

2.从JNDI上下文获取一个队列连接工厂。

3.从队列连接工厂中获取一个Quene。

4.创建一个Session对象。

5.创建一个发送者(sender)或接收者(receiver)对象。

6.使用步骤5创建的发送者或接收者对象发送或接收消息。

7.处理完消息后,关闭所有JMS资源。

您可以看到,步骤6是处理消息的唯一地方。其他步骤都只是管理与实际业务要求无关的JMS资源,但是开发人员必须编写并维护这些额外步骤的代码。

Spring JMS   Spring框架提供了一个模板机制来隐藏Java APIs的细节。JEE开发人员可以使用JDBCTemplate和JNDITemplate类来分别访问后台数据库和JEE资源(数据源,连接池)。JMS也不例外。Spring提供JMSTemplate类,因此开发人员不用为一个JMS实现去编写样本代码。接下来是在开发JMS应用程序时Spring所具有一些的优势。

1.提供JMS抽象API,简化了访问目标(队列或主题)和向指定目标发布消息时JMS的使用。

2.JEE开发人员不需要关心JMS不同版本(例如JMS1.0.2与JMS1.1)之间的差异。

3.开发人员不必专门处理JMS异常,因为Spring为所有JMS异常提供了一个未经检查的异常,并在JMS代码中重新抛出。

示例程序

        说明:因为只是为了演示如何使用spring编写jms的应用,所以本例没有什么实际用途。

        程序功能:MessageProducer.java根据一用户信息产生一个消息发送到 JMS Provider;由MessageConsumer.java接收。

1.在Jboss里配置XML文件创建一个新的JMS provider。

打开位于%JBOSS_HOME%server\default\deploy\jms文件夹下的jbossmq-destinations-service.xml文件,加入以下代码片断:

<!--RegisterUserSend/ReceiveQueue-->

<mbeancode="org.jboss.mq.server.jmx.Queue"

name="jboss.mq.destination:service=Queue,name=registerUserQueue">

<dependsoptional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>

</mbean>

<!--RegisterUserSend/ReceiveTopic-->

<mbeancode="org.jboss.mq.server.jmx.Topic"

name="jboss.mq.destination:service=Topic,name=registerUserTopic">

<dependsoptional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>

</mbean>

2.在spring的配置文件中配置JMS组件的具体细节。

(1)JNDI上下文是取得JMS资源的起始位置,因此首先我们要配置JNDI模板:

<!--JNDI上下文(它是取得JMS资源的起始位置)-->

<beanid="jndiTemplate"class="org.springframework.jndi.JndiTemplate">

<propertyname="environment">

<props>

<propkey="java.naming.factory.initial">

org.jnp.interfaces.NamingContextFactory

</prop>

<propkey="java.naming.provider.url">localhost</prop>

<propkey="java.naming.factory.url.pkgs">

org.jnp.interfaces:org.jboss.naming

</prop>

</props>

</property>

</bean>

注意:此JNDI模板用到了org.jnp.interfaces.NamingContextFactory所以要把%JBOSS_HOME%\client下的jbossall-client.jar加到你的项目的classpath中。

(2)配置连接工厂:

<!--JMS连接工厂-->

<beanid="jmsConnectionFactory"class="org.springframework.jndi.JndiObjectFactoryBean">

<propertyname="jndiTemplate">

<refbean="jndiTemplate"/>

</property>

<propertyname="jndiName">

<value>XAConnectionFactory</value>

</property>

</bean>

注意:XAConnectionFactory这个JNDI名字是在%JBOSS_HOME%server\default\deploy\jms文件夹下的jms-ds.xml中定义的(它是由JBoss指定的)。

(3)配置JmsTemplate组件。在例程中我们使用JmsTemplate102。同时使用defaultDestination属性来指定JMS目标。

<!--JMS模板配置-->

<beanid="jmsTemplate"class="org.springframework.jms.core.JmsTemplate102">

<propertyname="connectionFactory"ref="jmsConnectionFactory"/>

<propertyname="defaultDestination"ref="destination"/>

<propertyname="pubSubDomain">

<value>true</value>

</property>

<!--等待消息的时间(ms)-->

<propertyname="receiveTimeout">

<value>30000</value>

</property>

</bean>

注意:如果使用topic-subscribe(主题订阅)模式,该模板的pubSubDomain属性值为true;若使用PToP(点对点)模式,pubSubDomain属性值为false或不配置该属性。

(4)定义一个JMS目标来发送和接收消息:

<beanid="destination"class="org.springframework.jndi.JndiObjectFactoryBean">

<propertyname="jndiTemplate">

<refbean="jndiTemplate"/>

</property>

<propertyname="jndiName">

<value>topic/registerUserTopic</value>

</property>

</bean>

(5)配置发送者和接收者组件:

<!--消息发布者-->

<beanid="msgProducer"class="com.boco.jms.MessageProducer">

<propertyname="jmsTemplate"ref="jmsTemplate"/>

</bean>

<!--消息接收者-->

<beanid="msgConsumer"class="com.boco.jms.MessageConsumer">

<propertyname="jmsTemplate"ref="jmsTemplate"/>

</bean>

3.相应的类:

(1).User对象。

/**

*User.java

*createdonJul2,2006

*Copyrights2006BOCO,Inc.Allrightsreserved.

*/

packagecom.boco.dto;

importjava.io.Serializable;

/**

*desc:用户信息Bean

*@authorqiujy

*/

publicclassUser{

privateintid;

privateStringusername;

privateStringpassword;

privateStringemail;

publicUser(){}

//以下为Getter,setter方法略

......

}

(2).消息生产者:

/**

*MessageProducer.java

*createdonJul22,2006

*Copyrights2006BOCO,Inc.Allrightsreserved.

*/

packagecom.boco.jms;

importjavax.jms.JMSException;

importjavax.jms.MapMessage;

importjavax.jms.Message;

importjavax.jms.Session;

importorg.springframework.jms.core.JmsTemplate;

importorg.springframework.jms.core.MessageCreator;

importcom.boco.dto.User;

/**

*desc:消息生产者

*@authorqiujy

*

*/

publicclassMessageProducer{

/**JMS模板*/

privateJmsTemplatejmsTemplate;

publicvoidsetJmsTemplate(JmsTemplatejmsTemplate){

this.jmsTemplate=jmsTemplate;

}

publicvoidsendMessage(finalUseruser){

//调用模板的send来发送消息

jmsTemplate.send(newMessageCreator(){

publicMessagecreateMessage(Sessionsession)throwsJMSException{

//构造一个要发送的消息

MapMessagemessage=session.createMapMessage();

message.setInt("id",user.getId());

message.setString("username",user.getUsername());

message.setString("password",user.getPassword());

message.setString("email",user.getEmail());

System.out.println("sendsuccess!!");

returnmessage;

}

});

}

}

(3).消息消费者:

/**

*MessageConsumer.java

*createdonJul22,2006

*Copyrights2006BOCO,Inc.Allrightsreserved.

*/

packagecom.boco.jms;

importjavax.jms.JMSException;

importjavax.jms.MapMessage;

importorg.springframework.jms.core.JmsTemplate;

importcom.boco.dto.User;

/**

*desc:消息消费者

*@authorqiujy

*

*/

publicclassMessageConsumer{

/**JMS模板*/

privateJmsTemplatejmsTemplate;

publicvoidsetJmsTemplate(JmsTemplatejmsTemplate){

this.jmsTemplate=jmsTemplate;

}

publicUserreceiveMessage(){

//参数为Destination的JNDI名字去掉前面的模式类型标识

//MapMessagemsg=(MapMessage)jmsTemplate.receive("registerUserQueue");

MapMessagemsg=(MapMessage)jmsTemplate.receive("registerUserTopic");

Useruser=newUser();

try{

user.setId(msg.getInt("id"));

user.setUsername(msg.getString("username"));

user.setPassword(msg.getString("password"));

user.setEmail(msg.getString("email"));

}catch(JMSExceptione){

//TODOAuto-generatedcatchblock

e.printStackTrace();

}

returnuser;

}

  }

 (4).测试用例:

//========生产者测试用例===============

/**

*TestMsgProducer.java

*createdonJul22,2006

*Copyrights2006BOCO,Inc.Allrightsreserved.

*/

packagecom.boco.jms;

importjunit.framework.TestCase;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.support.ClassPathXmlApplicationContext;

importcom.boco.dto.User;

/**

*desc:

*@authorqiujy

*

*/

publicclassTestMsgProducerextendsTestCase{

privateApplicationContextcontext;

/**

*@paramarg0

*/

publicTestMsgProducer(Stringarg0){

super(arg0);

context=newClassPathXmlApplicationContext("applicationContext_jms.xml");

}

/*(non-Javadoc)

*@seejunit.framework.TestCase#setUp()

*/

protectedvoidsetUp()throwsException{

super.setUp();

}

/*(non-Javadoc)

*@seejunit.framework.TestCase#tearDown()

*/

protectedvoidtearDown()throwsException{

super.tearDown();

}

/**

*Testmethodfor{@linkcom.boco.jms.MessageProducer#sendMessage(com.boco.dto.User)}.

*/

publicvoidtestSendMessage(){

Useruser=newUser();

user.setId(132);

user.setUsername("JMSTest");

user.setPassword("password");

user.setEmail("[email protected]");

MessageProducerproducer=(MessageProducer)context.getBean("msgProducer");

producer.sendMessage(user);

}

  }

  //============ 消费者测试用例 ===============

/**

*TestMsgConsumer.java

*createdonJul22,2006

*Copyrights2006BOCO,Inc.Allrightsreserved.

*/

packagecom.boco.jms;

importjunit.framework.TestCase;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.support.ClassPathXmlApplicationContext;

importcom.boco.dto.User;

/**

*desc:

*@authorqiujy

*

*/

publicclassTestMsgConsumerextendsTestCase{

privateApplicationContextcontext;

/**

*@paramarg0

*/

publicTestMsgConsumer(Stringarg0){

super(arg0);

context=newClassPathXmlApplicationContext("applicationContext_jms.xml");

}

/*(non-Javadoc)

*@seejunit.framework.TestCase#setUp()

*/

protectedvoidsetUp()throwsException{

super.setUp();

}

/*(non-Javadoc)

*@seejunit.framework.TestCase#tearDown()

*/

protectedvoidtearDown()throwsException{

super.tearDown();

}

/**

*Testmethodfor{@linkcom.boco.jms.MessageConsumer#receiveMessage()}.

*/

publicvoidtestReceiveMessage(){

MessageConsumerconsumer=(MessageConsumer)context.getBean("msgConsumer");

Useruser=consumer.receiveMessage();

assertNotNull(user);

System.out.println("id========"+user.getId()

+"\nname======"+user.getUsername()

+"\npassword=="+user.getPassword()

+"\nemail====="+user.getEmail());

}

}

相关推荐