淘先锋技术网

首页 1 2 3 4 5 6 7

SpringBoot整合RabbitMQ

1、RabbitMQ核心概念

在这里插入图片描述

Broker:接收和分发消息的应用,RabbitMQ Server就是Message Broker。可以看做RabbitMQ的服务节

点,一般请下一个Broker可以看做一个RabbitMQ服务器,简单来说就是消息队列服务器实体。

Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中

的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,

每个用户在自己的vhost创建exchange/queue等。vhost可以作为不同权限隔离的手段(一个典型的例子就是

不同的应用可以跑在不同的vhost中)。

Connection:publisher/consumer和broker之间的TCP连接。

Channel:如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开

销将是巨大的,效率也较低。Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通

常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message

broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系

统建立TCP connection的开销。channel消息通道,在客户端的每个连接里,可建立多个channel,每个

channel代表一个会话任务由Exchange、Queue、RoutingKey三个才能决定一个从Exchange到Queue的唯

一的线路。

Exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到

queue中去。常用的类型有:headers、direct (point-to-point),topic (publish-subscribe) and fanout

(multicast)。Exchange生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队列中。当路由不

到时,或返回给生产者或直接丢弃。简言之消息交换机,它指定消息按什么规则,路由到哪个队列。用来接

收生产者发送的消息并将这些消息路由给服务器中的队列。

Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入

一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。消息最终被送到这里等待

consumer取走。

Binding:exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到

exchange中的查询表中,用于message的分发依据。简言之Binding它的作用就是把exchange和queue按照

路由规则绑定起来。

Routing Key:生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规

则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。路由关键字,

exchange根据这个关键字进行消息投递。

Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。

Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

Message:消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的

可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指

出该消息可能需要持久性存储)等。

2、整合

RabbitMQ版本 3.8.16,SpringBoot版本 2.5.4。

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rabbitmq-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq-demo</name>
    <description>rabbitmq-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.7</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

注:这里可以不指定AMQP版本,SpringBoot会自动导入与该SpringBoot版本相搭配的AMQP的版本。

配置文件

# RabbitMQ基本配置
# RabbitMQ的主机地址(默认为:localhost)
spring.rabbitmq.host=localhost
# 指定该用户要连接到的虚拟host端(注:如果不指定,那么默认虚拟host为"/")
spring.rabbitmq.virtual-host = /
# amqp协议端口号:5672; 集群端口号:25672;http端口号:15672;
spring.rabbitmq.port=5672
# 登录到RabbitMQ的用户名、密码
spring.rabbitmq.username=zsx242030
spring.rabbitmq.password=zsx242030
# RabbitMQ可选配置(注:这里只用到了特别少的几个)
# broker端没有收到消费者的ACK(即:消费者异常时)时,是否再次向消费者投递消息(默认为false)
# 为false时,如果没有收到消费者的ACK,那么会无限投递;设置为true时,默认投递时次数为3此
spring.rabbitmq.listener.simple.retry.enabled=true
# 设置向消费者投递消息的最大次数
spring.rabbitmq.listener.simple.retry.max-attempts=2
# 投递消息的间隔(单位ms)
spring.rabbitmq.listener.simple.retry.initial-interval=2000ms

用户实体类

package com.example.model;

/**
 * 用户实体类模型
 */
public class User {

	/** 姓名 */
	private String name;

	/** 年龄 */
	private Integer age;

	/** 性别 */
	private String gender;

	/** 座右铭 */
	private String motto;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getMotto() {
		return motto;
	}

	public void setMotto(String motto) {
		this.motto = motto;
	}

	@Override
	public String toString() {

		return age + "岁" + gender + "人[" + name + "]的座右铭居然是: " + motto + "!!!";
	}

}

Consumer -> QueueConfiguration配置类 -> 定义队列

这个是公共的队列,每个类型的交换机都会用到这些队列。

package com.example.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Queue队列注入
 */
@Configuration
public class QueueConfiguration {

	/**
	 * 注入第一个Queue队列 实例
	 */
	@Bean(name = "myFirstQueue")
	public Queue getFirstQueue() {
		// 设置队列名为My-First-Queue
		return new Queue("My-First-Queue");
	}

