本文参考自江南一点雨的Spring Boot+Vue系列视频教程第 12 章以及rabbitmq官网文档,详情参加【Spring Boot+Vue系列视频教程】
本文的代码地址为SpringBoot学习案例中的jms和amqp
消息中间件学习笔记
一、springboot中使用activemq
此处用active mq写一个收发消息的简单案例
1、启动activemq
访问activemq官网,会发现有ActiveMQ "Classic"
与ActiveMQ Artemis
两个版本供使用,后者是未来版本,此处下载ActiveMQ "Classic"
即可,本文下载的是Windows下的ActiveMQ 5.15.15 (Apr 28th, 2021)
版本(若用浏览器下载慢,可以复制下载链接用迅雷下载)。
下载完成之后得到压缩文件,解压该文件,进入到bin目录下,双击activemq
启动:
通过网址http://localhost:8161/
访问,默认名和密码都是admin
,出现下方页面代表启动成功。
2、用Spring Initiaizre新建activemq项目
①:新建项目名称设置为jms
,导入三个依赖:
②:在application.yml
文件中配置activemq
:
spring:
activemq:
broker-url: tcp://127.0.0.1:61616 #通信端口
packages:
trust-all: true #信任所有包
user: admin
password: admin
server:
port: 7723
③:在启动类中定义消息队列:
@SpringBootApplication
public class JmsApplication {
public static void main(String[] args) {
SpringApplication.run(JmsApplication.class, args);
}
//消息队列
@Bean
Queue queue() {
//咱们定义的消息队列
return new ActiveMQQueue("t2-queue");
}
}
④:创建配置类JmsComponent
:
@Component
public class JmsComponent {
//消息发送模板
@Autowired
JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
Queue queue;
public void send(Message message) {
//参数一:发送目的地 参数二:消息内容 参数三:
jmsMessagingTemplate.convertAndSend(queue, message);
}
//指明接收哪儿的消息
@JmsListener(destination = "t2-queue")
public void receive(Message msg) {
System.out.println("msg = " + msg);
}
}
⑤:创建传输信息实体类(用Lombok
的@Data
省略getters and setters和toString等方法)
//传输需要序列化
@Data
public class Message implements Serializable {
private String content;
private Date date;
}
⑥:启动JmsApplication
,在测试类中新建并发送信息:
@SpringBootTest
class JmsApplicationTests {
@Autowired
JmsComponent jmsComponent;
@Test
void contextLoads() {
Message message = new Message();
message.setContent("hello world");
message.setDate(new Date());
jmsComponent.send(message);
}
}
⑦:启动测试类,查看JmsApplication
项目的控制台,信息发送与接收成功
二、springboot中使用rabbitmq
1、启动rabbitmq
在linux上直接安装rabbitmq坑比较多,建议直接用docker安装,此处不做展开讲述,在docker上安装好rabbitmq后,使用docker ps -a
指令查看所有容器,此处rabbitmq
的容器名为myrabbit
(在安装容器的时候指定)
执行docker start myrabbit
指令启动rabbitmq
。
2、用Spring Initiaizre新建rabbitmq项目
①:新建项目名为amqp
,导入下面三个依赖:
②:在application.yml
文件中配置rabbitmq
,host
根据rabbitmq
地实际运行地址进行设置:
spring:
rabbitmq:
username: guest
password: guest
host: 192.168.42.155
port: 5672
server:
port: 7724
3、演示rabbitmq中有四种交换机模式
在rabbitmq
中有四种交换机模式,分别是Direct
、Fanout
、Topoc
、Headers
,下面针对这四种模式进行演示。
①:演示Direct
交换机模式
新建DirectConfig
配置类:
@Configuration
public class DirectConfig {
//队列1 命名为 t2-queue1
@Bean
Queue directQueue() {
return new Queue("t2-queue1");
}
//队列2 t2-queue2
@Bean
Queue directQueue2() {
return new Queue("t2-queue2");
}
// 如果用来direct模式,下面两个bean可以省略。
//交换机
@Bean
DirectExchange directExchange() {
//参数一:交换机名称 参数二:重启之后队列是否依然有效 参数三:长期未使用的时候是否自动删除
return new DirectExchange("t2-direct",true,false);
}
//粘合剂,作用:将队列和交换机绑定到一起,路由键命名为 direct1和direct2
@Bean
Binding directBinding() {
return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct1");
}
@Bean
Binding directBinding2() {
return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
}
}
新建DirectReceiver
消息处理类:
@Component
public class DirectReceiver {
//监听路由键值为 t2-queue1 和 t2-queue1 的消息队列
@RabbitListener(queues = "t2-queue1")
public void handler(String msg) {
System.out.println("msg1 = " + msg);
}
@RabbitListener(queues = "t2-queue2")
public void handler2(String msg) {
System.out.println("msg2 = " + msg);
}
}
启动项目:
启动过程中若遇到AMQP protocol version mismatch; we are version 0-9-1, server sent signature 3,1,0,0
报错是由于activemq
已经在本机开启了,关闭即可。
在测试类中进行消息发送:
@SpringBootTest
class AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
//参数一:消息队列路由键值 参数二:内容
rabbitTemplate.convertAndSend("t2-queue1","hello direct1");
rabbitTemplate.convertAndSend("t2-queue2","hello direct2");
}
}
测试结果:
从图形上梳理,P代表生产者(测试类),紫色X代表交换机,红色块列代表队列(在DirectConfig
配置类中定义),C代表消费者(在DirectReceiver
消息处理类中定义),生产者P发送数据的时候,需要声明与交换机绑定的队列的路由键,每次新增队列都需要在P(生产者)指定新增队列的路由键(队列名)。
下图中的交换机X绑定了两个队列Q1和Q2,其中队列Q1绑定的路由键是orange
,队列Q2绑定的路由键是black
和green
,当生产者P发送消息时,当消息中包含orange
路由键时,消息才会进入到队列Q1,当消息中包含black
或者green
路由键时,消息才会进入队列Q2,除了包含指定路由键的消息外,其他消息都会被忽略。
direct
(路由)模式的特点:
类似于activemq
中的点对点发送,定义队列和接收器即可,跟交换机的关系不密切。
验证Direct
模式在使用中不配置交换机和粘合剂是否能收发信息,注释掉配置类中相关代码:
@Configuration
public class DirectConfig {
//队列
@Bean
Queue directQueue() {
return new Queue("t2-queue1");
}
//新增队列
@Bean
Queue directQueue2() {
return new Queue("t2-queue2");
}
// // 如果用来direct模式,下面两个bean可以省略。
// //交换机
// @Bean
// DirectExchange directExchange() {
// //参数二:重启之后队列是否依然有效 参数三:长期未使用的时候是否自动删除
// return new DirectExchange("t2-direct",true,false);
// }
//
//
// //作用:将队列和交换机绑定到一起
// @Bean
// Binding directBinding() {
// return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct1");
// }
//
// @Bean
// Binding directBinding2() {
// return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
// }
}
在消息测试类中测试,测试结果:
②:演示Fanout
交换机模式
新建配置类FanoutConfig
:
@Configuration
public class FanoutConfig {
@Bean
Queue queueOne() {
return new Queue("queue-one");
}
@Bean
Queue queueTwo() {
return new Queue("queue-two");
}
@Bean
FanoutExchange fanoutExchange(){
return new FanoutExchange("t2-fanout",true,false);
}
//两个队列与同一个交换机绑定起来
@Bean
Binding bindingOne() {
return BindingBuilder.bind(queueOne()).to(fanoutExchange());
}
@Bean
Binding bindingTwo() {
return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
}
}
新建消息处理类FanoutReceiver
:
@Component
public class FanoutReceiver {
@RabbitListener(queues = "queue-one")
public void handler1(String msg) {
System.out.println("handler1:msg = " + msg);
}
@RabbitListener(queues = "queue-two")
public void handler2(String msg) {
System.out.println("handler2:msg = " + msg);
}
}
启动项目,在测试类中进行消息发送:
@SpringBootTest
class AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
//参数一:交换机名称 参数二:routingKey(此处不需要)参数三:内容
rabbitTemplate.convertAndSend("t2-fanout",null,"hello fanout");
}
}
测试结果:
从图形上梳理,在P(生产者)中,只需要指明交换机名称以及发布内容,就能调用多个消息队列,类似于现实生活中的群发消息、广播(前提是这些消息队列已与交换机绑定)
③:演示Topics
交换机模式
新建TopicConfig
配置类:
新建三个队列,分别命名为xiaomi
、huawei
和phone
,将这三个队列与交换机t2-topic
绑定,名称前后的#
用于做模糊匹配:
关于模糊匹配:
*
(star) can substitute for exactly one word. 用于匹配一个单词#
(hash) can substitute for zero or more words. 用于匹配零个或多个单词
@Configuration
public class TopicConfig {
@Bean
Queue xiaomi() {
return new Queue("xiaomi");
}
@Bean
Queue huawei() {
return new Queue("huawei");
}
@Bean
Queue phone() {
return new Queue("phone");
}
@Bean
TopicExchange topicExchange() {
return new TopicExchange("t2-topic",true,false);
}
@Bean
Binding xiaomiBinding() {
return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
}
@Bean
Binding huaweiBinding() {
return BindingBuilder.bind(huawei()).to(topicExchange()).with("huawei.#");
}
@Bean
Binding phoneBinding() {
return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
}
}
新建消息处理类TopicReceiver
:
@Component
public class TopicReceiver {
@RabbitListener(queues = "phone")
public void handler1(String msg) {
System.out.println("phone-msg = " + msg);
}
@RabbitListener(queues = "xiaomi")
public void handler2(String msg) {
System.out.println("xiaomi-msg = " + msg);
}
@RabbitListener(queues = "huawei")
public void handler3(String msg) {
System.out.println("huawei-msg = " + msg);
}
}
启动项目,在测试类中发送消息:
@SpringBootTest
class AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
rabbitTemplate.convertAndSend("t2-topic","xiaomi.new","小米新闻");
}
}
测试结果:
xiaomi
队列的消费者接受到了消息。
再次测试:
rabbitTemplate.convertAndSend("t2-topic","xiaomi.new","小米新闻");
rabbitTemplate.convertAndSend("t2-topic","huawei.watch","华为手表");
rabbitTemplate.convertAndSend("t2-topic","apple.phone","苹果手机");
测试结果:
从图形上梳理,相比于Direct
模式的直来直去,Topics
模式通过routingKey
模糊匹配,在业务上更加灵活。
④:演示Header
交换机模式
这种交换机模式用得比较少,它的策略主要是根据消息的Header将消息路由到不同的队列上面去,和routingKey
没有关系,纵观四中交换机模式,会发现只有Topics
模式和routingKey
的关系比较密切。
新建HeaderConfig
配置类:
@Configuration
public class HeaderConfig {
@Bean
Queue queueAge() {
return new Queue("queue-age");
}
@Bean
Queue queueName() {
return new Queue("queue-name");
}
@Bean
HeadersExchange headersExchange() {
return new HeadersExchange("t2-header",true,false);
}
@Bean
Binding bindingAge() {
Map<String, Object> map = new HashMap<>();
map.put("age",99);
//消息头里面需要有age,并且值需要为99才路由到这个队列
return BindingBuilder.bind(queueAge()).to(headersExchange()).whereAny(map).match();
}
@Bean
Binding bindingName() {
//消息头里面存在name属性就路由到这个队列
return BindingBuilder.bind(queueName()).to(headersExchange()).where("name").exists();
}
}
新建消息处理类HeaderReceiver
:
@Component
public class HeaderReceiver {
@RabbitListener(queues = "queue-age")
public void handler1(String msg) {
System.out.println("queue-age:msg = " + msg);
}
@RabbitListener(queues = "queue-name")
public void handler2(String msg) {
System.out.println("queue-name:msg = " + msg);
}
}
启动项目,在测试类中发送消息:
@SpringBootTest
class AmqpApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
Message nameMsg = MessageBuilder.withBody("hello you".getBytes()).setHeader("name","t2-header").build();
Message ageMsg = MessageBuilder.withBody("hello you".getBytes()).setHeader("age",99).build();
rabbitTemplate.send("t2-header",null,nameMsg);
rabbitTemplate.send("t2-header",null,ageMsg);
}
}
测试结果: