对Active MQ的了解、封装和使用
1、了解Active MQ
1.1 什么是消息队列?
ActiveMQ是Apache所提供的采用Java来实现的开源的消息系统。主要用于流量缓冲、异步解耦、多系统之间的通信以及日志处理等。
1.2 主流消息队列的对比
1.3 Active MQ的整体结构是怎样的?
Active MQ由三部分组成:生产者
、队列
、消费者
1.3.1 Active MQ有以下两种队列模式:
-
Queue(队列)模式
点对点:Queue,不可重复消费。该模式下多个消费者监听同一个队列,每条消息只能由某一个消费者消费一次。
-
Topic(通配符)模式
又称为发布-订阅模型。消息发布这将消息发布到某个主题,而消息订阅者则从主题订阅消息。消息会被投递到所有订阅的消费者,所以一条消息会被多个消费者消费
2、简单熟悉Active MQ的可视化管理界面
3、消息的持久化方式
// 创建一个生产者
MessageProducer producer = session.createProducer(destination);
//设置消息持久化
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
这个持久化是指消息发送到队列,持久化到硬盘,默认是开启的,重启MQ之后,消息依然存在。否则,这个消息只会保存在内存,重启后会丢失。
需要注意的是,在topic模式下,不使用持久化订阅者。即使设置持久化,也没法让订阅者离线后继续接收消息。如果使用持久化订阅者,不使用持久化,则重启后保存在临时队列中的消息丢失。
4、Active MQ 如何保证消息的可靠性
消息的成功消费通常包含三个阶段:客户接收消息
、客户处理消息
、消息被确认
。消息被确认后,消费者无法再次读取,相反如果消息未被确认,则消费者可以一直读取。
Session javax.jms.Connection.createSession(boolean transacted, int acknowledgeMode) throws JMSException
- 事务模式
由transacted参数控制。
-
transacted 为 true 时:表示事务性会话,此时当一个事务被提交时(调用session.commit()确认消息),确认自动发生。此时与 acknowledgeMode 参数设置无关。
-
transacted 为 false 时:表示非事务性会话,此时消息何时被确认取决于创建会话时的应答模式(acknowledgeMode)。与 acknowledgeMode 参数有关。
- ACK应答/确认模式
类型 | 描述 |
---|---|
Session.AUTO_ACKNOWLEDGE | 自动确认。当客户成功的从 receive 方法,或者从 MessageListener.onMessage 方法返回的时候,会话自动确认客户收到的消息。 |
Session.CLIENT_ACKNOWLEDGE | 客户确认。客户通过调用消息(Session)的 acknowledge() 方法确认消息。client 确认模式是在会话层上进行,只要确认一个被消费的消息,将自动确认当前会话中所有已经被消费的消息。比如一个会话中消费者消费了10个消息,然后确认了第8个消息,那么这10个消息都会被确认。 |
Session.DUPS_ACKNOWLEDGE | 迟钝确认。该模式只是会话迟钝的确认消息的提交。如果 JMS Provider 失败,那么可能会导致一些重复的消息。如果是重复的消息,那么 JMS provider 必须把消息头的 JMSRedelivered 字段设置为 true。不常用。 |
Session.SESSION_TRANSACTED | 当transacted为true时,默认就是这种模式,使用事务提交的方式确认消息 |
ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE | 单条消息确认,这种确认模式,它的确认时机和CLIENT_ACKNOWLEDGE几乎一样,当消息消费成功之后,需要调用message.acknowledege来确认此消息(单条),而CLIENT_ACKNOWLEDGE模式先message.acknowledge()方法将导致整个session中所有消息被确认(批量确认) |
5、创建消费者的几种方法
//创建普通消费者
MessageConsumer createConsumer(Destination destination);
MessageConsumer createConsumer(Destination destination, String messageSelector);
MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal);
//创建持久化订阅消费者
TopicSubscriber createDurableSubscriber(Topic topic, String name);
TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal);
参数解释:
Destination 和 Topic 分别都代表队列。
name:持久化订阅消费者名称。
messageSelector:消息选择器
noLocal:noLocal标志默认为false,当设置为true时限制消费者只能接收和自己相同的连接(Connection)所发布的消息,此标志只适用于主题,不适用于队列
关于消息选择器
消息选择器是用于MessageConsumer的过滤器,可以用来过滤传入消息的属性和消息头部分(但不过滤消息体),并确定是否将实际消费该消息。按照JMS文档的说法,消息选择器是一些字符串,它们基于某种语法,而这种语法是SQL-92的子集。可以将消息选择器作为MessageConsumer创建的一部分。
例如:
public final String SELECTOR = “JMSType = ‘TOPIC_PUBLISHER’”;
该选择器检查了传入消息的JMSType属性,并确定了这个属性的值是否等于TOPIC_PUBLISHER。如果相等,则消息被消费;如果不相等,那么消息会被忽略。
6、在普通java项目中如何使用Active MQ
- 生产者推送字符串消息:
// 创建一个生产者
MessageProducer producer = session.createProducer(destination);
// 创建map消息
MapMessage mapMessage = session.createMapMessage();
for (Entry<String, String> item : message.entrySet()) {
mapMessage.setString(item.getKey(), item.getValue());
}
// 发送消息
producer.send(mapMessage);
- 消费者获取字符串消息:
if (message instanceof MapMessage) {
final MapMessage mapMessage=(MapMessage) message;
//取出字符串消息
System.out.println("消费的消息client:" + textMessage.getText());
}
- 生产者推送map消息:
// 创建一个生产者
MessageProducer producer = session.createProducer(destination);
// 创建文本消息
TextMessage message = session.createTextMessage("一条文本消息");
// 发送消息
producer.send(message);
- 消费者获取map消息:
if (message instanceof MapMessage) {
final MapMessage mapMessage=(MapMessage) message;
//取出map消息
Map<String,String> params = new HashMap<String,String>();
Enumeration<String> keys = mapMessage.getMapNames();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = mapMessage.getString(key);
params.put(key, value);
}
}
添加jar包
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.9.0</version>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
Queue模式
消息生产者
package com.study.queue;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* 生产者
*/
public class Producer {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin","admin");
// 打开连接
connection.start();
// 创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据,在生产者段,使用哪种确认方式都无所谓
Destination destination = session.createQueue("myQueue");
// 创建一个生产者
MessageProducer producer = session.createProducer(destination);
// 向队列推送10个文本消息数据
for (int i = 1; i <= 10; i++) {
// 创建文本消息
TextMessage message = session.createTextMessage("第" + i + "个文本消息");
// 发送消息
producer.send(message);
// 在本地打印消息
System.out.println("已发送的消息:" + message.getText());
}
// 关闭连接
connection.close();
}
}
消费者(使用非事务、消息自动ACK确认的模式)
- 使用MessageListener的方式监听消息
package com.study.queue.autoack;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* 消费者
*/
public class Consumer {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin", "admin");
// 打开连接
connection.start();
// 创建会话,自动ACK确认
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
Destination destination = session.createQueue("myQueue");
// 创建消费者
MessageConsumer consumer = session.createConsumer(destination);
// 创建消费的监听
consumer.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费的消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
- 使用receive方法阻塞线程,直到接收到消息
package com.study.queue.clientack;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* 消费者
*/
public class Consumer {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin","admin");
// 打开连接
connection.start();
// 创建会话,手动ACK确认
Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
Destination destination = session.createQueue("myQueue");
// 创建消费者
MessageConsumer consumer = session.createConsumer(destination);
// 用阻塞的方式监听队列消息
while(true)
{
Message message = consumer.receive();//receive方法会导致当前线程阻塞,直到接收到消息
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;//消费者接收消息。因为对方发送的 Map 消息,所以可以强转
System.out.println("消费的消息client ACK:" + textMessage.getText());
textMessage.acknowledge();//应答/确认消息
}
}
}
}
Topic模式
注意:topic模式必须先启动消费者,否则无法消费启动消费者之前发送的消息
消息生产者
package com.study.topic;
import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Producer {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin","admin");
// 打开连接
connection.start();
// 创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
Destination destination = session.createTopic("topicTest");
// 创建一个生产者
MessageProducer producer = session.createProducer(destination);
//设置消息持久化,可以不设置,默认就是持久化到硬盘
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
// 向队列推送10个文本消息数据
for (int i = 1; i <= 10; i++) {
// 创建文本消息
TextMessage message = session.createTextMessage("第" + i + "个文本消息");
// 发送消息
producer.send(message);
// 在本地打印消息
System.out.println("已发送的消息:" + message.getText());
}
// 关闭连接
connection.close();
}
}
消费者
- 普通订阅者模式(该模式下和Queue的消费者基本一致。只是创建队列的时候使用session.createTopic(“topicTest”))
package com.study.topic;
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* topic模式必须先启动消费者,否则无法消费启动消费者之前发送的消息
*/
public class Consumer2 {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin","admin");
// 打开连接
connection.start();
// 创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
Destination destination = session.createTopic("topicTest");
// 创建消费者
MessageConsumer consumer = session.createConsumer(destination);
// 创建消费的监听
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者1的消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
- 持久订阅者模式
注意:若要持久订阅者在离线后依然可以获取订阅队列的消息
1、必须先注册持久订阅者,即先启动消费者,在启动生产者。
2、必须保证订阅者重启后,ClientID、队列名、持久化订阅者名称和离线前注册时一致,如果有一项不一致,则无法获取到消息
大概原理:持久订阅时,客户端Sub向服务器注册一个自己身份的ID、订阅队列名称和消费者名称,当这个客户端Sub处于离线时,Pub 会为注册的订阅者 保存所有发送到主题的消息,当客户端Sub再次连接到Pub时,会根据自己的注册时的信息得到所有当自己处于离线时发送到主题的消息
package com.study.topic;
import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import org.apache.activemq.ActiveMQConnectionFactory;
/**
* topic 持久订阅者模式,该模式下,订阅者会持久化,当订阅者离线后,该订阅者订阅的Topic的消息会用一个队列存储起来。直到该订阅者上线,就从暂存的队列推送给订阅者。
* 该模式依然要先启动消费者,先注册持久订阅者,否则依然接收不到注册之前的消息
* @author yanlei.wb
*
*/
public class Consumer3 {
private static final String ACTIVEMQ_URL = "tcp://127.0.0.1:61616";
public static void main(String[] args) throws JMSException {
// 创建连接工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
// 创建连接
Connection connection = activeMQConnectionFactory.createConnection("admin","admin");
//连接的id,该id必须唯一,针对于整个MQ服务,只能有一个连接的id为client_id_1
connection.setClientID("client_id_1");
// 打开连接
connection.start();
// 创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建队列目标,并标识队列名称,消费者根据队列名称接收数据
Topic topic = session.createTopic("topicTest");
// 创建消费者
TopicSubscriber consumer = session.createDurableSubscriber(topic, "consumer_name1");
// 创建消费的监听
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("消费者3的消息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
7、尝试对Active MQ进行封装(暂时仅针对Queue)
1、封装需要实现的效果:
- 使用客户端确认的方式,消费一条消息未抛出异常时,发送ACK确认消费。
- 维护Active MQ连接数量根据压力实现动态伸缩,并实现连接的重复利用。
- 封装发送消息的方法。
- 抽象出一个消费者抽象类,所有的消费者只需继承该类,重写抽象方法,在方法内实现消费逻辑。
- 支持配置多线程消费消息。
2、项目git地址
8、在spring中如何使用Active MQ
配置部分
项目公共配置
schema约束:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/jms
http://www.springframework.org/schema/jms/spring-jms-4.3.xsd
http://activemq.apache.org/schema/core
http://activemq.apache.org/schema/core/activemq-core-5.7.0.xsd">
</beans>
properties配置文件读取:
<bean id="propertyPlaceholder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:config.properties</value>
</list>
</property>
</bean>
第一步:添加依赖
activeMQ相关依赖:
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-spring</artifactId>
<version>3.16</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.12.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
spring相关依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
第二步:定义ActiveMQ连接工厂
<!-- ActiveMQ 连接工厂 :真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供 -->
<amq:connectionFactory id="amqConnectionFactory"
brokerURL="${activemq.ip}" userName="${activemq.username}" password="${activemq.password}" />
或者
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="clientID" value="test.clientID"/>
<property name="brokerURL" value="${activemq.ip}"/>
<property name="userName" value="${activemq.username}"/>
<property name="password" value="${activemq.password}"/>
</bean>
第三步:使用Spring Caching连接工厂 对第二步的连接工厂进行封装
- 这样做的目的是提升性能,对sessions, connections 和 producers进行缓存复用,减少开销。
<!-- Spring Caching连接工厂 -->
<bean id="connectionFactory" class="org.springframework.jms.connection.CachingConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="amqConnectionFactory"></property>
<!-- Session缓存数量 -->
<property name="sessionCacheSize" value="100" />
</bean>
第四步:构建JmsTemplate(通过JmsTemplate发送消息到队列)
这里配置了两个JmsTemplate,一个用于发送订阅型消息。一个用于发送队列型消息
<!-- 定义JmsTemplate的Queue类型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!--构造参数,传入连接工厂对象 -->
<constructor-arg ref="connectionFactory" />
<!-- 非pub/sub模型(发布/订阅),即队列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定义JmsTemplate的Topic类型 :对于Topic而言,一条消息只有所有的订阅者都消费才会被删除-->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!--构造参数,传入连接工厂对象 -->
<constructor-arg ref="connectionFactory" />
<!-- pub/sub模型(发布/订阅) -->
<property name="pubSubDomain" value="true" />
</bean>
第五步:定义队列监听器容器(在监听容器中添加消费者)
<!-- 定义Queue监听器(定义消费者) -->
<jms:listener-container destination-type="queue"
container-type="default" connection-factory="connectionFactory"
acknowledge="auto">
<!-- <jms:listener destination="队列名称" ref="消费者bean id"/> -->
<jms:listener destination="my-queue" ref="MyConsumerQueue1" />
<jms:listener destination="my-queue" ref="MyConsumerQueue2" />
</jms:listener-container>
<!-- 定义Topic监听器(定义消费者) -->
<jms:listener-container client-id="test.clientID" destination-type="durableTopic"
container-type="default" connection-factory="connectionFactory"
acknowledge="auto">
<!-- <jms:listener destination="队列名称" ref="消费者bean id"/> -->
<jms:listener subscription="Topic1-Name" destination="my-topic" ref="MyConsumerTopic1" />
<jms:listener subscription="Topic2-Name" destination="my-topic" ref="MyConsumerTopic2" />
</jms:listener-container>
配置说明:
jms:listener-container:
destination-type
:container-type
:container的类型,值可以是
default ( DefaultMessageListenerContainer )
simple (SimpleMessageListenerContainer)connection-factory
:连接工厂acknowledge
:确认方式
jms:listener:
destination
:队列名称ref
:消费者bean idsubscription
:持久化订阅者的名称
若使用持久化订阅者,则还需要在第二步配置clientID
。可以参考上面的在普通java项目中使用Topic模式时讲到的【持久订阅者模式】。
第六步:配置spring 包扫描
<context:component-scan base-package="com.study"/>
第七步:配置log4j
添加log4j配置文件,不然启动报错
代码部分
第一步:编写消费者代码,
消费者继承MessageListener,并纳入spring容器,将消费者的bean Id在【配置部分】第五步中配置在队列监听容器中。
- Queue消费者
package com.study.consumer;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;
@Component(value="MyConsumerQueue1")
public class MyConsumerQueue1 implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("Queue1收到的信息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
- Topic消费者
package com.study.consumer;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import org.springframework.stereotype.Component;
@Component(value="MyConsumerTopic1")
public class MyConsumerTopic1 implements MessageListener {
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("Topic1收到的信息:" + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
第二步:编写消费生产者
消息生产者,简单封装了4个方法,分别是向Queue和Topic这两种模式的队列中发送String消息和Map消息。
需要注意的是,在向这两种模式发送消息的时候使用的JmsTemplate 对象是不同的。这个对象是在在【配置部分】第四步中配置。
package com.study.producer;
import java.util.Map;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Component;
@Component
public class MessageProducer {
@Autowired
@Qualifier("jmsQueueTemplate")
private JmsTemplate queuejmsTemplate;
@Autowired
@Qualifier("jmsTopicTemplate")
private JmsTemplate topicjmsTemplate;
/**
* 发送一条消息到指定的队列(队列模式)
*
* @param queueName 队列名称
* @param message 消息内容
*/
public void sendQueue(String queueName, final String message) {
queuejmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(message);
}
});
}
/**
* 发送一条消息到指定的队列(Topic模式)
*
* @param queueName 队列名称
* @param message 消息内容
*/
public void sendTopic(String queueName, final String message) {
topicjmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(message);
}
});
}
/**
* 发送一条消息到指定的队列(队列模式)
*
* @param queueName 队列名称
* @param message 消息内容
*/
public void sendQueueMap(String queueName, final Map<String, String> message) {
queuejmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
for (Map.Entry<String, String> entry : message.entrySet()) {
mapMessage.setString(entry.getKey(), entry.getValue());
}
return mapMessage;
}
});
}
/**
* 发送一条消息到指定的队列(Topic模式)
*
* @param queueName 队列名称
* @param message 消息内容
*/
public void sendTopicMap(String queueName, final Map<String, String> message) {
topicjmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
MapMessage mapMessage = session.createMapMessage();
for (Map.Entry<String, String> entry : message.entrySet()) {
mapMessage.setString(entry.getKey(), entry.getValue());
}
return mapMessage;
}
});
}
}
第三步:测试
@Test
public void demo01() throws InterruptedException{
String xmlPath = "applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
MessageProducer messageProducer = (MessageProducer) applicationContext.getBean("messageProducer");
for (int i = 0; i < 3; i++) {
messageProducer.sendQueue("my-queue", "queue:消息内容"+i);
}
for (int i = 0; i < 3; i++) {
messageProducer.sendTopic("my-topic", "topic:消息内容"+i);
}
}
结果如下:
9、在Spring boot中如何使用Active MQ
项目文件结构以及说明
第一步:搭建一个Spring Boot项目
项目完整pom.xml如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.study</groupId>
<artifactId>ActiveMQ-SpringBoot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- activemq 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- springboot aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springboot-log4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!-- 排除spring-boot-starter-web中自带的logback依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<!-- provided:代表在编译和测试的时候用,运行,打包的时候不会打包进去 -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- test:表示当前依赖包只参与测试时的工作:比如Junit -->
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 设定主配置类 -->
<configuration>
<mainClass>com.study.AppStart</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
第二步:引入activemq相关的jar包依赖
<!-- activemq 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>
其中activemq-pool包,若配置spring.activemq.pool.enabled=true
则必须要引入,该配置的为true作用是启用连接池,所以必须要引入连接池的jar包
第三步:在application.properties文件中配置activeMQ
#配置端口和访问前缀
server.port=8080
server.servlet.context-path=/study
# activemq的账户、密码、访问地址
spring.activemq.user=admin
spring.activemq.password=admin
spring.activemq.broker-url=tcp://127.0.0.1:61616
# 最大连接数
spring.activemq.pool.max-connections=10
# 是否用Pooledconnectionfactory代替普通的ConnectionFactory,设置为true,则需要引入activemq-pool
spring.activemq.pool.enabled=true
# 是否信任所有包
spring.activemq.packages.trust-all=true
# 空闲的连接过期时间,默认为30秒
spring.activemq.pool.idle-timeout=30000
# 每个连接的有效会话的最大数目。
spring.activemq.pool.maximum-active-session-per-connection=500
# 队列名配置,这个配置不要求一定是以spring.activemq开头
spring.activemq.queue-name=queue_name
spring.activemq.topic-name=topic_name
第四步:添加log4j配置文件,不然会报错
第五步:编写一个类,定义ConnectionFactory、JmsMessagingTemplate、JmsListenerContainerFactory的对象,并纳入spring容器管理
ConnectionFactory
:创建连接
JmsMessagingTemplate
:类似于JdbcTemplate,用户发送消息到ActiveMQ
JmsListenerContainerFactory
:个人理解,从名字上来看可以理解为管理activeMQ中的每个队列监听者的容器的工厂。我们需要知道这个对象大概的作用,是用来管理消息监听者(即消费者)
package com.study.activeMQ.config;
import javax.jms.ConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.SimpleJmsListenerContainerFactory;
import org.springframework.jms.core.JmsMessagingTemplate;
@Configuration
public class ActiveMQConfig {
@Value("${spring.activemq.broker-url}")
private String brokerUrl;
@Value("${spring.activemq.user}")
private String username;
@Value("${spring.activemq.password}")
private String password;
@Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(username, password, brokerUrl);
}
@Bean("jmsMessageTemplate")
public JmsMessagingTemplate jmsMessageTemplate(ConnectionFactory connectionFactory) {
return new JmsMessagingTemplate(connectionFactory);
}
// 在Queue模式中,对消息的监听需要对containerFactory进行配置
@Bean("queueListener")
public JmsListenerContainerFactory<?> queueJmsListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
// 在Topic模式中,对消息的监听需要对containerFactory进行配置
@Bean("topicListener")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
第六步:创建消费者
- 创建queue模式消费者
package com.study.activeMQ.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class QueueConsumerListener
{
private static final Logger logger = LoggerFactory.getLogger(QueueConsumerListener.class);
//queue模式的消费者
@JmsListener(destination="${spring.activemq.queue-name}", containerFactory="queueListener")
public void readActiveQueue(String message) {
logger.info("queue接受到:" + message);
}
}
- 创建Topic模式消费者
topic模式我创建了两个消费者,这两个消费者会轮询消费同一个队列的消息。
package com.study.activeMQ.consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class TopicConsumerListener
{
private static final Logger logger = LoggerFactory.getLogger(TopicConsumerListener.class);
//topic模式的消费者
@JmsListener(destination="${spring.activemq.topic-name}", containerFactory="topicListener")
public void readActiveQueue1(String message) {
logger.info("topic1接受到:" + message);
}
//topic模式的消费者
@JmsListener(destination="${spring.activemq.topic-name}", containerFactory="topicListener")
public void readActiveQueue2(String message) {
logger.info("topic2接受到:" + message);
}
}
第七步:消息生产者工具类
在这里我只封装了一个发送文本消息的方法
package com.study.activeMQ.producer;
import javax.jms.Destination;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
public class MessageProducer {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 发送消息,destination是发送到的队列,message是待发送的消息
* @param destination
* @param message
*/
public void sendMessage(Destination destination, final String message){
jmsMessagingTemplate.convertAndSend(destination, message);
}
}
第八步:测试
启动activeMQ。启动spring boot,调用controller实现向队列中发送消息,即可看到消息被消费的日志打印。
package com.study.controller;
import javax.jms.Destination;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.activemq.command.ActiveMQTopic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.study.activeMQ.consumer.QueueConsumerListener;
import com.study.activeMQ.producer.MessageProducer;
@RestController
//@PropertySource("classpath:application-int1.properties")
public class ProducerController
{
private static final Logger logger = LoggerFactory.getLogger(ProducerController.class);
@Value("${spring.activemq.queue-name}")
private String queueName;
@Value("${spring.activemq.topic-name}")
private String topicName;
@Autowired
private MessageProducer messageProducer;
//http://127.0.0.1:8080/study/queue/test?msg=aaaaaaaaa
@GetMapping("/queue/test")
public String sendQueue(@RequestParam String msg) {
Destination queue = new ActiveMQQueue(queueName);
messageProducer.sendMessage(queue, msg);
logger.info("消息:["+msg+"]发送到"+queueName);
return "success";
}
//http://127.0.0.1:8080/study/topic/test?msg=bbbbbbbbb
@GetMapping("/topic/test")
public String sendTopic(@RequestParam String msg) {
Destination topic = new ActiveMQTopic(topicName);
messageProducer.sendMessage(topic, msg);
logger.info("消息:["+msg+"]发送到"+topicName);
return "success";
}
}
以下是写的非常好的相关文章地址:
[1]: https://www.cnblogs.com/juepei/p/4262981.html