activemq+spring 持久化发送消息

转载 2016年08月31日 10:49:28

转:http://blog.csdn.net/u010741376/article/details/51254436

JMS 即 Java message service 是为java提供了一种创建、发送、接收消息的通用方法。可以将复杂的系统进行业务分离,变成灵活的高度解耦合的布局。同时对我们的日常业务需求开发,提供了非常灵活的业务解决方案。比如缴费还款送积分,送积分的业务逻辑不能影响到缴费还款的业务逻辑,所以最好的,就是缴费/还款逻辑执行完成之后,通过一种方式告诉积分系统,给用户发送积分,发送积分的结果不要影响到复杂的缴费还款的过程。这种情况下,采用jms进行异步处理,便是一个很好的选择。

要使用消息的方式来进行系统交互,我们需要一个消息中间平台,来进行消息的接受转发,同时处理复杂的消息持久化等问题。本文我们采用activemq来做实验。这样的架构下,我们的系统通常会变成如下架构:

 消息产生者 -> 消息中心 -> 消息消费者


1、消息的两种传播方式

JMS支持两种消息传播:PTP 和 PUB/SUB

PTP : 点对点发送。消息的发送方将消息放入管道中,消息的接收方从管道中取出消息并处理。

PUB/SUB : 发布/订阅方式。消息的发布者将自己的主题放入消息中心,同时进行消息投递,消息订阅者只获取自己订阅的消息。

jms为了支持上述两种模式,提供了两套针对同样接口的实现,对照关系如下:

ConnectionFacatory:被管理的对象,由客户端(发布者/接受者)使用,用来创建一个链接。

Connection:提供一个JMS消息的活动链接。

Destination:封装了消息目的地,或者主题类型。

Session:一个用来发送和接受消息的线上上下文。

MessageProducer:由session创建的,用来发送消息的对象。

MessageConsumer:由session创建的用来接受消息的对象。

2、jms消息模型

Jms的消息分为三部分:消息头、消息属性、消息体

消息头:包含了消息的客户端和提供者用来路由和识别消息的数据。

 消息头包含的字段:

 JMSDestination:包含了消息发往的目的地或者主题信息。

 JMSDeliveryMode:消息传送模式。spring提供额jms模板提供了2种模式(有默认模式):DEFAULT_DELEVERY_MODE 默认模式、DEFAULT_PRIORITY、DEFAULT_TIME_TO_LIVE

 JMSMessageID:消息标示,唯一性,每个消息都不同,即便是承载者相同消息体的消息。

 JMSTimestamp:发送时间

 JMSCorrelationID:与当前消息关联的其他消息的标示

 JMSReplyTo:回复消息的目的地。带有这样属性的消息通常是发送方希望有一个响应,这个响应是可选的。

 JMSRedelivered:带有该字段的消息通常过去发送过但是没有被确认,如果要再次发送,提供者必须设置该字段。如果true,则消息接受者必须进行消息重复处理的逻辑。

 JMSType:消息类型标示。官方文档的解释:

 

JMSType头字段包含了由客户端在发送消息时提供的消息类型标识。一些消息提供者使用消息库来存储由应用发送的消息定义。type头字段可以引用提供者库中的消息定义。JMS没有定义一个标准的消息定义库,也没有定义这个库中所包含的各种定义的命名策略。一些消息系统要求每个被创建的应用消息都必须有一个消息类型定义,并且每个消息都指定它的类型。为了能够使JMS工作于这些消息系统提供者,无论应用是否使用,JMS客户端最好赋值JMSType ,这样可以保证为需要该头字段的提供者提供了正确的设置。为了保证移植性,JMS客户端应使用安装时在提供者消息库中定义的语义值作为JMSType的值。

 JMSExpiration :过期时间。

 JMSPriority:优先级。

消息属性:包括了标准投资段之外的额外添加给消息的可选的字段。比如 应用指定的属性。

