一般的RPC、RMI等机制采用的都是同步通信机制。
JMS(Java Message Service)是面向异步消息而制定的标准API
利用Spring的JmsTemplate
可以简化JMS异步消息的发送,和消息的异步接收。
通过ActiveMQ可以搭建一个强大的消息代理服务器,和一套消息代理API实现。
在JMS中有两个主要的概念:消息代理(message broker)和目的地(destination)。
消息代理一般采用的是ActiveMQ这一实现,目的地一般指实现中的队列或主题,分别对应点对点消息模型和发布-订阅消息模型。
点对点消息模型,一个消息只能被一个接受者接收,而发布-订阅模型一个消息可以被多个接受者处理。
传统同步通信的缺点:
JMS的优点:
异步无需等待;
面向消息和解耦,JMS发送消息是以数据为中心的,没有与特定方法绑定。
位置独立:通过消息代理服务集群实现。
确保投递:通过ActiveMQ消息代理实现。
安装ActiveMQ:http://activemq.apache.org/
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring-version}</version> </dependency> <dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-core</artifactId> <version>5.7.0</version> </dependency>
方式一:使用activemq的spring命名空间:
xmlns:amq=“http://activemq.apache.org/schema/core”
<amq:connectionFactory id="connectionFactory" 这个名称是有意义的 brokerURL="tcp://localhost:61616"/>
方式二:使用声明bean的形:
<bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> </bean>
参考普通形式:
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供--> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> </bean> <!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory"/> </bean>
ActiveMQ为我们提供了一个PooledConnectionFactory
,通过往里面注入一个ActiveMQConnectionFactory可以用来将Connection、Session和MessageProducer池化,这样可以大大的减少我们的资源消耗。当使用PooledConnectionFactory时,我们在定义一个ConnectionFactory时应该是如下定义:
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供--> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616"/> </bean> <bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"> <property name="connectionFactory" ref="targetConnectionFactory"/> <property name="maxConnections" value="10"/> </bean> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <property name="targetConnectionFactory" ref="pooledConnectionFactory"/> </bean>
目的地可以是一个队列或者一个主题。
方式一:使用activemq的spring命名空间:
xmlns:amq=“http://activemq.apache.org/schema/core”
<amq:queue id=“queue” physicalName=“spitter.queue” />
<amq:topic id=“queue” physicalName=“spitter.topic” />
方式二:使用声明bean的形式:
<bean id="queue" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg value="spitter.queue"/> </bean> <bean id="topic" class="org.apache.activemq.command.ActiveMQTopic"> <constructor-arg value="spitter.topic"/> </bean>
JMS API太过繁琐,JmsTemplate封装了模板化代码。简化开发。
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 --> <property name="connectionFactory" ref="connectionFactory"/> </bean>
面向接口编程,声明一个接口:用于组装和发送消息
public interface AlertService { void sendSpittleAlert(Spittle spittle); }
接口实现类中发送消息:
public class AlertServiceImpl implements AlertService { public void sendSpittleAlert(final Spittle spittle) { jmsTemplate.send( "spittle.queue", //指定目的地 new MessageCreator() { public Message createMessage(Session session) throws JMSException { return session.createObjectMessage(spittle); //创建消息。 } } ); } @Autowired JmsTemplate jmsTemplate; //注入jms模板 }
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 --> <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 --> <property name="connectionFactory" ref="connectionFactory"/> <property name="defaultDestinationName" value="spitter.queue"/> </bean>
这样调用jmsTemplate的send()方法时就可以不用指定目的地了。
只需要调用jmsTemplate的receive()
方法即可:
@Component public class AlertMessageReceiver { @Autowired JmsTemplate jmsTemplate; public Spittle getAlert() { try { ObjectMessage receivedMessage = (ObjectMessage) jmsTemplate.receive(); //注意此处为同步接收,如果没有可用消息,receive()则会一直等待,直到获得消息或者超时为止。 return (Spittle) receivedMessage.getObject();//<co id="co_getObject"/> } catch (JMSException jmsException) { throw JmsUtils.convertJmsAccessException(jmsException);//<co id="co_throwException"/> } } }
jmsTemplate.receive(); / /注意此处为同步接收,如果没有可用消息,receive()则会一直等待,直到获得消息或者超时为止。
为什么消息可以异步发送而不能异步接收呢。?这很奇怪,不过幸运的是,Spring提供了类似于EJB3当中的MDB消息驱动bean的解决方案MDP即消息驱动POJO。
用于异步接收消息:
写POJO用于异步接收处理消息:
public class SpittleAlertHandler { public void processSpittle(Spittle spittle) { // ... implementation goes here... } }
配置消息监听器
首先将POJO声明为一个bean:
<bean id=“spittleHandler” class=“com.css.SpittleAlertHandler”/>
然后,为了把这个POJO转化为消息驱动的POJO,需要把bean声明为消息监听器:
xmlns:jms=“http://www.springframework.org/schema/jms”
<jms:listener-container connection-factory="connectionFactory"> <jms:listener destination="spitter.queue" ref="spittleHandler" method="processSpittle" /> 同时指定处理消息的方法,方法参数会被注入值 </jms:listener-container>
略。。。
更多参考:
http://www.iteye.com/blogs/subjects/springjms