	/**
	 * 注入第二个Queue队列 实例
	 */
	@Bean(name = "myTwoQueue")
	public Queue getTwoQueue() {
		// 设置队列名为My-Two-Queue
		return new Queue("My-Two-Queue");
	}

	/**
	 * 注入第三个Queue队列 实例
	 */
	@Bean(name = "myThreeQueue")
	public Queue getThreeQueue() {
		// 设置队列名为My-Three-Queue
		return new Queue("My-Three-Queue");
	}
	
	/**
	 * 关于MQ传递消息时,实际上用的HttpClient的什么请求方式的测试
	 */
	@Bean(name = "myRequestTestQueue")
	public Queue getRequestTestQueue() {
		// 设置队列名为My-Request-Test-Queue
		return new Queue("My-Request-Test-Queue");
	}
}

Consumer -> DemoMessageListener监听类

这个是公共的监听器,每个类型的交换机都会用到这些监听器。

package com.example.listen;

import com.example.model.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;

/**
 * 注入消息监听器
 */
@Component
public class DemoMessageListener {

	@RabbitListener(queues = "My-First-Queue") // 指定Queue队列
	public void firstConsumer(String string) {

		System.out.println("我是:My-First-Queue" + "\tString:" + string);
	}

	@RabbitListener(queues = "My-Two-Queue") // 指定Queue队列
	public void twoConsumer(Integer num) {

		System.out.println("我是:My-Two-Queue" + "\tInteger:" + num);
	}

	@RabbitListener(queues = "My-Three-Queue") // 指定Queue队列
	public void threeConsumer() {
		System.out.println("我是:My-Three-Queue");

	}
	
	@RabbitListener(queues = "My-Request-Test-Queue") // 指定Queue队列
	public void requestTestConsumer(String userJsonString) {
		System.out.println("我是:My-Request-Test-Queue队列");
		System.out.println("json字符串为:" + userJsonString);
        // 将json字符串 转换为 User对象
		User user=JSON.parseObject(userJsonString, User.class);
		System.out.println(user.toString());
	}
}

启动类

package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RabbitmqDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(RabbitmqDemoApplication.class, args);
	}

}

3、Fanout策略

Fanout:路由规则是把所有发送到该Exchange的消息路由到所有与她绑定的Queue中。

在这里插入图片描述

备注:生产者P生产消息1推送到Exchange,由于Exchange Type=fanout这时候会遵循fanout的规则将消息推送

到所有与他绑定的Queue。

Consumer -> FanoutExchangeAndBindingConfiguration配置类 -> 定义交换机,绑定队列与交换机

package com.example.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * fanout路由策略(广播机制)的交换机注入、Queue与Exchange的绑定注入
 */
@Configuration
public class FanoutExchangeAndBindingConfiguration {

	/**
	 * 注入Fanout路由策略的Exchange交换机实例
	 */
	@Bean(name = "myFanoutExchange")
	FanoutExchange getFanoutExchange() {
		// 创建并返回名为My-Fanout-Exchange的交换机
		return new FanoutExchange("My-Fanout-Exchange");
	}

	/**
	 * 将myFirstQueue对应的队列,绑定到myFanoutExchange对应的交换机
	 */
	@Bean
	Binding bindingQueueOneToFanoutExchange(@Qualifier("myFirstQueue") Queue myFirstQueue,
			@Qualifier("myFanoutExchange") FanoutExchange myFanoutExchange) {
		return BindingBuilder.bind(myFirstQueue).to(myFanoutExchange);
	}

	/**
	 * 将myTwoQueue对应的队列,绑定到myFanoutExchange对应的交换机
	 */
	@Bean
	Binding bindingQueueTwoToFanoutExchange(@Qualifier("myTwoQueue") Queue myTwoQueue,
			@Qualifier("myFanoutExchange") FanoutExchange myFanoutExchange) {
		return BindingBuilder.bind(myTwoQueue).to(myFanoutExchange);
	}

	/**
	 * 将myThreeQueue对应的队列,绑定到myFanoutExchange对应的交换机
	 */
	@Bean
	Binding bindingQueueThreeToFanoutExchange(@Qualifier("myThreeQueue") Queue myThreeQueue,
			@Qualifier("myFanoutExchange") FanoutExchange myFanoutExchange) {
		return BindingBuilder.bind(myThreeQueue).to(myFanoutExchange);
	}
}

Producer -> 单元测试SimulationMessageProducerTest

package com.example;

import java.util.HashMap;
import java.util.Map;

import com.example.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.alibaba.fastjson.JSON;

/**
 * 模拟消息生产者 向Exchange发送消息 注:在正常使用时,消息生产者 一般都和 消息消费者 不处在同一个项目或服务下;
 * 这里只是测试,所以本人在同一个项目下,使用单元测试,模拟的消息生产者(用法是一样的)
 */

@SpringBootTest(classes = RabbitmqDemoApplication.class)
@RunWith(SpringRunner.class)
public class MessageProducerTest {

	/** 装配AMQP模板 */
	@Autowired
	private AmqpTemplate amqpTemplate;
	
	/** 装配RabbitMessaging模板 */
	@Autowired
	private RabbitMessagingTemplate rabbitMessagingTemplate;
	
	/**
	 * fanout路由策略(即:广播模式) 测试
	 */
	@Test
	public void fanoutExchangeTest() {

		amqpTemplate.convertAndSend("My-Fanout-Exchange", "", "123");
	}
}
我是:My-Three-Queue
我是:My-First-Queue	String:123
我是:My-Two-Queue	Integer:123

convertAndSend(String exchangeName,String routingKey,Object obj)

提示:当不采用direct或topic策略时,没有路由键,那么对应路由键参数位置使用双引号即可。

4、Direct策略

direct:把消息路由到那些 binding key 与 routing key 完全匹配的Queue中。

在这里插入图片描述

备注:生产者P发送消息时Routing key = booking时,这时候将消息传送到Exchange,Exchange获取到生产者发

送过来的消息后,会根据自身的规则进行与匹配响应的Queue,这时候发现Queue1和Queue2都符合,就会将消

息传送给这两个队列,如果我们以Routing key = create和routing key = confirm发送消息时,这时候消息只会被

推送到Queue2队列中,其他的Routing key 的消息会被丢弃。

Consumer -> DirectExchangeAndBindingConfiguration配置类 -> 定义交换机,绑定队列与交换机

package com.example.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * direct路由策略(路由键routingKey)的交换机注入、Queue与Exchange的绑定注入
 */
@Configuration
public class DirectExchangeAndBindingConfiguration {

	/**
	 * 注入Direct路由策略的Exchange交换机实例
	 */
	@Bean(name = "myDirectExchange")
	DirectExchange getDirectExchange() {
		// 创建并返回名为My-Direct-Exchange的交换机
		return new DirectExchange("My-Direct-Exchange");
	}

	/**
	 * 将Queue绑定到此directExchange,并指定路由键为"routingKey.First"
	 *
	 * @date 2018年7月19日 上午12:20:09
	 */
	@Bean
	Binding bindingQueueOneToDirectExchange(@Qualifier("myFirstQueue") Queue myFirstQueue,
			@Qualifier("myDirectExchange") DirectExchange myDirectExchange) {
		return BindingBuilder.bind(myFirstQueue).to(myDirectExchange).with("routingKey.First");
	}

	/**
	 * 将Queue绑定到此directExchange,并指定路由键为"requestTest"
	 *
	 * @date 2018年7月19日 上午12:20:09
	 */
	@Bean
	Binding bindingQueueRequestTestToDirectExchange(@Qualifier("myRequestTestQueue") Queue myRequestTestQueue,
			@Qualifier("myDirectExchange") DirectExchange myDirectExchange) {
		return BindingBuilder.bind(myRequestTestQueue).to(myDirectExchange).with("requestTest.aa");
	}
}

Producer -> 单元测试SimulationMessageProducerTest

package com.example;

import java.util.HashMap;
import java.util.Map;

import com.example.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.alibaba.fastjson.JSON;

/**
 * 模拟消息生产者 向Exchange发送消息 注:在正常使用时,消息生产者 一般都和 消息消费者 不处在同一个项目或服务下;
 * 这里只是测试,所以本人在同一个项目下,使用单元测试,模拟的消息生产者(用法是一样的)
 */