消息体:消息携带的内容。

3、消息传输编程步骤

 1)使用jndi获取一个ConnectionFacatory对象;

 2)使用jndi获取一个或者多个Destination对象;

 3)使用ConnectionFactory创建一个JMS连接;

 4)使用连接创建Jms session;

 5)使用session和destination创建MessageProducers和MessageConsumers

 6)使用Connection进行传输消息;

上述是jms的基础知识,简单了解可以便于下面的应用。jms本身提供了jar可以下载并使用相关配置,结合消息系统来完成消息的发送和接受等操作。但是一种便捷的方式,为加快开发,可以使用spring提供的jms模板,即JmsTemplate,这个类似于jdbcTemplate。

我们演示PTP和PUB/SUB两种模式的配置。

先看下基础公用的类:

我们定义:消息发送者、消息接受者、消息转换器

  1. public class MessageSender {    
  2.     // ~~~ jmsTemplate    
  3.     public JmsTemplate jmsTemplate;    
  4.         
  5.     /**   
  6.      * send message   
  7.      */    
  8.     public void sendMessage(){    
  9.         jmsTemplate.convertAndSend("hello jms!");    
  10.     }    
  11.     public void setJmsTemplate(JmsTemplate jmsTemplate) {    
  12.         this.jmsTemplate = jmsTemplate;    
  13.     }    
  14. }    

  1. public class MessageReceiver implements MessageListener {    
  2.     /* (non-Javadoc)   
  3.      * @see javax.jms.MessageListener#onMessage(javax.jms.Message)   
  4.      */    
  5.     public void onMessage(Message m) {    
  6.         System.out.println("[receive message]");    
  7.             
  8.         ObjectMessage om = (ObjectMessage) m;    
  9.         try {    
  10.             String key1 = om.getStringProperty("key1");    
  11.                 
  12.             System.out.println(key1);    
  13.                 
  14.             System.out.println("model:"+om.getJMSDeliveryMode());    
  15.             System.out.println("destination:"+om.getJMSDestination());    
  16.             System.out.println("type:"+om.getJMSType());    
  17.             System.out.println("messageId:"+om.getJMSMessageID());    
  18.             System.out.println("time:"+om.getJMSTimestamp());    
  19.             System.out.println("expiredTime:"+om.getJMSExpiration());    
  20.             System.out.println("priority:"+om.getJMSPriority());    
  21.         } catch (JMSException e) {    
  22.             e.printStackTrace();    
  23.         }    
  24.     }    
  25. }    

  1. public class MessageConvertForSys implements MessageConverter {    
  2.     /* (non-Javadoc)   
  3.      * @see org.springframework.jms.support.converter.MessageConverter#toMessage(java.lang.Object, javax.jms.Session)   
  4.      */    
  5.     public Message toMessage(Object object, Session session)    
  6.             throws JMSException, MessageConversionException {    
  7.         System.out.println("[toMessage]");    
  8.         ObjectMessage objectMessage = session.createObjectMessage();    
  9.             
  10.         objectMessage.setJMSExpiration(1000);    
  11.         objectMessage.setStringProperty("key1", object+"_add");    
  12.             
  13.         return objectMessage;    
  14.     }    
  15.     /* (non-Javadoc)   
  16.      * @see org.springframework.jms.support.converter.MessageConverter#fromMessage(javax.jms.Message)   
  17.      */    
  18.     public Object fromMessage(Message message) throws JMSException,    
  19.             MessageConversionException {    
  20.         System.out.println("[fromMessage]");    
  21.         ObjectMessage objectMessage = (ObjectMessage) message;    
  22.             
  23.         return objectMessage.getObjectProperty("key1");    
  24.     }    
  25. }   

