项目的代码:http://git.oschina.net/jsc/ActiveMQDemo,Maven项目。
我是以配置文件appicationContext.xml为目录从上往下,其中每组配置就是一组消息队列实例,每个实例功能点不同。发送消息方面比较单调,我都写在ProducerComponent类中,监听消息方面就每个实例写一个监听器,因为功能不同。
目录:
-
ActiveMQ服务的启动、基本的配置
-
实例1:入门例子
-
实例2:使用SessionAwareMessageListener
-
实例3:使用MessageListenerAdapter
-
实例4:MessageListenerAdapter绑定其他监听方法
-
实例5:MessageListenerAdapter进行回复
-
实例6: 消息转换器MessageConverter
-
实例7:MessageListenerAdapter绑定消息转换器
-
持久化到MySql
ActiveMQ服务的启动、基本的配置
百度上搜activemq从官网上下载5.13版(目前最新版),解压后在bin目录有win32和win64两个文件夹,根据自己系统位数双击对应文件夹中的activemq.bat启动,如果显示下面信息则表示成功。
我第一次启动不成功,报的错误忘记了,但是后来也没做什么又能启动了,以后都没问题。所以不能启动请根据错误在网上找找,正常是直接双击就启动的。
项目用的是spring整合的,整合最基本的配置是:
<!-- ActiveMQ提供的连接到ActiveMQ服务的类 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<!-- ActiveMQ默认服务端口为61616 -->
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
<!-- Spring提供的jms连接消息队列的桥梁,有两个:SingleConnectionFactory和CachingConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
这三个bean是最基本的配置,后面的配置基本都是一组消息队列实例。
实例1:入门例子
<!-- 实例1 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>queueDestination</value>
</constructor-arg>
</bean>
<!-- jms提供的消息监听器器 -->
<bean id="consumerMessageListener" class="demo.jms.listener.ConsumerMessageListener" />
<bean id="consumerMessageListenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="consumerMessageListener" />
</bean>
每一个消息队列实例都需要这三个bean,第一个bean表示的是消息发送的目的地,构造器传的是队列名字,第二个bean表示谁去监听这个队列有消息了,第三个是容器,即将目的地、监听器、还有连接工厂关联起来,bean的id自己随便取。
发送消息很简单,注入jmsTemplate这个bean,调用它的send方法即可,参见ProducerComponent类。发送的消息有4种:TextMessage、BytesMessage、MapMessage和ObjectMessage。
监听器:从配置中可以看到对应的监听器类是ConsumerMessageListener
调试方法:TestDemo类testSend方法。右键该类 --> Run As -->JUnit Test
发送接受都是异步的,测试结果输出:
[生产者发送了一条文本消息] testSend的消息(0)
[ConsumerMessageListener接收到一条消息] testSend的消息(0)
[生产者发送了一条文本消息] testSend的消息(1)
[ConsumerMessageListener接收到一条消息] testSend的消息(1)
有时候是:
[生产者发送了一条文本消息] testSend的消息(0)
[生产者发送了一条文本消息] testSend的消息(1)
[ConsumerMessageListener接收到一条消息] testSend的消息(0)
[ConsumerMessageListener接收到一条消息] testSend的消息(1)
实验总结:发送消息比较简单不说了,监听器需要实现MessageListener接口,实现onMessage方法。
实例2:使用SessionAwareMessageListener
配置请从applicableContext.xml中搜索“实例2”。
与实例1不同之处:监听器实现的不是MessageListener而是SessionAwareMessageListener,onMessage多了个Session参数,表示当前的连接,可以用来发送消息,如何发送参见ConsumerSessionAwareMessageListener类。
监听器:ConsumerSessionAwareMessageListener
调试方法:TestDemo类testSend2方法。
实验总结:用MessageListener想发送消息,步骤和ProducerComponent类一样,但是连接是新建的。而SessionAwareMessageListener的Session保存的则是监听器与服务之间当前的连接,不需要新建。
实例3:使用MessageListenerAdapter
配置请从applicableContext.xml中搜索“实例3”。
其中监听器是这样配置的:
<bean id="consumerMessageListenerAdapter"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<!-- 默认调用方法handleMessage -->
<constructor-arg>
<bean class="demo.jms.listener.ConsumerMessageListenerAdapter" />
</constructor-arg>
</bean>
监听器类不需要实现任何接口,以构造参数的形式注入给该bean,默认调用handleMessage方法。
MessageListenerAdapter会把接收到的消息做如下转换:
TextMessage转换为String对象;
BytesMessage转换为byte数组;
MapMessage转换为Map对象;
ObjectMessage转换为对应的Serializable对象。
所以监听的方法的参数类型用上面4种转换后的类型接收即可。而且支持重载!例如调用的是handleMessage,可以写两个同名方法handleMessage,参数分别是String和Map,会根据发送的消息是TextMessage或MapMessage来选择对应处理方法。
监听器:ConsumerMessageListenerAdapter
调试方法:TestDemo类testSend3方法。
实验总结:监听器不需要实现任何接口,不需要跟onMessage方法一样接收一个Message参数再自己转换,可以根据消息的类型选择对应的重载方法。
实例4:MessageListenerAdapter绑定其他监听方法
配置请从applicableContext.xml中搜索“实例4”。
其中监听器是这样配置的:
<bean id="consumerMessageListenerAdapter2"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<!-- 调用其他处理方法 -->
<property name="delegate">
<bean class="demo.jms.listener.ConsumerMessageListenerAdapter2" />
</property>
<property name="defaultListenerMethod" value="receiveMessage" />
</bean>
分别注入delegate属性监听的类,defaultListenerMethod属性监听处理方法。
监听器:ConsumerMessageListenerAdapter2
调试方法:TestDemo类testSend4方法。
实验总结:有时候需要绑定其他方法时可以使用。
实例5:MessageListenerAdapter进行回复
配置请从applicableContext.xml中搜索“实例5”。
其中监听器是这样配置的:
<bean id="consumerMessageListenerAdapter3"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="demo.jms.listener.ConsumerMessageListenerAdapter3" />
</constructor-arg>
<!-- 回复的目的地 -->
<property name="defaultResponseDestination" ref="responseQueue" />
</bean>
在defaultResponseDestination属性注入回复消息的目的地,然后监听调用的方法直接return就可以了。
监听器:ConsumerMessageListenerAdapter3
调试方法:TestDemo类testSend5方法。
实验总结:回复消息比SessionAwareMessageListener更简单直接。
实例6: 消息转换器MessageConverter
配置请从applicableContext.xml中搜索“实例6”。
jms的消息转换器是MessageConverter接口,写一个类实现MessageConverter接口的toMessage和fromMessage方法,还需要给JmsTemplate的bean中配置该转换器,实例6中的配置:
<bean id="jmsTemplate2" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<!-- 指定消息转化器 -->
<property name="messageConverter" ref="emailMessageConverter" />
</bean>
当JmsTemplate配置了转换器,发送消息时调用convertAndSend方法,它会自动调用消息转换器的toMessage方法,监听器接收到消息后,需要手动调用消息监听器的fromMessage方法。如果不给JmsTemplate注入消息监听器,则convertAndSend会使用默认的转换器,和MessageListenerAdapter的自动转换功能一样。
本实例中发送的是一个Email对象,将Message中的邮件反序列化,调用objMessage.getObject()时,需要下面这段代码:
System.setProperty("org.apache.activemq.SERIALIZABLE_PACKAGES","*")
设置系统参数,参数值表示可被反序列化的包名,“*”表示所有包下的类都能被反序列化,否则会报javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker错误。目前我只想到写一个WebApplicationinitializer在spring初始化的时候设置一次这个变量,本项目代码没弄那么复杂直接在反序列化之前设置该参数。
监听器:EmailQueueListener
调试方法:TestDemo类testSend6方法。
实验总结:发送消息方面,只不过将JmsTemplate的send方法的MessageCreator参数的匿名实现方法写在消息转换器的toMessage方法中,接收消息方面,调用fromMessage是自己手动调用的:在监听器里注入消息转换器,自己手动写代码调用。
实例7:MessageListenerAdapter绑定消息转换器
配置请从applicableContext.xml中搜索“实例7”。
实例7中的监听器配置:
<bean id="emailQueueAdapter"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<!-- 默认调用方法handleMessage -->
<constructor-arg>
<bean class="demo.jms.listener.EmailQueueAdapter" />
</constructor-arg>
<!-- 消息转换器 -->
<property name="messageConverter" ref="emailMessageConverter"/>
</bean>
给属性messageConverter注入消息转化器,MessageListenerAdapter就不再使用默认的转换器,而是使用指定的进行转换,还可以给他设置空值,不做任何处理:
<property name="messageConverter">
<null/>
</property>
监听器:EmailQueueAdapter
调试方法:TestDemo类testSend7方法。
实验总结:与实例6相同的是,发送消息调用JmsTemplate的convertAndSend方法,会默认调用消息转换器的toMessage方法,不同的是,EmailQueueListener接收到消息会默认调用消息转换器的fromMessage方法,这样种方式就解决MessageConverter的fromMessage方法手动调用的问题了。
持久化到MySql
ActiveMQ接收到生产者的消息存放在内存中,所以很有必要把消息持久化到数据库里的。
打开ActiveMQ文件夹的/conf/activemq.xml中进行配置。
第一步:首先配置一个数据库连接池的bean,看别人写的贴子里,就是一个dbcp连接池配置,而我下载的5.13版本会报找不到类的错误,因为ActiveMQ没有dbcp连接池的jar包,可能之前的版本是有自带dbcp,这个版本没有,所以得自己下载。我估计这样是为了让用户自己选连接池,所以我这里选的是c3p0,因为它支持数据库断开自动重连。
从http://sourceforge.net/projects/c3p0/这里下载c3p0压缩包,取出lib文件夹里的jar包,还需要下载mysql的连接jdbc的那个驱动mysql-connector-java.jar,将这些jar放进ActiveMQ目录下的lib文件夹。
然后配置c3p0连接池,在</broker>标签下面添加这个spring的bean:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost/activemq?relaxAutoCommit=true" />
<property name="user" value="root" />
<property name="password" value="123456" />
<property name="initialPoolSize" value="5" />
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="100" />
<property name="maxIdleTime" value="600" />
<property name="acquireIncrement" value="5" />
<property name="checkoutTimeout" value="60000" />
</bean>
具体使用什么连接池,连接参数是什么可以根据自己喜好而定。
第二步:找到<persistenceAdapter>标签,原来的内容注释掉,改成如下内容:
<!--
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
-->
<persistenceAdapter>
<!-- dataSource值为:“#连接池的id” -->
<jdbcPersistenceAdapter dataSource="#dataSource"/>
</persistenceAdapter>
第三步:在mysql中建立一个叫“activemq”的数据库,就是连接池进行连接的那个数据库,名字可以自己取,只要c3p0能连上就行。重启ActiveMQ会自动在数据库中建立需要的表。
测试:将实例1的监听容器注释掉,使该组消息队列只能发送消息,不能监听到消息,执行TestDemo类的testSend方法,会看到数据库插入了还未处理的消息记录。
将监听容器取消注释,再次执行调试方法,控制台显示发送了2条消息,处理了4条消息,而之前数据库中的记录不见了。
补充:我用的是c3p0连接池是为了数据库断开重连问题,但是实际上不太可能让ActiveMQ远程连接数据库,这样效率太低,数据库和ActiveMQ应该放在一起。ActiveMQ接收消息并发送给监听器不停插入删除动作消耗性能,所以不建议数据库和业务系统一起使用。dbcp连接池的效率比c3p0高,所以推荐用dbcp。
后面想研究RabbitMQ了,ActiveMQ内容还有topic消息(本文是queue)、jms事物(实现jta),与zookeeper集群等读者可以自行百度研究,如果我有时间也会补充在本demo里。