@SpringBootTest(classes = RabbitmqDemoApplication.class)
@RunWith(SpringRunner.class)
public class MessageProducerTest {

	/** 装配AMQP模板 */
	@Autowired
	private AmqpTemplate amqpTemplate;
	
	/** 装配RabbitMessaging模板 */
	@Autowired
	private RabbitMessagingTemplate rabbitMessagingTemplate;

	/**
	 * direct路由策略---测试HttpClient的请求方法
	 */
	@Test
	public void directExchangeRequestTest() {
		User user = new User();
		user.setAge(18);
		user.setGender("女");
		user.setMotto("不感兴趣!");
		user.setName("喵喵");
		String jsonString = JSON.toJSONString(user);
		amqpTemplate.convertAndSend("My-Direct-Exchange", "requestTest.aa",jsonString);
	}

	/**
	 * direct路由策略(具体的路由键)测试
	 */
	@Test
	public void directExchangeTest() {
		amqpTemplate.convertAndSend("My-Direct-Exchange", "routingKey.First", "1234578");
	}
}

单元测试directExchangeRequestTest()的运行结果为:

我是:My-Request-Test-Queue队列
json字符串为:{"age":18,"gender":"女","motto":"不感兴趣!","name":"喵喵"}
18岁女人[喵喵]的座右铭居然是: 不感兴趣!!!!

单元测试directExchangeTest()的运行结果为:

我是:My-First-Queue	String:1234578

5、Topic策略

topic:模糊匹配,通过通配符满足一部分规则就可以传送,其中注意的是有两个字符 *#号,其中*用于匹

配一个单词,#号用于匹配多个单词(可以是0个)。

在这里插入图片描述

备注:当生产者发送消息Routing Key=F.C.E的时候,这时候只满足Queue1,所以会被路由到Queue中,如果

Routing Key=A.C.E这时候会被同是路由到Queue1和Queue2中,如果Routing Key=A.F.B时,这里只会发送一条

消息到Queue2中。

Consumer -> TopicExchangeAndBindingConfiguration配置类 -> 定义交换机,绑定队列与交换机

package com.example.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * topic路由策略(路由键routingKey,且支持模糊匹配)的交换机注入、Queue与Exchange的绑定注入
 */
@Configuration
public class TopicExchangeAndBindingConfiguration {

	/**
	 * 注入Topic路由策略的Exchange交换机实例
	 */
	@Bean(name = "myTopicExchange")
	TopicExchange getTopicExchange() {
		// 创建并返回名为My-Topic-Exchange的交换机
		return new TopicExchange("My-Topic-Exchange");
	}
	
	
	/**
	 * 将myFirstQueue对应的Queue绑定到此topicExchange,并指定路由键为"routingKey.#"
	 * 即:此Exchange中,路由键以"routingKey."开头的Queue将被匹配到
	 */
	@Bean
	Binding bindingQueueOneToTopicExchange(@Qualifier("myFirstQueue") Queue myFirstQueue,
			@Qualifier("myTopicExchange") TopicExchange myTopicExchange) {
		return BindingBuilder.bind(myFirstQueue).to(myTopicExchange).with("routingKey.#");
	}
	
	/**
	 * 将myTwoQueue对应的Queue绑定到此topicExchange,并指定路由键为"#.topic"
	 * 即:此Exchange中,路由键以".topic"结尾的Queue将被匹配到
	 */
	@Bean
	Binding bindingQueueTwoToTopicExchange(@Qualifier("myTwoQueue") Queue myTwoQueue,
			@Qualifier("myTopicExchange") TopicExchange myTopicExchange) {
		return BindingBuilder.bind(myTwoQueue).to(myTopicExchange).with("#.topic");
	}
	
	/**
	 * 将myThreeQueue对应的Queue绑定到此topicExchange,并指定路由键为"#"
	 * 即:此topicExchange中,任何Queue都将被匹配到
	 */
	@Bean
	Binding bindingQueueThreeToTopicExchange(@Qualifier("myThreeQueue") Queue myThreeQueue,
			@Qualifier("myTopicExchange") TopicExchange myTopicExchange) {
		return BindingBuilder.bind(myThreeQueue).to(myTopicExchange).with("#");
	}
}