第一种,PTP方式的配置:
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <beans xmlns="http://www.springframework.org/schema/beans"    
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"    
  4.     xmlns:context="http://www.springframework.org/schema/context"    
  5.     xmlns:aop="http://www.springframework.org/schema/aop"    
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans     
  7.     http://www.springframework.org/schema/beans/spring-beans-2.5.xsd     
  8.     http://www.springframework.org/schema/context     
  9.     http://www.springframework.org/schema/context/spring-context-2.5.xsd "    
  10.     default-autowire="byName">    
  11.         
  12.     <!-- JMS PTP MODEL -->    
  13.     <!-- PTP链接工厂 -->    
  14.     <bean id="queueConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">    
  15.         <property name="brokerURL" value="tcp://127.0.0.1:61616" />    
  16.         <!-- <property name="brokerURL" value="vm://normandy.notify" /> -->    
  17.         <property name="useAsyncSend" value="true" />    
  18.     </bean>    
  19.     <!-- 定义消息队列 -->    
  20.     <bean id="dest" class="org.apache.activemq.command.ActiveMQQueue">    
  21.         <constructor-arg value="queueDest" />    
  22.     </bean>    
  23.     <!-- PTP jms模板 -->    
  24.     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">    
  25.         <property name="connectionFactory" ref="queueConnectionFactory"></property>    
  26.         <property name="defaultDestination" ref="dest" />    
  27.         <property name="messageConverter" ref="messageConvertForSys" />    
  28.         <property name="pubSubDomain" value="false" />    
  29.     </bean>    
  30.         
  31.     <!-- 消息转换器 -->    
  32.     <bean id="messageConvertForSys" class="com.normandy.tech.test.MessageConvertForSys"></bean>    
  33.         
  34.     <!-- 消息发送方 -->    
  35.     <bean id="messageSender" class="com.normandy.tech.test.MessageSender"></bean>    
  36.     <!-- 消息接收方 -->    
  37.     <bean id="messageReceiver" class="com.normandy.tech.test.MessageReceiver"></bean>    
  38.         
  39.     <!-- 消息监听容器 -->    
  40.     <bean id="listenerContainer"      
  41.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">      
  42.         <property name="connectionFactory" ref="queueConnectionFactory" />      
  43.         <property name="destination" ref="dest" />      
  44.         <property name="messageListener" ref="messageReceiver" />      
  45.     </bean>    
  46. </beans>  

第二种:PUB/SUB方式的配置

