SpringBoot2.x学习之路(五)RabbitMQ的使用

今天小七给大家介绍一下在Spring Boot项目中如何使用RabbitMQ,下面直入正题吧。

(一)RabbitMQ的安装以及介绍

之前的博文,小七有介绍过RabbitMQ以及如何安装,这里就不再赘述了,请查看下面的博文地址:
https://blog.csdn.net/qiyongkang520/category_6751853.html

(二)RabbitMQ的依赖引入

pom.xml中添加如下依赖即可:


      org.springframework.boot
      spring-boot-starter-amqp
    

(三)RabbitMQ的文件配置

.yml文件中添加如下配置:

spring:
  rabbitmq:
    host: 172.16.3.108
    port: 5672
    username: admin
    password: admin123
    virtualHost: cn

这里virtualHost可以不配置,就表示所有的交换机和队列都在/下创建。

(四)交换机的简单说明

目前RabbitMQ提供了direct、topic、headers、fanout四种交换机类型,每种类型都有自己的特点,大家可以根据具体的业务需求来选择使用,下面小七先简单介绍一下。
首先大家都知道,咱们应用系统给RabbitMQ发消息,不是直接发给队列,而是发给交换机,然后交换机根据自己的规则(路由key、键值对属性等)来分发到相应的队列上,也就是说交换机和队列之间是有绑定关系的,分别对应如下:

  • direct:交换机和队列是通过路由Key来匹配,并且是完全匹配;
  • topic:交换机和队列是通过路由Key来匹配,但是支持通配符匹配,也就是说给此类型交换机发消息时指定的路由Key如果满足多个路由的通配符匹配,那么这些队列都可以收到消息;
  • headers:交换机和队列是通过键值对来任一匹配或者完全匹配
  • fanout:交换机和队列只要绑定了即可,不需要指定任何规则,这些队列都可以收到消息

下面小七会一一介绍并截图,就容易理解多了。

(五)Direct交换机的使用

在Spring Boot怎么创建交换机、队列以及绑定呢,其实很简单,这里小七介绍两种方式,一是在配置类中创建,二是在接收消息监听类中创建。
这里小七先统一使用第二种方式,后面会介绍第一种方式。
小七,先一次性贴出监听类的所有代码MQReceiver.java:

package org.qyk.springboot.rabbitmq;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 
 *
 * @version 1.0
 * 
 * Author       Date            Changes
 * yongkang.qi   2020年04月08日   Created
 *
 * 
