本例用于展示Spring+ActiveMQ的简单使用,例子实现了如下几个功能:
(1)、基于java配置ActiveMQ;
(2)、使用ActiveMQ发送queue消息;
(3)、使用ActiveMQ发送topic消息;
(4)、消费ActiveMQ的queue消息;
(5)、消费ActiveMQ的topic消息;
本文只是实现Spring+ActiveMQ的简单整合使用,对于Spring+ActiveMQ讲解比较详细的博客可以参考如下文章:
http://elim.iteye.com/blog/1893038
http://blog.csdn.net/lifetragedy/article/details/51836557
首先从如下地址下载ActiveMQ的安装文件:
http://activemq.apache.org/download.html
下载对应的版本后解压压缩文件(本文使用windows 64位的开发环境)。进入对应的解压目录(如本文中的路径为apache-activemq-5.9.0\bin\win64)下执行如下脚本启动ActiveMQ:
activemq.bat
启动成功后可以使用如下链接来访问ActiveMQ的控制后台,使用默认密码登录即可(默认密码在解压目录的conf目录下的jetty-realm.properties文件中可以查看到,如本文的apache-activemq-5.9.0\conf\jetty-realm.properties文件)。
http://localhost:8161/admin
注意:5.14版本的mq需要用jdk1.7以上,否则会报错起不来。报错信息如下:
Unsupported major.minor version 51.0
主要需要导入如下两个jar包。
org.apache.activemq
activemq-core
5.7.0
org.springframework
spring-jms
4.3.7.RELEASE
本文中的配置使用java类+注解的方式配置,相关的配置源码如下:
package cn.hi_fei.ActiveMQ.configuration;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.MessageListener;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import cn.hi_fei.ActiveMQ.listener.ConsumerMessageListener;
@Configuration
@ComponentScan(value = { "cn.hi_fei.ActiveMQ.service.impl" })
public class RootConfiguration {
@Bean("targetConnectionFactory")
public ConnectionFactory getTargetConnectionFactory() {
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory();
factory.setBrokerURL("tcp://localhost:61616");
return factory;
}
/**
* Get spring MQ connection factory.
*
* @param mqFactory
* @return
*/
@Bean("springConnectionFactory")
public ConnectionFactory getSpringConnectionFactory(
ConnectionFactory targetConnectionFactory) {
SingleConnectionFactory factory = new SingleConnectionFactory();
factory.setTargetConnectionFactory(targetConnectionFactory);
return factory;
}
/**
* Get JMS template by spring connection factory.
* @param springConnectionFactory
* @return
*/
@Bean
public JmsTemplate getJmsTemplate(ConnectionFactory springConnectionFactory) {
JmsTemplate template = new JmsTemplate();
template.setConnectionFactory(springConnectionFactory);
return template;
}
/**
* Create a queue destination by active MQ.
* @return
*/
@Bean("queueDestination")
public Destination getQueueDestination() {
ActiveMQQueue queue = new ActiveMQQueue("queue");
return queue;
}
/**
* Create a queue destination by active MQ.
* @return
*/
@Bean("topicDestination")
public Destination getTopicDestination() {
ActiveMQTopic topic = new ActiveMQTopic("topic");
return topic;
}
@Bean
@Profile("customer")
public MessageListener getConsumerMessageListener() {
ConsumerMessageListener listener = new ConsumerMessageListener();
return listener;
}
@Bean
@Profile("customer")
public DefaultMessageListenerContainer getQueueMessageListenerContainer(
ConnectionFactory springConnectionFactory,
Destination queueDestination,
MessageListener consumerMessageListener) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(springConnectionFactory);
container.setDestination(queueDestination);
container.setMessageListener(consumerMessageListener);
return container;
}
@Bean
@Profile("customer")
public DefaultMessageListenerContainer getTopicMessageListenerContainer(
ConnectionFactory springConnectionFactory,
Destination topicDestination,
MessageListener consumerMessageListener) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(springConnectionFactory);
container.setDestination(topicDestination);
container.setMessageListener(consumerMessageListener);
return container;
}
}
消息生产者用于发送消息到目的地。本文中在接口IProducerService中进行定义,定义的源码如下:
package cn.hi_fei.ActiveMQ.service;
import javax.jms.Destination;
public interface IProducerService {
/**
* 发送普通的纯文本消息
* @param destination
* @param message
*/
public void sendMessage(final Destination destination,final String message);
}
package cn.hi_fei.ActiveMQ.service.impl;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
import cn.hi_fei.ActiveMQ.service.IProducerService;
@Component
public class ProducerServiceImpl implements IProducerService{
private Logger logger = Logger.getLogger(ProducerServiceImpl.class);
@Autowired
private JmsTemplate jmsTemplate;
public void sendMessage(Destination destination, final String message) {
logger.info("---------------send message by producer:" + message);
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(message);
}
});
}
}
消息监听器用于监听消息,通常用于监听、读取消息并通知消费者进行消费。本文中的消息监听器实现源码如下:
package cn.hi_fei.ActiveMQ.listener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.apache.log4j.Logger;
public class ConsumerMessageListener implements MessageListener{
private Logger logger = Logger.getLogger(ConsumerMessageListener.class);
public void onMessage(Message arg0) {
if(arg0 instanceof TextMessage) {
TextMessage tMsg = (TextMessage)arg0;
try {
logger.info(tMsg.getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
老习惯,首先创建一个测试基类(BaseTester类)用于统一加载配置,具体的测试实现在其子类中。基类的源码如下:
package cn.hi_fei.ActiveMQ;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.hi_fei.ActiveMQ.configuration.RootConfiguration;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={RootConfiguration.class})
public class BaseTester {
}
生产者测试类继承与测试基类,实现发送消息到queue和topic。在测试类中仅激活了“producer”的profile,所以不会创建消息监听容器。源码如下:
package cn.hi_fei.ActiveMQ.impl;
import javax.jms.Destination;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ActiveProfiles;
import cn.hi_fei.ActiveMQ.BaseTester;
import cn.hi_fei.ActiveMQ.service.IProducerService;
@ActiveProfiles("producer")
public class ProducerTester extends BaseTester{
private Logger logger = Logger.getLogger(ProducerTester.class);
@Autowired
private IProducerService mProducer;
@Autowired
@Qualifier("queueDestination")
private Destination mQueueDestination;
@Autowired
@Qualifier("topicDestination")
private Destination mTopicDestination;
@Test
public void sendMessage() {
logger.info("Send message to queue...");
mProducer.sendMessage(mQueueDestination, "123123123");
logger.info("Send message to queue...");
mProducer.sendMessage(mTopicDestination, "456789");
logger.info("Active producer end...");
}
}
运行此测试用例,结果如下:
图1、单独运行生产者发送消息到queue和topic
此时打开ActiveMQ的控制台可以在queue和topic中即看到发送的消息个数,但是本例中还没有相关消费者对消息进行消费。
消费者测试类也继承于测试基类,但是激活了“customer”的profile,此时消息监听器的容器创建,应用进行消息的消费。相关源码如下:
package cn.hi_fei.ActiveMQ.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.springframework.test.context.ActiveProfiles;
import cn.hi_fei.ActiveMQ.BaseTester;
@ActiveProfiles("customer")
public class CustomerTester extends BaseTester{
private Logger logger = Logger.getLogger(CustomerTester.class);
@Test
public void waitToRead() {
try {
logger.info("Wait to customer queue message.");
InputStreamReader is_reader = new InputStreamReader(System.in);
new BufferedReader(is_reader).readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
源码比较粗糙。启动后就一直执行直到用户按下任意按键退出。运行多个消费者测试类,然后再运行生产者测试类,可用于查看queue和topic的消费区别。如下为运行两个测试类实例,然后生产者测试类发送消息后消费者消费消息的结果:
图3、第2个消费者的消费结果
说明:
(1)、图2中的第一个“123123123”是之前单独运行生产者时发送的消息,被本次运行的消费者给消费了;
(2)、图2中的第二个“123123123”是两个消费者运行起来后运行生产者发送的消息,对比图3的消费结果可以看出来queue中的消息只会被一个消费者消费;
(3)、图2和图3中的消息“456789”是topic消息,对比可以看出两个消费者都收到了该主题消息。
在使用ActiveMQ5.14的时候出现ActiveMQ无法启动的问题,查看启动日志(在安装目录的data目录下),发现如下错误信息:
WrapperSimpleApp: Unable to locate the class org.apache.activemq.console.Main: java.lang.UnsupportedClassVersionError: org/apache/activemq/console/Main : Unsupported major.minor version 51.0
在运行代码时出现如下类似的错误从而导致bean创建失败,代码无法执行:
org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/StaticLoggerBinder
该问题是由于缺少log库或者log库的版本不对,导致无法找到某些方法导致。本文中的解决办法是添加如下依赖:
org.slf4j
slf4j-api
1.7.25
org.slf4j
slf4j-log4j12
1.7.25
源码可从如下地址进行下载:
http://download.csdn.net/detail/yxtouch/9835989