我们配置两个消息订阅者,分别订阅不同的消息,这样用于判断是否成功执行了消息的发布和消息的订阅

  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <beans xmlns="http://www.springframework.org/schema/beans"    
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"    
  4.     xmlns:context="http://www.springframework.org/schema/context"    
  5.     xmlns:aop="http://www.springframework.org/schema/aop"    
  6.     xsi:schemaLocation="http://www.springframework.org/schema/beans     
  7.     http://www.springframework.org/schema/beans/spring-beans-2.5.xsd     
  8.     http://www.springframework.org/schema/context     
  9.     http://www.springframework.org/schema/context/spring-context-2.5.xsd "    
  10.     default-autowire="byName">    
  11.         
  12.     <!-- JMS TOPIC MODEL -->    
  13.     <!-- TOPIC链接工厂 -->    
  14.     <bean id="topicSendConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">    
  15.         <property name="brokerURL" value="tcp://127.0.0.1:61616" />    
  16.         <property name="useAsyncSend" value="true" />    
  17.     </bean>    
  18.         
  19.     <bean id="topicListenConnectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">    
  20.         <property name="brokerURL" value="tcp://127.0.0.1:61616" />    
  21.     </bean>    
  22.         
  23.     <!-- 定义主题 -->    
  24.     <bean id="myTopic"  class="org.apache.activemq.command.ActiveMQTopic">    
  25.       <constructor-arg value="normandy.topic"/>    
  26.     </bean>    
  27.         
  28.     <bean id="myTopic2"  class="org.apache.activemq.command.ActiveMQTopic">    
  29.       <constructor-arg value="normandy.topic2"/>    
  30.     </bean>    
  31.         
  32.     <!-- 消息转换器 -->    
  33.     <bean id="messageConvertForSys" class="com.normandy.tech.test.MessageConvertForSys"></bean>    
  34.         
  35.     <!-- TOPIC send jms模板 -->    
  36.     <bean id="topicSendJmsTemplate" class="org.springframework.jms.core.JmsTemplate">    
  37.         <property name="connectionFactory" ref="topicSendConnectionFactory"></property>    
  38.         <property name="defaultDestination" ref="myTopic" />    
  39.         <property name="messageConverter" ref="messageConvertForSys" />    
  40.         <!-- 开启订阅模式 -->    
  41.         <property name="pubSubDomain" value="true"/>    
  42.     </bean>    
  43.         
  44.     <!-- 消息发送方 -->    
  45.     <bean id="topicMessageSender" class="com.normandy.tech.test.MessageSender">    
  46.         <property name="jmsTemplate" ref="topicSendJmsTemplate"></property>    
  47.     </bean>    
  48.         
  49.     <!-- 消息接收方 -->    
  50.     <bean id="topicMessageReceiver" class="com.normandy.tech.test.MessageReceiver">    
  51.     </bean>    
  52.         
  53.     <!-- 主题消息监听容器 -->    
  54.     <bean id="listenerContainer"      
  55.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">      
  56.         <property name="connectionFactory" ref="topicListenConnectionFactory" />      
  57.         <property name="pubSubDomain" value="true"/><!-- default is false -->    
  58.         <property name="destination" ref="myTopic" />  <!-- listen topic: myTopic -->    
  59.         <property name="subscriptionDurable" value="true"/>    
  60.         <property name="clientId" value="clientId_001"/>    
  61.         <property name="messageListener" ref="topicMessageReceiver" />      
  62.     </bean>    
  63.         
  64.     <!-- 主题消息监听容器2 -->    
  65.     <bean id="listenerContainer2"      
  66.         class="org.springframework.jms.listener.DefaultMessageListenerContainer">      
  67.         <property name="connectionFactory" ref="topicListenConnectionFactory" />      
  68.         <property name="pubSubDomain" value="true"/><!-- default is false -->    
  69.         <property name="destination" ref="myTopic2" />  <!-- listen topic: myTopic2 -->    
  70.         <property name="subscriptionDurable" value="true"/>    
  71.         <property name="clientId" value="clientId_002"/>    
  72.         <property name="messageListener" ref="topicMessageReceiver" />      
  73.     </bean>    
  74. </beans>    

单元测试代码:
  1. public class TechTest extends TestCase {    
  2.     ApplicationContext ptpApplicationContext;    
  3.     ApplicationContext topicApplicationContext;    
  4.         
  5.     @Override    
  6.     protected void setUp() throws Exception {    
  7.         super.setUp();    
  8.         ptpApplicationContext = new ClassPathXmlApplicationContext(    
  9.                 "com/normandy/tech/test/ptpContext.xml");    
  10.         topicApplicationContext = new ClassPathXmlApplicationContext(    
  11.                 "com/normandy/tech/test/topicContext.xml");    
  12.     }    
  13.         
  14.     protected Object getPtpBean(String name) {    
  15.         return ptpApplicationContext.getBean(name);    
  16.     }    
  17.         
  18.     protected Object getTopicBean(String name) {    
  19.         return topicApplicationContext.getBean(name);    
  20.     }    
  21. }    

  1. public class JmsQueueTest extends TechTest {    
  2.         
  3.     /**   
  4.      * 测试消息发送   
  5.      */    
  6.     public void testQueueSend() {    
  7.         long beginTime = System.currentTimeMillis();    
  8.         // PTP    
  9. //      MessageSender messageSender = (MessageSender) getPtpBean("messageSender");    
  10. //      messageSender.sendMessage();    
  11.             
  12.         // TOPIC    
  13.         MessageSender messageSender = (MessageSender) getTopicBean("topicMessageSender");    
  14.         messageSender.sendMessage();    
  15.         System.out.println("cost time:"+ (System.currentTimeMillis() - beginTime));    
  16.     }    
  17. }    