* @since 1.7 */ @Component public class MQReceiver { private Logger logger = LoggerFactory.getLogger(MQReceiver.class); // EXCHANGE final static String EXCHANGE_DIRECT_EXCHANGE = "QYK_DIRECT_EXCHANGE"; final static String EXCHANGE_TOPIC_EXCHANGE = "QYK_TOPIC_EXCHANGE"; final static String EXCHANGE_HEADER_EXCHANGE = "QYK_HEADER_EXCHANGE"; final static String EXCHANGE_FANOUT_EXCHANGE = "QYK_FANOUT_EXCHANGE"; final static String EXCHANGE_SEND_TOPIC_EXCHANGE = "QYK_SEND_TOPIC_EXCHANGE"; final static String EXCHANGE_RECEIVE_DIRECT_EXCHANGE = "QYK_RECEIVE_DIRECT_EXCHANGE"; // QUEUE final static String QUEUE_DIRECT_MESSAGE1_APP1 = "QYK_DIRECT_MESSAGE1.APP1"; final static String QUEUE_DIRECT_MESSAGE2_APP1 = "QYK_DIRECT_MESSAGE2.APP1"; final static String QUEUE_DIRECT_MESSAGE3_APP1 = "QYK_DIRECT_MESSAGE3.APP1.AUR"; final static String QUEUE_DIRECT_MESSAGE4_APP1 = "QYK_DIRECT_MESSAGE4.APP1.AUR"; final static String QUEUE_TOPIC_MESSAGE1_APP1 = "QYK_TOPIC_MESSAGE1.APP1"; final static String QUEUE_TOPIC_MESSAGE2_APP1 = "QYK_TOPIC_MESSAGE2.APP1"; final static String QUEUE_TOPIC_MESSAGE3_APP1 = "QYK_TOPIC_MESSAGE3.APP1"; final static String QUEUE_HEADER_MESSAGE1_APP1 = "QYK_HEADER_MESSAGE1.APP1"; final static String QUEUE_HEADER_MESSAGE2_APP1 = "QYK_HEADER_MESSAGE2.APP1"; final static String QUEUE_FANOUT_MESSAGE1_APP1 = "QYK_FANOUT_MESSAGE1.APP1"; final static String QUEUE_FANOUT_MESSAGE2_APP1 = "QYK_FANOUT_MESSAGE2.APP1"; // ROUTE KEY final static String ROUTE_KEY_CNR = "#.CNR"; final static String ROUTE_KEY_USR = "#.USR"; final static String ROUTE_KEY_EUR = "#.EUR"; final static String ROUTE_KEY_AUR = "#.AUR"; // DIRECT: Exchange、Queue、QueueBinding @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_DIRECT_MESSAGE1_APP1), exchange = @Exchange(value = EXCHANGE_DIRECT_EXCHANGE), key = QUEUE_DIRECT_MESSAGE1_APP1 )) public void process1(String message) { logger.error("MqReceiver [QYK_DIRECT_MESSAGE1.APP1]: {}", message); } // DIRECT: Exchange、Queue、QueueBinding @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_DIRECT_MESSAGE2_APP1), exchange = @Exchange(value = EXCHANGE_DIRECT_EXCHANGE), key = QUEUE_DIRECT_MESSAGE2_APP1 )) public void process2(String message) { logger.error("MqReceiver [QYK_DIRECT_MESSAGE2.APP1]: {}", message); } // TOPIC: Exchange、Queue、QueueBinding @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_TOPIC_MESSAGE1_APP1), exchange = @Exchange(value = EXCHANGE_TOPIC_EXCHANGE, type ="topic"), key = {ROUTE_KEY_CNR} )) public void process3(String message) { logger.error("MqReceiver [QUEUE_TOPIC_MESSAGE1_APP1]: {}", message); } // TOPIC: Exchange、Queue、QueueBinding @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_TOPIC_MESSAGE2_APP1), exchange = @Exchange(value = EXCHANGE_TOPIC_EXCHANGE, type ="topic"), key = {ROUTE_KEY_USR,ROUTE_KEY_EUR,ROUTE_KEY_AUR} )) public void process4(String message) { logger.error("MqReceiver [QUEUE_TOPIC_MESSAGE2_APP1]: {}", message); } // TOPIC: Exchange、Queue、QueueBinding @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_TOPIC_MESSAGE3_APP1), exchange = @Exchange(value = EXCHANGE_TOPIC_EXCHANGE, type ="topic"), key = {ROUTE_KEY_CNR,ROUTE_KEY_USR} )) public void process5(String message) { logger.error("MqReceiver [QUEUE_TOPIC_MESSAGE3_APP1]: {}", message); } // Headers: @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_HEADER_MESSAGE1_APP1), exchange = @Exchange(value = EXCHANGE_HEADER_EXCHANGE, type ="headers"), arguments = {@Argument(name = "One",value = "A"), @Argument(name = "Two",value = "B"), @Argument(name = "x-match",value = "any")} )) public void process6(String message) { logger.error("MqReceiver [QUEUE_HEADER_MESSAGE1_APP1]: {}", message); } // Headers: @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_HEADER_MESSAGE2_APP1), exchange = @Exchange(value = EXCHANGE_HEADER_EXCHANGE, type ="headers"), arguments = {@Argument(name = "One",value = "A"), @Argument(name = "Two",value = "B"), @Argument(name = "x-match",value = "all")} )) public void process7(String message) { logger.error("MqReceiver [QUEUE_HEADER_MESSAGE2_APP1]: {}", message); } // Fanout: @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_FANOUT_MESSAGE1_APP1), exchange = @Exchange(value = EXCHANGE_FANOUT_EXCHANGE, type ="fanout") )) public void process8(String message) { logger.error("MqReceiver [QUEUE_FANOUT_MESSAGE1_APP1]: {}", message); } // Fanout: @RabbitListener(bindings = @QueueBinding( value = @Queue(QUEUE_FANOUT_MESSAGE2_APP1), exchange = @Exchange(value = EXCHANGE_FANOUT_EXCHANGE, type ="fanout") )) public void process9(String message) { logger.error("MqReceiver [QUEUE_FANOUT_MESSAGE2_APP1]: {}", message); } // 交换机绑定交换机 @RabbitListener(queues = QUEUE_DIRECT_MESSAGE3_APP1) public void process10(String message) { logger.error("MqReceiver [QYK_DIRECT_MESSAGE3_APP1]: {}", message); } // 交换机绑定交换机 @RabbitListener(queues = QUEUE_DIRECT_MESSAGE4_APP1) public void process11(String message) { logger.error("MqReceiver [QYK_DIRECT_MESSAGE4_APP1]: {}", message); } }

目前只需要关注process1和process2。
下面,咱们再来创建一下个单元测试类,代码如下:

package org.qyk.springboot.rabbitmq;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


/**
 * 
 *
 * @version 1.0
 * 
 * Author       Date            Changes
 * yongkang.qi   2020年04月08日   Created
 *
 * 
* @since 1.7 */ @SpringBootTest @RunWith(SpringRunner.class) public class RabbitMQTest { @Autowired private AmqpTemplate rabbitTemplate; @Test public void testCreateQueueAndExchange() { System.out.println("***********"); System.out.println("***********"); } @Test public void testSendDirectMessage() throws InterruptedException { rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_DIRECT_EXCHANGE, MQReceiver.QUEUE_DIRECT_MESSAGE1_APP1, "hello"); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_DIRECT_EXCHANGE, MQReceiver.QUEUE_DIRECT_MESSAGE2_APP1, "hello"); Thread.sleep(5000); } @Test public void testSendTopicMessage() throws InterruptedException { rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_TOPIC_EXCHANGE, "TOPIC.MESSAGE.CNR", "hello"); Thread.sleep(3000); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_TOPIC_EXCHANGE, "USR.TOPIC.MESSAGE", "hello"); Thread.sleep(3000); System.out.println("***********"); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_TOPIC_EXCHANGE, "TOPIC.MESSAGE.EUR", "hello"); Thread.sleep(5000); } @Test public void testSendHeadersMessage() throws InterruptedException { String msg = "hello"; MessageProperties messageProperties = new MessageProperties(); messageProperties.setHeader("One", "A"); Message message = new Message(msg.getBytes(), messageProperties); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_HEADER_EXCHANGE, null, message); Thread.sleep(3000); messageProperties = new MessageProperties(); messageProperties.setHeader("One", "A"); messageProperties.setHeader("Two", "B"); message = new Message(msg.getBytes(), messageProperties); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_HEADER_EXCHANGE, null, message); Thread.sleep(5000); } @Test public void testSendFanoutMessage() throws InterruptedException { rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_FANOUT_EXCHANGE, null, "hello"); Thread.sleep(5000); } @Test public void testSendExchangeExchange() throws InterruptedException { rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_SEND_TOPIC_EXCHANGE, "TEST.CNR", "hello"); Thread.sleep(3000); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_SEND_TOPIC_EXCHANGE, "TEST.USR", "hello"); Thread.sleep(3000); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_SEND_TOPIC_EXCHANGE, MQReceiver.QUEUE_DIRECT_MESSAGE3_APP1, "hello"); Thread.sleep(3000); rabbitTemplate.convertAndSend(MQReceiver.EXCHANGE_SEND_TOPIC_EXCHANGE, MQReceiver.QUEUE_DIRECT_MESSAGE4_APP1, "hello"); Thread.sleep(5000); } }

