之前有用过activeMq这种消息中间件,但是通过配置文件的方式,现在spring都越来越推行注解,所以写一个小demo来记录一下。
1.什么是ActiveMQ
ActiveMQ是Apache出品的一款基于JMS规范的消息中间件。
2.什么是JMS
Java Message Service(JMS)是SUN提出的旨在统一各种MOM(Message-Oriented Middleware )系统接口的规范,它包含点对点(Point to Point,PTP)和发布/订阅(Publish/Subscribe,pub/sub)两种消息模型,提供可靠消息传输、事务和消息过滤等机制。简单来说,jms定制一个规范,而ActiveMQ就是这个规范的实现。(就如同 jdbc规范,和各个数据库厂商基于这个规范提供的jdbc驱动。)其中JMS定义了五种不同的消息正文格式
- StreamMessage – Java原始值的数据流
- MapMessage–一套名称-值对
- TextMessage–一个字符串对象(最常用)
- ObjectMessage–一个序列化的 Java对象
- BytesMessage–一个字节的数据流
3.ActiveMQ的消息形式
(1) 队列(queue) :点对点消息通信(point-to-point)
消息发送者发送消息,并将发送的消息放在 一个队列当中,接收消息的一方从队列当中获取消息,消息被接收后,将移出消息队列。
(2)主题(topic) :发布(publish) /订阅(subscribe)消息通信
消息发的发送者发送消息到主题,多个订阅者(消息的接受者),订阅了这个主题,那么消息发布到主题时,多个订阅者都能接收到消息。
4.ActiveMQ的作用以及使用场景。
大多应用中,可通过消息服务中间件来提升系统异步通信、扩展解耦能力。
(1)异步处理
比如:户注册业务,用户注册完之后,需要给用户发送注册邮件,注册 与发送邮件是同步执行的。但是注册完成的通知邮件,并不需要立即给用户发送,可以把这个消息发送给消息队列,然后发送邮寄这个功能异步读取,消息队列里面的内容,然后执行相关操作,可以提高系统的运行速度。
(2)应用解耦
比如:我们常见的场景,用户下一个订单之后,我们要调用库存系统,来进行相关业务,如果是在订单系统中 直接调用库存系统,那么两个系统的耦合度就会比价高,所以可以通过消息中间件来进行系统解耦。
(3)流量消减
比如处理秒杀系统的一种解决方案,如果每次用户的请求都直接发送给秒杀处理业务,系统将承受很大的压力,所以可以将秒杀的用户安装先后顺序,进入消息队列,设置消息队列的容量,队列满了之后,就无法获取再进队列,然后系统从消息对队列中获取消息,处理秒杀之后的相关操作。
5.spring整合ActiveMQ
(1)创建测试工程
(2)在父工程的pom.xml导入相关依赖
<!-- 导入相关依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!-- jsm整合规范包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
<!-- activemq先关依赖-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.11.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
相关配置文件信息conf.properties
activemq.url=tcp://192.168.25.128:61616
activemq.queueName=message-queue
activemq.topicName=message-topic
(3)配置生产者
/**
*
* 配置activeMq的发送者
* @author Hao
* @date: 2019年6月9日 上午11:06:51
* @Description:
* 参照配置版本
* : <!-- 配activemq -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
<!-- 配置生产者 -->
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!--这个是队列目的地,点对点的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="Itemtopic" />
</bean>
</beans>
*/
import org.springframework.jms.core.JmsTemplate;
@Configuration
@PropertySource("classpath:conf.properties")
public class ProducerConfig {
@Value("${activemq.url}")
String URL;
@Value("${activemq.queueName}")
String QUEUE_NAME;
@Value("${activemq.topicName}")
String TOPIC_NAME;
@Bean
//配置ConnectionFactory用于生成connection
public ActiveMQConnectionFactory connectionFactory(){
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(URL);
return activeMQConnectionFactory;
}
@Bean
//注册SingleConnectionFactory,这个spring的一个包装工厂 用于管理真正的ConnectionFactory
public SingleConnectionFactory singleConnectionFactory(ActiveMQConnectionFactory activeMQconnectionFactory){
SingleConnectionFactory connectionFactory = new SingleConnectionFactory();
//设置目标工厂
connectionFactory.setTargetConnectionFactory(activeMQconnectionFactory);
return connectionFactory;
}
@Bean
//配置生产者,jmsTemplate
public JmsTemplate jmsTemplate(SingleConnectionFactory connectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory);
return jmsTemplate;
}
/**
* 配置队列目的的: 根据测试需要配置其中一个
* 1.队列 点对点 queue
* 2.主题 一对多 topic
* */
@Bean //
public ActiveMQQueue queueDestination(){
ActiveMQQueue activeMQQueue = new ActiveMQQueue(QUEUE_NAME);
return activeMQQueue;
}
@Bean
public ActiveMQTopic topicDestination(){
ActiveMQTopic activeMQTopic = new ActiveMQTopic(TOPIC_NAME);
return activeMQTopic;
}
}
(4)配置消费者
package com.kuake.config;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.connection.SingleConnectionFactory;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import com.kuake.component.QueueMsgListener;
/**
* <!-- 配activemq -->
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128:61616" />
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
<!--这个是队列目的地,点对点的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--这个是主题目的地,一对多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="Itemtopic" />
</bean>
<!-- 消息监听器 -->
<bean id="mytestmessage" class="com.e3shop.search.message.Mytestmessage"/>
<!-- 消息监听容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="mytestmessage" />
</bean>
<bean id="itemListenerMessage" class="com.e3shop.search.message.ItemListenerMessage"></bean>
<!-- 消息监听容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="topicDestination" />
<property name="messageListener" ref="itemListenerMessage" />
</bean>
参照,xml配置的方法 进行配置
*
* @author Hao
* @date: 2019年6月9日 下午1:56:23
* @Description:配置消费者
*/
@ComponentScan(basePackages={"com.kuake"})
@EnableJms
@Configuration
@PropertySource("classpath:conf.properties")
public class ConsumerConfig {
@Value("${activemq.url}")
String URL;
@Value("${activemq.queueName}")
String QUEUE_NAME;
@Value("${activemq.topicName}")
String TOPIC_NAME;
@Bean
//配置ConnectionFactory用于生成connection
public ActiveMQConnectionFactory connectionFactory(){
ActiveMQConnectionFactory activeMQConnectionFactory
= new ActiveMQConnectionFactory(URL);
return activeMQConnectionFactory;
}
@Bean
//注册SingleConnectionFactory,这个spring的一个包装工厂 用于管理真正的ConnectionFactory
public SingleConnectionFactory singleConnectionFactory(ActiveMQConnectionFactory activeMQconnectionFactory){
SingleConnectionFactory connectionFactory = new SingleConnectionFactory();
//设置目标工厂
connectionFactory.setTargetConnectionFactory(activeMQconnectionFactory);
return connectionFactory;
}
/*在xml当中的如下配置 效果相同
* <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
* <property name="connectionFactory" ref="connectionFactory" />
* <property name="destination" ref="topicDestination" />
* <property name="messageListener" ref="itemListenerMessage" />
* </bean>
**/
@Bean //配置topic监听容器,从ioc容器当中注入 ConnectionFactory 和 queueMsgListener
public DefaultMessageListenerContainer defaultMessageListenerContainer2(SingleConnectionFactory singleConnectionFactory,TopicMsgListener topicMsgListener,Destination destination){
//创建容器
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
//设置监听器
container.setMessageListener(topicMsgListener);
//设置连接工厂
container.setConnectionFactory(singleConnectionFactory);
//设置监听目的地的名字/也可以直接设置对象目的地
container.setDestination(destination);
return container;
}
/**
* 配置队列目的的: 测试的时候 根据需要配置其中一个
* 1.队列 点对点 queue
* 2.主题 一对多 topic
* */
@Bean
public ActiveMQQueue queueDestination(){
ActiveMQQueue activeMQQueue = new ActiveMQQueue(QUEUE_NAME);
return activeMQQueue;
}
@Bean
public ActiveMQTopic topicDestination(){
ActiveMQTopic activeMQTopic = new ActiveMQTopic(TOPIC_NAME);
return activeMQTopic;
}
}
点对点监听器:
/**
*
*
* @author Hao
* @date: 2019年6月9日 下午2:03:11
* @Description:队列消息的 接受者
*/
@Component
public class QueueMsgListener implements MessageListener{
/*
* @JmsListener(containerFactory="defaultMessageListenerContainer",destination="queueDestination")
* 经过测 发现这种方式 声明 监听的消费 者无效。几经探究,无果。
* */
public void onMessage(Message message){
TextMessage textMessage = (TextMessage) message;
System.out.println("queue监听者正在监听消息.............");
try {
System.out.println("监听者监听到消息"+textMessage.getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
订阅监听器
package com.kuake.component;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
/**
*
*
* @author Hao
* @date: 2019年6月9日 下午3:48:06
* @Description: 一对多监听容器
*/
public class TopicMsgListener implements MessageListener{
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
System.out.println("topic监听者正在监听消息.............");
try {
System.out.println("topic监听者监听到消息"+textMessage.getText());
} catch (JMSException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(5)开始单元测试
package com.kuake.test;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import com.kuake.config.ProducerConfig;
public class DemoTest {
//测试点对点
@Test
public void testActiveMq(){
AnnotationConfigApplicationContext aContext =
new AnnotationConfigApplicationContext(ProducerConfig.class);
//获得发送者的模板对象
JmsTemplate jmsTemplate = aContext.getBean(JmsTemplate.class);
Destination bean = (Destination) aContext.getBean("queueDestination");
//发送消息
jmsTemplate.send(bean, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage message = session.createTextMessage();
message.setText("this is a activemq message for queue");
return message;
}
});
}
//测试topic发送
@Test
public void testActiveMq2(){
AnnotationConfigApplicationContext aContext =
new AnnotationConfigApplicationContext(ProducerConfig.class);
//获得发送者的模板对象
JmsTemplate jmsTemplate = aContext.getBean(JmsTemplate.class);
Destination bean = (Destination) aContext.getBean("topicDestination");
//发送消息
jmsTemplate.send(bean, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage message = session.createTextMessage();
message.setText("this is a activemq message for topic");
return message;
}
});
}
}
**先进行点对点的生成者测试:**执行testActiveMq
控制台打印如下:大致可以知道,消息发送出去了
进入ActiveMQ的控制台
可以看出有一个入对列的消息,因为我们没有接收者监听,所有Number of Consumers
的数量是 0 。进入这个消息队列,查看详细信息。
消息详情就是生产者风发送过来的消息。
现在消费者开始上场,看是否能消费到该消息。测试代码:
package com.kuake.test;
import java.io.IOException;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.kuake.config.ConsumerConfig;
public class DemoTest {
@Test
public void testConsumerForQueue() throws IOException{
AnnotationConfigApplicationContext aContext =
new AnnotationConfigApplicationContext(ConsumerConfig.class);
//等待键盘输入 ,保持处于监听状态
System.in.read();
}
@Test
public void testConsumerForTopic() throws IOException{
AnnotationConfigApplicationContext aContext =
new AnnotationConfigApplicationContext(ConsumerConfig.class);
//等待键盘输入 ,保持处于监听状态
System.in.read();
}
@Test
public void testConsumerForTopic2() throws IOException{
AnnotationConfigApplicationContext aContext =
new AnnotationConfigApplicationContext(ConsumerConfig.class);
//等待键盘输入 ,保持处于监听状态
System.in.read();
}
}
测试testConsumerForQueue
控制台输出:
可以看到这个就是刚刚queue生产者,发送到消息队列的消息,现在已经监听者接收到(消费者),再次进入ActiveMQ
控制台,看看,消费和生产状况如何。
对比之前发现,pending Messages
(等待消息)的数量由 1 变为了0 ,number Consumers
和messages dequeued
的数量都从0 增加到1 说明有了一个消费者,并且消出队了一次。然后点进去查看一下详情发现,没有消息内容了,说明消息被消费了。
因为我代码设置了System.in.read();
等待键盘输入,如果没有输入就一直处于监听状态,所有只要生产者一发送一个消息到消息队列,消费者这边就可以立马接收到。
进行一对多 ,topic主题发布测试,
这次我先让topic属于监听状态,先执行testConsumerForTopic
和testConsumerForTopic2
,控制台如下:
一直处于监听状态,然后执行生产者的测试方法发布主题测试(topic)测试方法testActiveMq2
进行消息发布,发布的内容是"this is a activemq message for topic"
监听测试的控制台打印结果如下:
在发布消息之后,监听者会立马收到消息,(接收消息有一个前提,就是要先订阅这个主题,也就是先进行监听,才能收到消息。如果发布主题之后,再进行监听,是不能收到消息的)
进入ActiveMQ
控制台
记录一下基本用法 方便自己以后复习。
有关参考书籍:《ActiveMQ in Action .pdf》