测试结果执行便可。

在这里,消息系统我们采用的是activemq,试想一个问题,如果消息过多,这个时候发生了宕机,消息是否会丢失?

这里就涉及到了一个新问题,即消息持久化。

activemq的消息持久化分成两种:文件和数据库(支持MySQL/oracle)。可以再其配置文件中进行配置,activemq配置文件采用的是spring的方式,所以配置起来非常的方便。

通常下载了activemq后,会有一系列的配置文件demo,可以参照其中的样例修改即可。

这里我们使用MySQL作为消息持久化的数据库服务器。

将mysql的驱动包,拷贝到activemq的lib目录,配置如下:

conf/activemq.xml

  1. <persistenceAdapter>    
  2.             <!--<kahaDB directory="${activemq.base}/data/kahadb"/>-->    
  3.              <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#mysql-ds"/>    
  4.         </persistenceAdapter>    

  1. <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">    
  2.         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>    
  3.         <property name="url" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true"/>    
  4.         <property name="username" value="root"/>    
  5.         <property name="password" value="root"/>    
  6.         <property name="maxActive" value="200"/>    
  7.         <property name="poolPreparedStatements" value="true"/>    
  8.     </bean>   

特别注意的是,这里指定的数据库名称,需要事先在mysql中创建好schema。

运行activemq,可以发现自动创建了三张表:

activemq_acks

activemq_lock

activemq_msgs


JMS 消息传送模式、消息签收以及spring jmsTemplate配置

Activemq支持两种消息传送模式:PERSISTENT (持久消息)和 NON_PERSISTENT(非持久消息)

从字面意思就可以了解,这是两种正好相反的模式。

1、PERSISTENT 持久消息

是activemq默认的传送方式,此方式下的消息在配合activemq.xml中配置的消息存储方式,会被存储在特定的地方,直到有消费者将消息消费或者消息过期进入DLQ队列,消息生命周期才会结束。

此模式下可以保证消息只会被成功传送一次和成功使用一次,消息具有可靠性。在消息传递到目标消费者,在消费者没有成功应答前,消息不会丢失。所以很自然的,需要一个地方来持久性存储。

如果消息消费者在进行消费过程发生失败,则消息会被再次投递。

 

2、NON_PERSISTENT 非持久消息

非持久的消息适用于不重要的,可以接受消息丢失的哪一类消息,这种消息只会被投递一次,消息不会在持久性存储中存储,也不会保证消息丢失后的重新投递。

在spring提供的JmsTemplate中,同样提供了针对于当前功能的配置选项:
  1. <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">    
  2.         <property name="connectionFactory" ref="cachingConnectionFactory"></property>    
  3.         <property name="defaultDestination" ref="dest" />    
  4.         <property name="messageConverter" ref="messageConverter" />    
  5.         <property name="pubSubDomain" value="false" />    
  6.         <property name="explicitQosEnabled" value="true" /> <!-- deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认false-->    
  7.         <property name="deliveryMode" value="1" /> <!-- 发送模式  DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久-->    
  8.     </bean>   

消息的签收模式:

客户端成功接收一条消息的标志是一条消息被签收,成功应答。

消息的签收情形分两种:

1、带事务的session

 如果session带有事务,并且事务成功提交,则消息被自动签收。如果事务回滚,则消息会被再次传送。

2、不带事务的session

 不带事务的session的签收方式,取决于session的配置。

Activemq支持一下三种模式:

  Session.AUTO_ACKNOWLEDGE  消息自动签收
  Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收
  Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送。在第二次重新传递消息的时候,消息头的JmsDelivered会被置为true标示当前消息已经传送过一次,客户端需要进行消息的重复处理控制。

spring提供的JmsTemplate中的配置方式:

  1. <!-- PTP jms模板 -->    
  2.     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">    
  3.         <property name="connectionFactory" ref="cachingConnectionFactory"></property>    
  4.         <property name="defaultDestination" ref="dest" />    
  5.         <property name="messageConverter" ref="messageConverter" />    
  6.         <property name="pubSubDomain" value="false" />    
  7.         <property name="sessionAcknowledgeMode" value="1" />     
  8. <!-- 消息应答方式    
  9.         Session.AUTO_ACKNOWLEDGE  消息自动签收    
  10.         Session.CLIENT_ACKNOWLEDGE  客户端调用acknowledge方法手动签收    
  11.         Session.DUPS_OK_ACKNOWLEDGE 不必必须签收,消息可能会重复发送    
  12.         -->    
  13.     </bean>   

相关文章推荐

JMS学习十一(Spring+ActiveMQ消息持久化,Topic持久化订阅)

消息持久化就是将消息保存到磁盘,这样的好处就是如果服务挂了,则消息还保存在磁盘不会丢失,服务起来后还能找到消息并在此发送,消息的持久化和消息的发送模型是没有关系的。 消息持久化的配置很方便的,所以其他...
  • QH_JAVA
  • QH_JAVA
  • 2017年03月14日 18:06
  • 2629

ActiveMQ与Spring整合:(2)数据的持久化

由于本系列文章并不是介绍activemq的基本概念和使用,不会activemq基本概念的人可能会觉得云里雾里的,所以最好找一些介绍activemq基本使用的文章。本篇文章主要继承上一篇博客,重点介绍使...

深入浅出JMS(四)--Spring和ActiveMQ整合的完整实例

我们基于Spring+JMS+ActiveMQ+Tomcat,做一个Spring4.1.0和ActiveMQ5.11.1整合实例,实现了Point-To-Point的异步队列消息和PUB/SUB(发布...

使用JmsTemplate发送持久化和非持久化消息

由于JmsTemplate内部发送消息默认为持久化的,所以不用设置即可发送持久化消息。 如果要发送非持久化的消息,请按照以下配置即可: 对于发送非持久化消息需要配置explicit...

ActiveMQ(三)———spring消息持久化配置

一、Topic与Queue比较1、Topic Publish Subscribe messaging 发布订阅消息。 topic数据默认不落地,是无状态的。 并不保证publisher发布的每条数据,...

[jms那点事儿] JMS 消息传送模式、消息签收以及spring jmsTemplate配置

Activemq支持两种消息传送模式:PERSISTENT (持久消息)和 NON_PERSISTENT(非持久消息) 从字面意思就可以了解,这是两种正好相反的模式。1、PERSISTENT 持久消息...
  • quzishen
  • quzishen
  • 2011年01月12日 14:27
  • 13295

spring的jms:用spring的JmsTemplate发送和接收消息 .

Spring是一个完整的J2EE框架和解决方案,完整并且模块化,你可以用的他的全部也可以只用他的一部分特性,Spring 包含 IoC, AOP, 事务处理,ORM, 也包括一个简单的MVC框架. ...

activemq+spring 持久化发送消息

最近有项目,需要用到 jms来提高性能。在网上查了下 ,发现 activeMQ 不错,所以就在晚上搜了些资料。 在网上找的资料如下(相当给力): 1、先...

activemq+spring 持久化发送消息

最近的一个项目里,访问压力过大,需要异步处理一些不需要即使处理的请求。 于是考虑用JMS,开始考察了几个jms服务,显示看了openJMS,实验了一下,发现他和spring的结合不是很好,而且只支持...
  • wo240
  • wo240
  • 2016年01月04日 15:56
  • 374

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:activemq+spring 持久化发送消息
举报原因:
原因补充:

(最多只允许输入30个字)