Producer -> 单元测试SimulationMessageProducerTest

package com.example;

import java.util.HashMap;
import java.util.Map;

import com.example.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.alibaba.fastjson.JSON;

/**
 * 模拟消息生产者 向Exchange发送消息 注:在正常使用时,消息生产者 一般都和 消息消费者 不处在同一个项目或服务下;
 * 这里只是测试,所以本人在同一个项目下,使用单元测试,模拟的消息生产者(用法是一样的)
 */

@SpringBootTest(classes = RabbitmqDemoApplication.class)
@RunWith(SpringRunner.class)
public class MessageProducerTest {

	/** 装配AMQP模板 */
	@Autowired
	private AmqpTemplate amqpTemplate;
	
	/** 装配RabbitMessaging模板 */
	@Autowired
	private RabbitMessagingTemplate rabbitMessagingTemplate;

	/**
	 * topic路由策略(可以通配的路由键)测试
	 */
	@Test
	public void topicExchangeTest1() {
		// 此消息能匹配上 路由键为“routingKey.#”和“#”的队列
		amqpTemplate.convertAndSend("My-Topic-Exchange", "routingKey.myTest", "1");
	}

	@Test
	public void topicExchangeTest2() {
		// 此消息能匹配上 路由键为“#.topic”和“#”的队列
		amqpTemplate.convertAndSend("My-Topic-Exchange", "myTest.topic", "2");
	}

	@Test
	public void topicExchangeTest3() {
		// 此消息能匹配上 路由键为“#”的队列
		amqpTemplate.convertAndSend("My-Topic-Exchange", "myTest", "3");
	}

}

单元测试topicExchangeTest1()的运行结果为:

我是:My-Three-Queue
我是:My-First-Queue	String:1

单元测试topicExchangeTest2()的运行结果为:

我是:My-Three-Queue
我是:My-Two-Queue	Integer:2

单元测试topicExchangeTest3()的运行结果为:

我是:My-Three-Queue

提示:如果输出结果与理想结果不一样;那么最好去MQ里,把原来与Queue绑定的Exchange删除,重新绑定Queue

队列到TopicExchange交换机并放入vhost里(原因分析:该交换机原来绑定的可能是没有通配路由键的Queue,改

成了路由键通配后,需要先删除vhost中原来的该交换机,再重新把绑定了通配路由键队列后的交换机放入vhost

里面)。

即:一旦将绑定了Queue的Exchange放入vhost后,那么该交换机对于其绑定了的Queue的“认知”就固定了;如果

想改变该交换机的认知,那么必须先删除该交换机,然后在放入有新“认知”的交换机。

追注:其他几种策略也一样,修改Queue与Exchange的绑定信息后,也最好去vhost中删除该交换机,再重新放

入新的交换机。

6、Headers策略

headers:单薄模式,1对1。

headers 匹配规则:anyall

any: 只要在发布消息时携带的有一对键值对headers满足队列定义的多个参数的其中一个就能匹配上,注意这

里是键值对的完全匹配,只匹配到键了,值却不一样是不行的;

all:在发布消息时携带的所有Entry必须和绑定在队列上的所有Entry完全匹配。

缺点:Headers类型的交换器性能会很差。

在这里插入图片描述

Consumer -> HeadersExchangeAndBindingConfiguration配置类 -> 定义交换机,绑定队列与交换机

package com.example.config;

import java.util.HashMap;
import java.util.Map;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * headers路由策略的交换机注入、Queue与Exchange的绑定注入
 */
@Configuration
public class HeadersExchangeAndBindingConfiguration {

	/**
	 * 注入Headers路由策略的Exchange交换机实例
	 */
	@Bean(name = "myHeadersExchange")
	HeadersExchange getDirectExchange() {
		// 创建并返回名为My-Headers-Exchange的交换机
		return new HeadersExchange("My-Headers-Exchange");
	}

