SpringAMQP-Study-Demo
消息队列-SpringAMQP
项目包括消息发送微服务publisher,消息接收微服务consumer.
一、项目代码编写步骤
1.创建工程
创建一个springboot项目,添加spring-for-rabbitmq功能

在创建项目是勾选sprinng-for-rabbitmq功能,会自动添加springamqp的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.simple.queue
publisher微服务发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
//基本队列-simple.queue
@Test
public void sendToSimpleQueue() {
String queueName = "simple.queue";
String message = "hello rabbitmq, simple queue";
rabbitTemplate.convertAndSend(queueName, message);
System.out.println(LocalTime.now() + "成功发送消息【" + message + "】到 simple.queue");
}
consumer微服务接收消息
//1.接收 simple.queue消息
@RabbitListener(queues = "simple.queue")
public void listenToSimpleQueue(String msg){
System.out.println(LocalTime.now()+" 消费者接收到来自simple.queue的消息【"+msg+"】");
}
3.work.queue
publisher微服务发送消息
//工作队列-work.queue
@Test
public void sendToWorkQueue() throws InterruptedException {
String queueName = "work.queue";
String message = "hello rabbitmq, work queue";
for (int i = 1; i <= 20; i++) {
rabbitTemplate.convertAndSend(queueName, message);
System.out.println(LocalTime.now() + "成功发送消息"+i+"【" + message + "】到 work.queue");
Thread.sleep(20);
}
}
consumer微服务接收消息
//2.接收 work.queue消息
@RabbitListener(queues = "work.queue")
public void listenToWorkQueue1(String msg) throws InterruptedException {
System.out.println(LocalTime.now()+" 消费者1接收到来自work.queue的消息【"+msg+"】");
Thread.sleep(20);
}
@RabbitListener(queues = "work.queue")
public void listenToWorkQueue2(String msg) throws InterruptedException {
System.out.println(LocalTime.now()+" 消费者2接收到来自work.queue的消息【"+msg+"】");
Thread.sleep(200);
}
4.FanoutExchange
将消息路由到与之绑定的所有队列
在consumer微服务中创建一个类,进行交换机、队列的声明和绑定
package com.lqy.mqconsumer.exchange;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class MyFanoutExchange {
//交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("myFanout.exchange");
}
//队列1
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定1
@Bean
public Binding binding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//队列2
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
//绑定2
@Bean
public Binding binding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机

在publisher微服务中编写消息发送代码,将消息发送到交换机myFanout.exchange
@Test
public void sendToFanoutQueue1(){
String exchangeName="myFanout.exchange";
String message ="hello myFanout.exchange";
rabbitTemplate.convertAndSend(exchangeName,"",message);
System.out.println(LocalTime.now()+"成功发送消息【"+message+"】myFanout.exchange");
}
小结:
FanoutExchange交换机会将消息路由到与之绑定的所有队列。
不能缓存消息,路由失败,消息会丢失。
5.DirectExchange
与FanoutExchange类似,但是DirectExchange不是将消息路由到绑定的队列,而是根据RutingKey来路由到与之绑定的队列。(增加了RutingKey的条件)
在consumer微服务中,基于注解@QueueBinding进行交换机、队列、Rutingkey的声明和绑定
//接收 direct.queue1消息,基于注解@QueueBinding进行队列和交换机的绑定
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "direct.queue1"),exchange=@Exchange(name = "myDirect.exchange",type = ExchangeTypes.DIRECT),key = {"blue","red"}))
public void listenToDirectQueue1(String msg){
System.out.println(LocalTime.now()+"接收到来自direct.queue1的消息【"+msg+"】");
}
//接收 direct.queue2消息,基于注解@QueueBinding进行队列和交换机的绑定
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "direct.queue2"),exchange=@Exchange(name = "myDirect.exchange",type = ExchangeTypes.DIRECT),key = {"green","red"}))
public void listenToDirectQueue2(String msg){
System.out.println(LocalTime.now()+"接收到来自direct.queue2的消息【"+msg+"】");
}
启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机

在publisher微服务中编写消息发送代码,将消息发送到交换机myDirecct.exchange
@Test
public void sendToDirectExchange(){
String exchangeName="myDirect.exchange";
String message ="hello myDirect.exchange";
rabbitTemplate.convertAndSend(exchangeName,"blue",message);
System.out.println(LocalTime.now()+"成功发送消息【"+message+"】myDirect.exchange");
}
测试:
发消息:

