1.依赖
<!--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 配置
spring.activemq.broker-url=tcp://127.0.0.1:61616
# 在考虑结束之前等待的时间
#spring.activemq.close-timeout=15s
# 默认代理URL是否应该在内存中。如果指定了显式代理,则忽略此值。
spring.activemq.in-memory=true
# 是否在回滚回滚消息之前停止消息传递。这意味着当启用此命令时,消息顺序不会被保留。
spring.activemq.non-blocking-redelivery=false
# 等待消息发送响应的时间。设置为0等待永远。
spring.activemq.send-timeout=0
#默认情况下activemq提供的是queue模式,若要使用topic模式需要配置下面配置
spring.jms.pub-sub-domain=true
#账号
spring.activemq.user=admin
# 密码
spring.activemq.password=admin
# 是否信任所有包
#spring.activemq.packages.trust-all=
# 要信任的特定包的逗号分隔列表(当不信任所有包时)
#spring.activemq.packages.trusted=
# 当连接请求和池满时是否阻塞。设置false会抛“JMSException异常”。
#spring.activemq.pool.block-if-full=true
# 如果池仍然满,则在抛出异常前阻塞时间。
#spring.activemq.pool.block-if-full-timeout=-1ms
# 是否在启动时创建连接。可以在启动时用于加热池。
#spring.activemq.pool.create-connection-on-startup=true
# 是否用Pooledconnectionfactory代替普通的ConnectionFactory。
#spring.activemq.pool.enabled=false
# 连接过期超时。
#spring.activemq.pool.expiry-timeout=0ms
# 连接空闲超时
#spring.activemq.pool.idle-timeout=30s
# 连接池最大连接数
#spring.activemq.pool.max-connections=1
# 每个连接的有效会话的最大数目。
#spring.activemq.pool.maximum-active-session-per-connection=500
# 当有"JMSException"时尝试重新连接
#spring.activemq.pool.reconnect-on-exception=true
# 在空闲连接清除线程之间运行的时间。当为负数时,没有空闲连接驱逐线程运行。
#spring.activemq.pool.time-between-expiration-check=-1ms
# 是否只使用一个MessageProducer
#spring.activemq.pool.use-anonymous-producers=true
2.activemq有两种方式,点对点(队列)模式,和发布订阅(topic)模式
当spring.jms.pub-sub-domain=true是true表示支持topic模式,默认是false,支持点对点,如果true的前提下,没有配置 containerFactory = "jmsListenerContainerQueue",是不支持点对点模式的,我这边测试是没收到队列的消息.最好的方式就是队列设置队列的factory,topic设置topic的factory,下面说的不能用是指消费者收不到消息
3.消息生产者
package com.alimama.server.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alimama.api.enums.TopicEnum;
import com.alimama.api.service.IMqMessageProducerService;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
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.jms.core.JmsMessagingTemplate;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.jms.*;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Created by PengWX on 2019/7/22.
*/
@Service("mqMessageProducerService")
public class MqMessageProducerServiceImpl implements IMqMessageProducerService {
private static final Logger LOGGER = LoggerFactory.getLogger(MqMessageProducerServiceImpl.class);
private Executor executor = new ThreadPoolExecutor(1, 10, 1, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
/**
* 点对点和发布订阅都可以用
* @param topic
* @param message
*/
@Override
public void oldSend(TopicEnum topic, String message) {
executor.execute(new Runnable() {
@Override
public void run() {
jmsTemplate.send(topic.getKey(), new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage tm = session.createTextMessage();
tm.setText(JSON.toJSONString(message, SerializerFeature.WriteClassName));
LOGGER.info("send message to topic:" + topic.getKey() + ",content:" + message);
return tm;
}
});
}
});
}
/**
* 发布订阅模式,配置spring.jms.pub-sub-domain=true且如果消费者没有配置containerFactory的条件下,在点对点模式不能用.
* @param topic
* @param message
*/
@Override
public void send(TopicEnum topic, String message) {
if (!StringUtils.isEmpty(message)) {
executor.execute(new Runnable() {
@Override
public void run() {
/**
* 将接受到的消息及消息模式(topic或queue)放到队列里面,然后消费
* 者只需要正确的添加注解@JmsListener(destination = "目的地"),监听队列消息就会主动获取
*/
ActiveMQTopic mqTopic = new ActiveMQTopic(topic.getKey());
jmsMessagingTemplate.convertAndSend(mqTopic, message);
}
});
}
}
/**
* 点对点模式,spring.jms.pub-sub-domain=false且消费者没有配置containerFactory的条件下,发布订阅模式不能用
* @param queueName
* @param message
*/
@Override
public void sendQueue(String queueName, String message) {
if (!StringUtils.isEmpty(message)) {
executor.execute(new Runnable() {
@Override
public void run() {
Destination queue = new ActiveMQQueue(queueName);
jmsMessagingTemplate.convertAndSend(queue, message);
}
});
}
}
}
4.消息消费者
package com.alimama.server.service;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.Serializable;
/**
* mq消息接收,需要加上@Component
* Created by PengWX on 2019/7/22.
*/
@Component
public class MqMessageComsumer {
/*
* 监听和读取消息,如果spring.jms.pub-sub-domain=true此时是topic模式且没有配置
*containerFactory = "jmsListenerContainerTopic",这两个消费者都会重复消费消息, 设置对应
*的factory才能收对应的消息
*/
@JmsListener(destination = "cheguo.queues.loan.test")
public void messageComsumer(String message) {
System.out.println("===============================================");
System.out.println("接受到1:" + message);
System.out.println("===============================================");
//TODO something
}
/*
* 监听和读取消息,如果spring.jms.pub-sub-domain=true且没有配置containerFactory =
*"jmsListenerContainerQueue",topic
* 模式下这两个都会重复消费消息,设置对应的factory才能收对应的消息,如果spring.jms.pub-sub-
*domain=false,那么是点对点模式,这个时候不管几个消费者都只有一个消费者消费消息,不管有没有
*containerFactory =
*"jmsListenerContainerQueue"
*/
@JmsListener(destination = "cheguo.queues.loan.test")
public void messageComsumer1(String message) {
System.out.println("===============================================");
System.out.println("接受到2:" + message);
System.out.println("===============================================");
//TODO something
}
}
#默认情况下activemq提供的是queue模式,若要使用topic模式需要配置下面配置
#spring.jms.pub-sub-domain=true
这个被注释的时候打印信息
activemq配置奉上
package com.alimama.server.config;
import com.alimama.api.enums.TopicEnum;
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.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import javax.jms.Queue;
import javax.jms.Topic;
/**
* Created by PengWX on 2019/7/23.
*/
@Configuration
public class ActiveMQConfig {
@Value("${spring.activemq.user}")
private String usrName;
@Value("${spring.activemq.password}")
private String password;
@Value("${spring.activemq.broker-url}")
private String brokerUrl;
@Bean
public Queue queue(){
return new ActiveMQQueue("cheguo.queues.loan.test");
}
@Bean
public Topic topic(){
return new ActiveMQTopic(TopicEnum.LOAN.getKey());
}
@Bean
public ActiveMQConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(usrName, password, brokerUrl);
}
/**
*设置queue工厂
*/
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerQueue(ActiveMQConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setConnectionFactory(connectionFactory);
return bean;
}
/**
*设置topic工程
*/
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ActiveMQConnectionFactory connectionFactory){
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
//设置为发布订阅方式, 默认情况下使用的生产消费者方式
bean.setPubSubDomain(true);
bean.setConnectionFactory(connectionFactory);
return bean;
}
}
上面的消费者只能在对应的模式下收取对应消息
要想topic和queue一起,现在说的是使用 jmsMessagingTemplate就需要在消费者上面加上对应的 containerFactory,正确代码
/*
* 监听和读取消息
*/
@JmsListener(destination = "cheguo.queues.loan.test",containerFactory = "jmsListenerContainerTopic")
public void messageComsumer(String message) {
System.out.println("===============================================");
System.out.println("接受到1:" + message);
System.out.println("===============================================");
//TODO something
}
/*
* 监听和读取消息,只接受queue消息
*/
@JmsListener(destination = "cheguo.queues.loan.test",containerFactory = "jmsListenerContainerQueue")
public void messageComsumer1(String message) {
System.out.println("===============================================");
System.out.println("接受到2:" + message);
System.out.println("===============================================");
//TODO something
}
总结:
使用jmsMessagingTemplate
1. 如果
#默认情况下activemq提供的是queue模式,若要使用topic模式需要配置下面配置
spring.jms.pub-sub-domain=true
此时是发布订阅模式,消费者没有指定containerFactory 那么两个消费者都会消费消息,需要设置queue和topic的containerFactory才能收取对应的消息.
2.如果
#默认情况下activemq提供的是queue模式,若要使用topic模式需要配置下面配置
spring.jms.pub-sub-domain=false
或者不配置这个的情况下,此时是点对点模式,就算没有设置containerFactory 消息依然只有一个消费者会消费,最好是设置好containerFactory
需要在active的那个配置类里面的topic的工厂里面设置 bean.setPubSubDomain(true);不然不管有没有在application.propeties里面设置spring.jms.pub-sub-domain=,也不管设置的什么.topic都收不到消息,要想两种模式都可以,配置文件要加上这个spring.jms.pub-sub-domain=true,在activemqConfig里面的topic的factory加上bean.setPubSubDomain(true)