	/**
	 * 将Queue绑定到此headersExchange(并指定:当headers中所有的“map”被此Queue匹配时,才可使用此队列)
	 * 注:此示例是匹配的.whereAll(Map<String,Object> map);
	 *    也可以只匹配.whereAll(String... headersKeys);
	 */
	@Bean
	Binding bindingQueueOneToHeadersAllExchange(@Qualifier("myFirstQueue") Queue myFirstQueue,
			@Qualifier("myHeadersExchange") HeadersExchange myHeadersExchange) {
		Map<String, Object> headers = new HashMap<>();
		headers.put("name", "邓沙利文");
		headers.put("motto", "justry");
		return BindingBuilder.bind(myFirstQueue).to(myHeadersExchange).whereAll(headers).match();
	}
	
	/**
	 * 将Queue绑定到此headersExchange(并指定:当headers中任意一个map被此Queue匹配时,就会使用此队列)
	 * 注:此示例是匹配的.whereAny(Map<String,Object> map);
	 *    也可以只匹配.whereAny(String... headersKeys);
	 */
	@Bean
	Binding bindingQueueOneToHeadersAnyExchange(@Qualifier("myThreeQueue") Queue myThreeQueue,
			@Qualifier("myHeadersExchange") HeadersExchange myHeadersExchange) {
		Map<String, Object> headers = new HashMap<>();
		headers.put("name", "邓沙利文");
		headers.put("motto", "justry");
		return BindingBuilder.bind(myThreeQueue).to(myHeadersExchange).whereAny(headers).match();
	}

}

注:headers中,可以存放很多用来验证身份的键值对,我们可以用All来指定:当消息生产者传到Exchange中的

headers中的键值对所有都符合(所有Map或所有Key)要求时,才启用该队列;或使用Any来指定:只要消息生产者

传到Exchange中的headers中的键值对有至少一个(至少一个Map或至少一个Key)符合要求时,就启用该队列。

Producer -> 单元测试SimulationMessageProducerTest

package com.example;

import java.util.HashMap;
import java.util.Map;

import com.example.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.alibaba.fastjson.JSON;

/**
 * 模拟消息生产者 向Exchange发送消息 注:在正常使用时,消息生产者 一般都和 消息消费者 不处在同一个项目或服务下;
 * 这里只是测试,所以本人在同一个项目下,使用单元测试,模拟的消息生产者(用法是一样的)
 */

@SpringBootTest(classes = RabbitmqDemoApplication.class)
@RunWith(SpringRunner.class)
public class MessageProducerTest {

	/** 装配AMQP模板 */
	@Autowired
	private AmqpTemplate amqpTemplate;
	
	/** 装配RabbitMessaging模板 */
	@Autowired
	private RabbitMessagingTemplate rabbitMessagingTemplate;

	/**
	 * headers路由策略---WhereAllMap匹配测试
	 */
	@Test
	public void headersExchangeWhereAllMapTest() {
		Map<String, Object> headers = new HashMap<String, Object>();
		headers.put("name", "邓沙利文");
		headers.put("motto", "justry");
		rabbitMessagingTemplate.convertAndSend("My-Headers-Exchange", "", "通过[头交换机]传递数据咯", headers);
	}

	/**
	 * headers路由策略测试WhereAnyMap匹配测试
	 */
	@Test
	public void headersExchangeWhereAnyMapTest() {
		Map<String, Object> headers = new HashMap<String, Object>();
		headers.put("name", "邓沙利文");
		headers.put("motto", "justry123");
		rabbitMessagingTemplate.convertAndSend("My-Headers-Exchange", "", "", headers);
	}

}

单元测试headersExchangeWhereAllMapTest()的运行结果为:

我是:My-Three-Queue
我是:My-First-Queue	String:通过[头交换机]传递数据咯

单元测试headersExchangeWhereAnyMapTest()的运行结果为:

2021-11-07 18:44:32.556  INFO 13428 --- [           main] com.example.MessageProducerTest          : Started MessageProducerTest in 2.422 seconds (JVM running for 3.182)
我是:My-Three-Queue

提示:本文只是对SpringBoot使用MQ的一个示例介绍;在实际运用时,方式方法注意事项等较多。

如果想传递对象,那么可以:消息生产者将对象转换为json字符串放入MQ,然后消息消费者接受json字符串后,

转换为Java对象即可。