这里只需要关注testCreateQueueAndExchange和testSendDirectMessage两个单元测试方法。
首先,咱们来运行testCreateQueueAndExchange,然后看下RabbitMQ管理后台的页面,首先是交换机,截图如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第1张图片
然后,再来看下队列,如下图:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第2张图片
接下来,再点进去看一下和队列的绑定关系,截图如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第3张图片
这个相信大家能看懂,消息发送到此交互机后,是通过发消息时指定的Routing key和上面的路由规则完全匹配来决定发送到哪个队列的。
下面,咱们再来运行一下testSendDirectMessage,运行日志如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第4张图片
就是按咱们说的规则来的,大家可以把QUEUE_DIRECT_MESSAGE1_APP1和QUEUE_DIRECT_MESSAGE2_APP1这两个队列的路由key指定成一样的,然后再运行testSendDirectMessage,就会发现当路由匹配上时,这两个队列都会收到此条消息。

(六)Topic交换机的使用

这里只需要关注接收类的process3、process4以及process5,下面直接先运行testCreateQueueAndExchange,来看下控制台交换机的效果,截图如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第5张图片
通配符的含义,简单说一下:

  • *(星号)仅代表一个单词
  • #(井号)代表任意个单词
    也就是说只要发送消息给此交互机时,如果指定的路由key能匹配到以上的通配符路由规则,那么对应的队列就可以收到此条消息。
    下面,大家可以运行testSendTopicMessage测试看看,这里小七就不演示了。

(七)Headers交换机的使用

这里,只需要关注消息接收类的process6和process7,咱们可以直接运行testCreateQueueAndExchange,先来看下控制台交换机的样子,如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第6张图片
这里先说明一下x-match的作用:

  • any:发送消息给交换机时指定的键值对,只要有一对键值匹配上,此队列就可以收到消息
  • all:发送消息给交换机时指定的键值对,所有的键值对都必须匹配上,此队列才能收到消息。
    大家可以运行testSendHeadersMessage看一下效果,小七也不演示啦。