接收消息:
同一个RutingKey

不同RutingKey


小结:
DirectExchange交换机会将消息根据RutingKey路由到与之绑定的对应的队列上。如果队列的Rutingkey都相同,则相当于FanoutExchange,即消息发送到所有队列。
6.TopicExchange
与DirectExchange类似,但是其RutingKey是由多个单词组成的,可以使用通配符
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
在consumer微服务中,基于注解@QueueBinding进行交换机、队列、Rutingkey的声明和绑定
//5.接收 topic.queue1消息,基于注解@QueueBinding进行队列和交换机的绑定
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "myTopic.exchange", type = ExchangeTypes.TOPIC), key = "china.#"))
public void listenToTopicQueue1(String msg) {
System.out.println(LocalTime.now() + "接收到来自topic.queue1的消息【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "myTopic.exchange", type = ExchangeTypes.TOPIC), key = "*.news"))
public void listenToTopicQueue2(String msg) {
System.out.println(LocalTime.now() + "接收到来自topic.queue2的消息【" + msg + "】");
}
启动consumer服务,查看rabbitmq客户端,两个队列成功绑定到交换机

在publisher微服务中编写消息发送代码,将消息发送到交换机myTopic.exchange
//5.消息订阅(话题)- myTopic.exchange
@Test
public void sendToTopicExchange(){
String exchangeName="myTopic.exchange";
String message="hello myTopic.exchange,北京今天是晴天!";
rabbitTemplate.convertAndSend(exchangeName,"china.weather",message);
System.out.println(LocalTime.now()+"成功发送消息【"+message+"】到myTopic.exchange");
}
测试1:RutingKey=china.news
发消息:

接收消息:

测试2:RutingKey=china.weather
发消息:

接收消息:

小结:
TopicExchange使用的Rutingkey采用多个单词组成,使用点号分隔,可以使用通配符。
TopicExchange通过RutingKey来将消息路由给队列。
6.转换器-序列化方法优化
在consumer微服务中创建一个object.queue队列
@Bean
public Queue objectQueue(){
return new Queue("object.queue");
}
在publisher微服务中编写对象类型的消息发送器,向object.queue队列发送对象类型的消息
package com.lqy.mqpublisher.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private int id;
private String name;
private int age;
private String email;
}
//6.测试发送对象类型的消息
@Test
public void sendUserToObjectQueue1(){
String queueName="object.queue";
User user = new User();
user.setId(1001);
user.setName("Lily");
user.setAge(24);
user.setEmail("lily@163.com");
rabbitTemplate.convertAndSend(queueName,user);
System.out.println(LocalTime.now()+"发送对象类型消息\n"+user+"\n到object.queue");
}
@Test
public void sendUserToObjectQueue2(){
String queueName="object.queue";
Map<String,Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",24);
rabbitTemplate.convertAndSend(queueName,map);
System.out.println(LocalTime.now()+"发送对象类型消息\n"+map+"\n到object.queue");
}
启动consumer微服务,查看消息
通过打断点调试,可以发现对于对象类型的消息RabbitTemplate会使用MessageConverter来进行序列化,但是序列化后可读性很差,并且占用较大的内存空间。

编写消息接收代码:
@RabbitListener(queues = "object.queue")
public void listenToObjectQueue2(Map<String ,Object> msg){
System.out.println(LocalTime.now()+"接收到来自object.queue的消息:\n"+msg);
}
重启consumer微服务,接收到的对象类型消息也是序列化的代码

对象类型消息序列化优化:
两个微服务中都导入以下依赖,使用json方式进行序列化
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
在两个微服务的启动类中添加Bean
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
测试:
重启服务,发送对象类型的消息
发消息:

rabbitmq客户端:

接收消息:

小结:
RabbitTemplate默认的MessageConver方式可以使用Jackson2JsonMessageConverter进行优化,即将对象类型的消息转换(序列化)成json格式。
问题 & 解决方案
1.在创建springboot工程后可以在编写逻辑代码前先运行启动类,保证启动类能正常启动,防止后续出现启动异常等问题。
2.启动类报错找不到主类时,可以使用maven-clean和maven-install命令,之后再次尝试启动。
3.test的包名要和main/java下的包名一致,否则出现找不到配置类,RabbitTemplate注入不成功的问题

浙公网安备 33010602011771号