今天小七给大家介绍一下在Spring Boot项目中如何使用RabbitMQ,下面直入正题吧。
之前的博文,小七有介绍过RabbitMQ以及如何安装,这里就不再赘述了,请查看下面的博文地址:
https://blog.csdn.net/qiyongkang520/category_6751853.html
pom.xml中添加如下依赖即可:
org.springframework.boot
spring-boot-starter-amqp
.yml文件中添加如下配置:
spring:
rabbitmq:
host: 172.16.3.108
port: 5672
username: admin
password: admin123
virtualHost: cn
这里virtualHost可以不配置,就表示所有的交换机和队列都在/下创建。
目前RabbitMQ提供了direct、topic、headers、fanout四种交换机类型,每种类型都有自己的特点,大家可以根据具体的业务需求来选择使用,下面小七先简单介绍一下。
首先大家都知道,咱们应用系统给RabbitMQ发消息,不是直接发给队列,而是发给交换机,然后交换机根据自己的规则(路由key、键值对属性等)来分发到相应的队列上,也就是说交换机和队列之间是有绑定关系的,分别对应如下:
下面小七会一一介绍并截图,就容易理解多了。
在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管理后台的页面,首先是交换机,截图如下:
然后,再来看下队列,如下图:
接下来,再点进去看一下和队列的绑定关系,截图如下:
这个相信大家能看懂,消息发送到此交互机后,是通过发消息时指定的Routing key和上面的路由规则完全匹配来决定发送到哪个队列的。
下面,咱们再来运行一下testSendDirectMessage,运行日志如下:
就是按咱们说的规则来的,大家可以把QUEUE_DIRECT_MESSAGE1_APP1和QUEUE_DIRECT_MESSAGE2_APP1这两个队列的路由key指定成一样的,然后再运行testSendDirectMessage,就会发现当路由匹配上时,这两个队列都会收到此条消息。
这里只需要关注接收类的process3、process4以及process5,下面直接先运行testCreateQueueAndExchange,来看下控制台交换机的效果,截图如下:
通配符的含义,简单说一下:
这里,只需要关注消息接收类的process6和process7,咱们可以直接运行testCreateQueueAndExchange,先来看下控制台交换机的样子,如下:
这里先说明一下x-match的作用:
这里大家只需要关注process8和process9,这个交换机类型比较简单,就像订阅模式一下,只要订阅了就会收到消息。下面直接截图:
大家可以运行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交换机的绑定关系,如下图:
QYK_RECEIVE_DIRECT_EXCHANGE的,小七也截图出来,如下:
看到没有,QYK_RECEIVE_DIRECT_EXCHANGE不是队列,是交换机。那么当客户端发消息到QYK_SEND_TOPIC_EXCHANGE交换机时,如果指定的路由如果能匹配到QYK_RECEIVE_DIRECT_EXCHANGE上,之后是怎么处理的呢。其实很简单,再把此路由放到QYK_RECEIVE_DIRECT_EXCHANGE上继续路由匹配就行了。
大家可以运行testSendExchangeExchange来看一下效果,小七就不演示了。
上面,小七没有介绍如何给RabbitMQ发消息,首先引入按下面方式引入客户端即可:
@Autowired
private AmqpTemplate rabbitTemplate;
然后,发现消息的时候,指定交换机、路由、键值对以及消息等即可。
好了,如果有不明白的童鞋,一定要亲自动手试试,就能明白啦。