(八)Fanout交换机的使用

这里大家只需要关注process8和process9,这个交换机类型比较简单,就像订阅模式一下,只要订阅了就会收到消息。下面直接截图:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第7张图片
大家可以运行testSendFanoutMessage来看一下效果。

(九)交换机绑定交换机

上面咱们介绍的都是队列如何与交换机绑定的,其实,交换机也可以与交互机绑定,下面咱们来一起看看,这里小七通过配置的方式来创建交换机、队列以及绑定关系,配置类如下:

package org.qyk.springboot.rabbitmq;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 
 *
 * @version 1.0
 * 
 * Author       Date            Changes
 * yongkang.qi   2020年04月08日   Created
 *
 * 
* @since 1.7 */ @Configuration public class RabbitMQConfig { @Bean(name = "directQueue1") public Queue directQueue1() { return new Queue(MQReceiver.QUEUE_DIRECT_MESSAGE1_APP1); } @Bean(name = "directQueue2") public Queue directQueue2() { return new Queue(MQReceiver.QUEUE_DIRECT_MESSAGE2_APP1); } @Bean(name = "directQueue3") public Queue directQueue3() { return new Queue(MQReceiver.QUEUE_DIRECT_MESSAGE3_APP1); } @Bean(name = "directQueue4") public Queue directQueue4() { return new Queue(MQReceiver.QUEUE_DIRECT_MESSAGE4_APP1); } @Bean(name = "sendTopicExchange") public TopicExchange sendTopicExchange() { return new TopicExchange(MQReceiver.EXCHANGE_SEND_TOPIC_EXCHANGE); } @Bean(name = "receiveDirectExchange") public DirectExchange receiveDirectExchange() { return new DirectExchange(MQReceiver.EXCHANGE_RECEIVE_DIRECT_EXCHANGE); } @Bean Binding bindingExchangeQueue1(@Qualifier("directQueue1") Queue queue, TopicExchange sendTopicExchange) { return BindingBuilder.bind(queue).to(sendTopicExchange).with(MQReceiver.ROUTE_KEY_CNR); } @Bean Binding bindingExchangeQueue2(@Qualifier("directQueue2") Queue queue, TopicExchange sendTopicExchange) { return BindingBuilder.bind(queue).to(sendTopicExchange).with(MQReceiver.ROUTE_KEY_USR); } @Bean Binding bindingExchangeExchange(DirectExchange receiveDirectExchange, TopicExchange sendTopicExchange) { return BindingBuilder.bind(receiveDirectExchange).to(sendTopicExchange).with(MQReceiver.ROUTE_KEY_AUR); } @Bean Binding bindingExchangeQueue3(@Qualifier("directQueue3") Queue queue, DirectExchange receiveDirectExchange) { return BindingBuilder.bind(queue).to(receiveDirectExchange).with(MQReceiver.QUEUE_DIRECT_MESSAGE3_APP1); } @Bean Binding bindingExchangeQueue4(@Qualifier("directQueue4") Queue queue, DirectExchange receiveDirectExchange) { return BindingBuilder.bind(queue).to(receiveDirectExchange).with(MQReceiver.QUEUE_DIRECT_MESSAGE4_APP1); } }

咱们再来看下QYK_SEND_TOPIC_EXCHANGE交换机的绑定关系,如下图:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第8张图片
QYK_RECEIVE_DIRECT_EXCHANGE的,小七也截图出来,如下:
SpringBoot2.x学习之路(五)RabbitMQ的使用_第9张图片
看到没有,QYK_RECEIVE_DIRECT_EXCHANGE不是队列,是交换机。那么当客户端发消息到QYK_SEND_TOPIC_EXCHANGE交换机时,如果指定的路由如果能匹配到QYK_RECEIVE_DIRECT_EXCHANGE上,之后是怎么处理的呢。其实很简单,再把此路由放到QYK_RECEIVE_DIRECT_EXCHANGE上继续路由匹配就行了。
大家可以运行testSendExchangeExchange来看一下效果,小七就不演示了。

(十)结语

上面,小七没有介绍如何给RabbitMQ发消息,首先引入按下面方式引入客户端即可:

@Autowired
    private AmqpTemplate rabbitTemplate;

然后,发现消息的时候,指定交换机、路由、键值对以及消息等即可。
好了,如果有不明白的童鞋,一定要亲自动手试试,就能明白啦。

你可能感兴趣的:(Spring,Boot,消息队列)