前言
ActiveMQ 一个成熟的消息中间件,作用于系统之间的通信,降低模块与模块之间的耦合度。
消息的传递有两种类型:
1. Queue 队列模式:一个生产者和一个消费者一一对应
2. Topic 发布者/订阅者模式:一个生产者对应多个消费者
本博客配置了 Queue 队列模式和 Topic 发布者/订阅者模式,可同时支持。
源码
GitHub地址:https://github.com/intomylife/SpringBoot
环境
JDK 1.8.0 +
Maven 3.0 +
SpringBoot 2.0.3
apache-activemq-5.9.0
开发工具
IntelliJ IDEA
正文
commons 工程 - POM 文件
<?xml version="1.0" encoding="UTF-8"?>
<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.zwc</groupId>
<artifactId>springboot-activemq-commons</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 工程名称和描述 -->
<name>springboot-activemq-commons</name>
<description>公用工程</description>
<!-- 打包方式 -->
<packaging>jar</packaging>
<!-- 在properties下声明相应的版本信息,然后在dependency下引用的时候用${spring-version}就可以引入该版本jar包了 -->
<properties>
<!-- 编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- jdk -->
<java.version>1.8</java.version>
<!-- springboot -->
<platform-bom.version>Cairo-SR3</platform-bom.version>
<!-- ali json -->
<fastjson.version>1.2.47</fastjson.version>
<jackson.mapper.asl.version>1.9.9</jackson.mapper.asl.version>
</properties>
<!-- 加入依赖 -->
<dependencies>
<!-- ali json依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.mapper.asl.version}</version>
</dependency>
<!-- activemq 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
</dependencies>
<!-- 依赖 jar 包版本管理的管理器 -->
<!-- 如果 dependencies 里的 dependency 自己没有声明 version 元素,那么 maven 就此处来找版本声明。 -->
<!-- 如果有,就会继承它;如果没有就会报错,告诉你没有版本信息 -->
<!-- 优先级:如果 dependencies 里的 dependency 已经声明了版本信息,就不会生效此处的版本信息了 -->
<dependencyManagement>
<dependencies>
<!-- SpringBoot -->
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>${platform-bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 插件依赖 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置一些共用依赖,其中包括 spring-boot-starter-activemq 依赖来整合 ActiveMQ
commons 工程 - system.properties
activemq
## 配置消息的类型,true:topic消息,false:Queue消息
#jms.pub-sub-domain=false
## 用户名
activemq.user=admin
## 密码
activemq.password=admin
## 连接主机
activemq.broker-url=tcp://127.0.0.1:61616
## 是否信任所有包
activemq.packages.trust-all=true
一些共用配置,不经常修改的,或者是可以统一修改的
比如还可以配置 OSS 的配置信息,Redis 的配置信息,MongoDB 的配置信息等等…
commons 工程 - 工具类
queue 队列模式发送消息工具类
package com.zwc.core.utils;
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 javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import java.io.Serializable;
/**
* @ClassName QueueUtil
* @Desc TODO queue 队列模式发送消息工具类
* @Date 2019/4/8 15:57
* @Version 1.0
*/
public class QueueUtil {
@Autowired
@Qualifier("queueTemplate")
private JmsTemplate jmsTemplate;
/*
* @ClassName QueueUtil
* @Desc TODO 发送一条消息到指定的队列
* @param queueName 队列名称
* @param message 消息内容
* @Date 2019/4/8 16:26
* @Version 1.0
*/
public void send(String queueName, final Serializable message) {
jmsTemplate.send(queueName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(message);
}
});
}
}
装配对象 queueTemplate 在下面自定义配置中定义
topic 订阅模式发送消息工具类
package com.zwc.core.utils;
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 javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import java.io.Serializable;
/**
* @ClassName TopicUtil
* @Desc TODO topic 订阅模式发送消息工具类
* @Date 2019/4/8 17:03
* @Version 1.0
*/
public class TopicUtil {
@Autowired
@Qualifier("topicTemplate")
private JmsTemplate jmsTemplate;
/*
* @ClassName QueueUtil
* @Desc TODO 发送一条消息到指定的订阅者
* @param queueName 订阅者名称
* @param message 消息内容
* @Date 2019/4/8 16:36
* @Version 1.0
*/
public void send(String topicName, final Serializable message) {
jmsTemplate.send(topicName, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(message);
}
});
}
}
装配对象 topicTemplate 在下面自定义配置中定义
commons 工程 - 自定义配置(核心)
package com.zwc.core.config;
import com.zwc.core.utils.QueueUtil;
import com.zwc.core.utils.TopicUtil;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import javax.jms.ConnectionFactory;
/**
* @ClassName ActiveMQConfig
* @Desc TODO ActiveMQ 配置
* @Date 2019/4/8 16:05
* @Version 1.0
*/
@Configuration
@ConditionalOnClass(ActiveMQConnectionFactory.class)
@PropertySource("classpath:system.properties")
public class ActiveMQConfig {
// ActiveMQ 用户名
@Value("${activemq.user}")
private String mqUser;
// ActiveMQ 密码
@Value("${activemq.password}")
private String mqPassword;
// ActiveMQ 连接主机
@Value("${activemq.broker-url}")
private String mqBrokerUrl;
// 是否信任所有包
@Value("${activemq.packages.trust-all}")
private boolean mqPackagesTA;
/*
* @ClassName ActiveMQConfig
* @Desc TODO ActiveMQ 连接配置
* @Date 2019/4/8 16:48
* @Version 1.0
*/
@Bean
public CachingConnectionFactory cachingConnectionFactory() {
// 连接工厂
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
ActiveMQConnectionFactory mqConnectionFactory = new ActiveMQConnectionFactory();
// 连接主机
mqConnectionFactory.setBrokerURL(mqBrokerUrl);
// 用户名
mqConnectionFactory.setUserName(mqUser);
// 密码
mqConnectionFactory.setPassword(mqPassword);
// 是否信任所有包
mqConnectionFactory.setTrustAllPackages(mqPackagesTA);
// 设置连接工厂配置信息
connectionFactory.setTargetConnectionFactory(mqConnectionFactory);
// 返回连接工厂
return connectionFactory;
}
/*
* 有两种消息类型:queue-队列 / topic-订阅
* 使用 pub-sub-domain 属性控制消息类型
* true:topic消息,false:Queue消息
*/
/*
* @ClassName ActiveMQConfig
* @Desc TODO 在读取队列消息时需要配置此 bean( PubSubDomain 默认为就是 false,可以不用配置)
* @Date 2019/4/9 11:01
* @Version 1.0
*/
@Bean("queueListenerFactory")
public DefaultJmsListenerContainerFactory queueListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
/*
* @ClassName ActiveMQConfig
* @Desc TODO 队列模式,设置 pub-sub-domain 为 false
* @Date 2019/4/8 16:51
* @Version 1.0
*/
@Bean
public JmsTemplate queueTemplate(CachingConnectionFactory cachingConnectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate(cachingConnectionFactory);
// jmsTemplate.setReceiveTimeout();
jmsTemplate.setPubSubDomain(false);
return jmsTemplate;
}
/*
* @ClassName ActiveMQConfig
* @Desc TODO 在读取订阅者消息时需要配置此 bean
* @Date 2019/4/9 11:01
* @Version 1.0
*/
@Bean("topicListenerFactory")
public DefaultJmsListenerContainerFactory topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
/*
* @ClassName ActiveMQConfig
* @Desc TODO 订阅模式,设置 pub-sub-domain 为 true
* @Date 2019/4/8 16:59
* @Version 1.0
*/
@Bean
public JmsTemplate topicTemplate(CachingConnectionFactory connectionFactory){
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
// jmsTemplate.setReceiveTimeout();
jmsTemplate.setPubSubDomain(true);
return jmsTemplate;
}
/*
* @ClassName ActiveMQConfig
* @Desc TODO 把 queue 队列模式发送消息工具类注入到 spring 中
* @Date 2019/4/8 17:05
* @Version 1.0
*/
@Bean
@ConditionalOnBean(value = JmsTemplate.class, name = "queueTemplate")
public QueueUtil queueUtil(){
return new QueueUtil();
}
/*
* @ClassName ActiveMQConfig
* @Desc TODO 把 topic 订阅模式发送消息工具类注入到 spring 中
* @Date 2019/4/8 17:08
* @Version 1.0
*/
@Bean
@ConditionalOnBean(value = JmsTemplate.class, name = "topicTemplate")
public TopicUtil topicUtil(){
return new TopicUtil();
}
}
通过 @Configuration + @Bean 注解把对象注入到 spring 中
注意这里在注入类的时候,还要加载自定的配置文件,因为 SpringBoot 不会默认加载 system.properties
使用 @Value 注解读取配置文件中的数据
下面两点是整合中特别需要注意的地方
@Bean -> queueTemplate / topicTemplate:这两个用来发送消息用
@Bean -> queueListenerFactory / topicListenerFactory:这两个用来接收消息用
commons 工程 - 项目结构
service 工程
service 工程是一个父工程,里面包含 基础模块,用户模块,每个模块中又会分为 core 和 api
此工程中 base-service 作为 Provider(提供者/发送消息),user-service 作为 Consumer(消费者/接收消息)
Provider(提供者/发送消息)
base-service - base-service-core
Queue 队列模式 - service
package com.zwc.base.mq.queue;
import com.zwc.core.constants.ActiveMQConstants;
import com.zwc.core.utils.QueueUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @ClassName MqQueueProducerOneToOne
* @Desc TODO ActiveMQ queue 队列模式 - 提供方
* @Date 2019/4/8 15:30
* @Version 1.0
*/
@Service
public class MqQueueProducer {
@Autowired
private QueueUtil queueUtil;
/*
* @ClassName MqQueueProducerOneToOne
* @Desc TODO 发送队列消息
* @Date 2019/4/8 16:29
* @Version 1.0
*/
public void sendQueueMessage(){
queueUtil.send(ActiveMQConstants.QUEUE_NAME,"EN: From springboot-activemq! I'm queue. CN: springboot 整合 activemq 发送队列消息。");
}
}
在 commons 工程 - 自定义配置文件中使用 @Bean 注解把 QueueUtil 类注入到了 spring
这里就可以直接使用 @Autowired 注解把 Bean 装配进来
Topic 订阅者模式 - service
package com.zwc.base.mq.topic;
import com.zwc.core.constants.ActiveMQConstants;
import com.zwc.core.utils.TopicUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/*
* @ClassName MqTopicProducer
* @Desc TODO ActiveMQ topic 订阅者模式 - 提供方
* @Date 2019/4/8 18:09
* @Version 1.0
*/
@Service
public class MqTopicProducer {
@Autowired
private TopicUtil topicUtil;
/*
* @ClassName MqTopicProducer
* @Desc TODO 发送订阅者消息
* @Date 2019/4/8 18:09
* @Version 1.0
*/
public void sendTopicMessage(){
topicUtil.send(ActiveMQConstants.TOPIC_NAME,"EN: From springboot-activemq! I'm topic. CN: springboot 整合 activemq 发送订阅者消息。");
}
}
在 commons 工程 - 自定义配置文件中使用 @Bean 注解把 TopicUtil 类注入到了 spring
这里就可以直接使用 @Autowired 注解把 Bean 装配进来
base-service - 启动项目
注:项目启动前需要启动 activemq
1. 端口:8081(具体可以根据自己的喜好,在 application.properties 配置文件中配置 server.port)
2. 发送队列消息接口:http://localhost:8081/queue/send (调用成功会在页面显示 success)
3. 发送订阅者消息接口:http://localhost:8081/topic/send (调用成功会在页面显示 success)
4. 因为启动了 activemq,所以可直接访问地址:http://localhost:8161/admin 进入到控制台
5. 点击 Queues 和 Topics 分别可以看到刚刚发送的消息
注:
1. 上图中的红色文字非正规翻译,都是以自己的理解说明的,仅供参考!
2. 可以看出消费者都为 0,接下来就是消费者工程
Consumer(消费者/接收消息)
user-service - user-service-core
接收 Queue 队列模式 - service
package com.zwc.user.mq.queue;
import com.zwc.core.constants.ActiveMQConstants;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;
import javax.jms.Queue;
/**
* @ClassName MqQueueConsumerOne
* @Desc TODO ActiveMQ queue 队列模式 - 消费方 一号
* @Date 2019/4/8 17:44
* @Version 1.0
*/
@Service
public class MqQueueConsumerOne {
/*
* @ClassName MqQueueConsumerOne
* @Desc TODO 接收队列消息
* @Date 2019/4/8 17:48
* @Version 1.0
*/
@JmsListener(destination = ActiveMQConstants.QUEUE_NAME , containerFactory = "queueListenerFactory")
public void receiveQueueMessage(String message){
System.out.println("MqQueueConsumerOne ---> receiveQueueMessage:接收队列模式发送的消息,内容为:" + message);
}
}
package com.zwc.user.mq.queue;
import com.zwc.core.constants.ActiveMQConstants;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;
/**
* @ClassName MqQueueConsumerTwo
* @Desc TODO ActiveMQ queue 队列模式 - 消费方 二号
* @Date 2019/4/8 17:44
* @Version 1.0
*/
@Service
public class MqQueueConsumerTwo {
/*
* @ClassName MqQueueConsumerTwo
* @Desc TODO 接收队列消息
* @Date 2019/4/8 17:48
* @Version 1.0
*/
@JmsListener(destination = ActiveMQConstants.QUEUE_NAME , containerFactory = "queueListenerFactory")
public void receiveQueueMessage(String message){
System.out.println("MqQueueConsumerTwo ---> receiveQueueMessage:接收队列模式发送的消息,内容为:" + message);
}
}
使用 @JmsListener 注解开始监听消息任务
destination 参数指定队列名称
containerFactory 参数指定连接工厂,queueListenerFactory 在 commons 工程 - 自定义配置文件中注入到 Bean 中了
接收 Topic 订阅者模式 - service
package com.zwc.user.mq.topic;
import com.zwc.core.constants.ActiveMQConstants;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;
/*
* @ClassName MqTopicConsumerOne
* @Desc TODO ActiveMQ topic 订阅者模式 - 消费方 一号
* @Date 2019/4/8 18:11
* @Version 1.0
*/
@Service
public class MqTopicConsumerOne {
/*
* @ClassName MqTopicConsumerOne
* @Desc TODO 接收订阅者消息
* @Date 2019/4/8 18:12
* @Version 1.0
*/
@JmsListener(destination = ActiveMQConstants.TOPIC_NAME , containerFactory = "topicListenerFactory")
public void receiveTopicMessage(String message){
System.out.println("MqTopicConsumerOne ---> receiveTopicMessage:接收订阅者模式发送的消息,内容为:" + message);
}
}
package com.zwc.user.mq.topic;
import com.zwc.core.constants.ActiveMQConstants;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Service;
/*
* @ClassName MqTopicConsumerTwo
* @Desc TODO ActiveMQ topic 订阅者模式 - 消费方 二号
* @Date 2019/4/8 18:11
* @Version 1.0
*/
@Service
public class MqTopicConsumerTwo {
/*
* @ClassName MqTopicConsumerTwo
* @Desc TODO 接收订阅者消息
* @Date 2019/4/8 18:12
* @Version 1.0
*/
@JmsListener(destination = ActiveMQConstants.TOPIC_NAME , containerFactory = "topicListenerFactory")
public void receiveTopicMessage(String message){
System.out.println("MqTopicConsumerTwo ---> receiveTopicMessage:接收订阅者模式发送的消息,内容为:" + message);
}
}
使用 @JmsListener 注解开始监听消息任务
destination 参数指定订阅者名称
containerFactory 参数指定连接工厂,topicListenerFactory 在 commons 工程 - 自定义配置文件中注入到 Bean 中了
user-service - 启动项目
注:项目启动前需要启动 activemq
-
端口:8082(具体可以根据自己的喜好,在 application.properties 配置文件中配置 server.port)
-
启动后观察 idea 的控制台,会有一条字符串打印出来
-
在提供方一共发送了两条消息,在消费方一共有四个监听,为什么只有一条打印信息
Queue 队列模式 和 Topic 发布者/订阅者模式区别
Queue 队列模式:一个生产者和一个消费者一一对应。生产者把消息发送出去后消息就会在队列中排队等候被消费,只要有一个消费者出现,就会被消费者消费;但如果有多个消费者,就只会被其中某一个消费者消费;并且此生产者如果还陆续发送消息的话,每一条消息依然都只会被某一个消费着消费。
Topic 发布者/订阅者模式:一个生产者对应多个消费者。只有当多个消费者存在(项目启动中),生产者发送的消息才会被接收到,而且消息会被每一个消费者接收到。
在发送了一条 Queue 模式的消息和一条 Topic 模式的消息后,再启动消费者的情况,因为 Queue 模式的消息是一对一的,所以只打印了一条 …接收队列模式发送的消息…;而 Topic 模式的消息是在消费者启动前发送的,所以 …接收订阅者模式发送的消息… 没有接收到。
-
再次调用 base-service 工程的发送队列消息接口:http://localhost:8081/queue/send (调用成功会在页面显示 success)
-
观察 idea 的控制台,会有一条新字符串打印出来
-
怎么还是消费者二号接收到了消息,再调用一次步骤 4 中接口,消费者一号终于接收到了消息
-
刷新:http://localhost:8161/admin/queues.jsp ,可以看到消费者有两个,发送消息有三条,已读消息有三条
-
再次调用 base-service 工程的发送订阅者消息接口:http://localhost:8081/topic/send (调用成功会在页面显示 success)
-
观察 idea 的控制台,会有两条新字符串打印出来,所有订阅者模式的消费者都接收到了消息
-
刷新:http://localhost:8161/admin/topics.jsp ,可以看到消费者有两个,发送消息有两条,已读消息有两条(发送的两条消息,第一条没有被接收到,第二条被两个消费者都接收到了,所以已读消息是两条)
service 工程 - 项目结构
在 service 总工程中创建了 base-service (基础模块)和 user-service(用户模块)
每一个模块中都包含 api 和 core
把多工程项目使用 IntelliJ IDEA 打开
把项目从 GitHub 中下载到你的本地
打开 IntelliJ IDEA
点击 File -> Open
打开你下载到本地的项目目录
springboot-activemq -> springboot-activemq-service(选择打开此工程)
打开 service 工程后
再次点击 File -> Project Structrue
选择 Modules,点击 ‘+’ 符号
点击 Import Module
还是打开你下载到本地的项目目录
springboot-activemq -> springboot-activemq-commons -> pom.xml
点击 OK
点击 Next,Finish
点击 Apply,OK
扩展
如何配置 ActiveMQ 的密码
ActiveMQ 的密码分为两种,一种是程序连接的密码,也就是访问队列和订阅者时的密码;还有一种是连接控制台的密码。
注:此处描述的 ActiveMQ 版本为 5.9.0
- 设置访问密码
a) 打开下载解压后的 apache-activemq-5.9.0 文件夹
b) 打开 conf 文件夹,相关配置信息都在里面
c) 需要关注的文件是:activemq.xml,用文本编辑器打开它
d) 在 节点的末尾处添加代码
e) username 就是用户名,password 就是密码
f) 在此处设置前,程序中连接根本就不需要设置用户名和密码,都可以连接上
g) 设置后保存,重启 activemq;commons 工程的配置文件也要对应更改为上面设置的用户名和密码
- 设置控制台密码
a) 打开下载解压后的 apache-activemq-5.9.0 文件夹
b) 打开 conf 文件夹,相关配置信息都在里面
c) 需要关注的两个文件是:jetty.xml 和 jetty-realm.properties,用文本编辑器打开它们
d) 打开 jetty.xml 文件,ctrl + f 查找 securityConstraint,修改 authenticate 属性为 true
e) 打开 jetty-realm.properties 文件,在末尾添加 用户名: 密码, 权限
比如此处用户名为 zwc ,密码为 123,权限为 admin。
保存文件重启 activemq,到控制台就可以使用刚刚设置的用户名和密码登录了。