SpringBoot整合RabbitMQ direct交换机、fanout交换机、topic交换机

回调函数相关
SpringBoot整合RabbitMQ 回调函数 direct交换机、fanout交换机、topic交换机

PS 常见错误

1、有匹配到交换机,但是没有匹配到绑定的队列。(交换机没有绑定队列)- not route

2、没有匹配到交换机。(交换机名称错误,not found - exchange)

3、交换机和队列都没有匹配(和第二种状态一样,没有匹配到交换机,直接返回。)

本篇主要讲的是

直连交换机(amq.direct)、

扇形交换机(amq.fanout)、

主题交换机(amq.topic)。

MQ中交换机和队列是绑定的关系,其中这三个交换机之间的差别就是,绑定路由键值的不同,看以下代码和解释:

java 复制代码
1.BindingBuilder.bind(queue()).to(exchange()).with(routingkey);
2.BindingBuilder.bind(queue()).to(exchange());
第一种是直连交换机和主题交换机的用法。
第二种是扇形交换机的用法,不需要绑定路由键。
queue()方法:自定义的队列,也可以使用已存在的队列
	new Queue("已存在的队列名或自定义队列名",true,true,false)。
	1.durable,是否持久化,默认为false。若为true,则将队列放入到磁盘中。否则只存再内存中,宕机后,所有数据都会消失。
	2.exclusive(排他性队列),是否仅当前连接可见,默认为false。若为true,则该队列在当前连接断开后,就会删除该队列,其优先级高于durable,也就是说无论是否设置为持久化,该操作都生效。
	3.autoDelete,自动删除队列,默认为false。若为true,则当消费者宕机后,会自动删除该队列。此时若生产者持续发送消息,这期间的发送内容都会丢失。
			
	这些自己测试一下就知道了。
exchange()方法:自定义的交换机,也可以使用已存在的交换机。
	(直连交换机) new DirectExchange("已存在交换机名或自定义交换机名",true);
	1.durable
routinekey:交换机与队列之间绑定的路由键。发送消息时,routinekey错误,则无法获取队列,则无法进行消息发送。
            直连交换机和主题交换机是必须加路由键,扇形交换机则不是必须加的。

项目依赖及配置

POM的依赖文件,基于SpringBoot 2.4.3版本,生产者项目和消费者项目都可以使用这样的依赖。

java 复制代码
<?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.4.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.mq.producter</groupId>
	<artifactId>mqproducter</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>mqproducter</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

application.properties配置如下,生产者和消费者都可用一样的。

java 复制代码
spring.application.name=MqProducer
##配置rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

1、直连交换机

直连交换机与队列进行绑定时,需要绑定一个路由键X。此时若有消息载体中携带了X路由键,则会找到对应的队列,并将消息存放到对应的队列中。

直连交换机 及 队列配置代码

java 复制代码
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
/**
 * 直连交换机配置
 */
public class DirectConfig {
    @Bean
    Queue directQueue(){
        return new Queue("directQueue",true);
    }
	
	@Bean
    DirectExchange createExchange(){
    	//原本存在的交换机,都是持久性的。
        return new DirectExchange("amq.direct");
    }
	
    @Bean
    Binding bindingExchange(){
    //可以绑定自定义交换机或者绑定已存在的交换机
        return BindingBuilder.bind(directQueue()).to(createExchange()).with("directRouting");
    }
}

监听直连交换机 及 队列配置

监听直连交换机绑定的队列

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"directQueue"})
public class DirectListener {
    @RabbitHandler
    public void process(Map m){
        System.out.println("DirectListener接受到的消息为:"+m.toString());
    }
}

2、扇形交换机(广播)

扇形交换机与队列进行绑定,不需要绑定路由键,就算绑定了也没有作用。若扇形交换机中绑定了N个队列,此时有消息载体到了扇形交换机,则会被存放到扇形交换机绑定的所有队列中,消费者消费时,就会消费其所有队列的消息。

扇形交换机 及 队列配置代码

java 复制代码
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
/**
 * 直连交换机配置
 */
public class FanoutConfig {
    @Bean
    Queue firstFanoutQueue(){
        return new Queue("firstFanoutQueue",true);
    }
    @Bean
    Queue secondFanoutQueue(){
        return new Queue("secondFanoutQueue",true);
    }
	
	@Bean
    FanoutExchange createFanoutExchange(){
        return new FanoutExchange("amq.fanout");
    }
	
    @Bean
    Binding bindingFirstQueue(){
    //可以绑定自定义交换机或者绑定已存在的交换机
    //需要注意的是,扇形交换机with就不需要了
        return BindingBuilder.bind(firstFanoutQueue()).to(createFanoutExchange());
    }
    @Bean
    Binding bindingSeccondQueue(){
    //可以绑定自定义交换机或者绑定已存在的交换机
        return BindingBuilder.bind(secondFanoutQueue()).to(createFanoutExchange());
    }
}

监听扇形交换机 及 队列配置

因为有N个队列,此时需要创建N个监听器,或者在一个监听器中,监听N个队列,这样子简单的监听就没办法对不同的队列进行逻辑处理了。

FirstFanoutListener.java

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"firstFanoutQueue"})
public class FirstFanoutListener {
    @RabbitHandler
    public void process(Map m){
        System.out.println("FirstFanoutListener接受到的消息为:"+m.toString());
    }
}

SecondFanoutListener.java

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"secondFanoutQueue"})
public class SecondFanoutListener {
    @RabbitHandler
    public void process(Map m){
        System.out.println("SecondFanoutListener接受到的消息为:"+m.toString());
    }
}

这两个类监听的是扇形交换机绑定的两个队列,若有消息载体进入扇形交换机,这两个监听类都会实时消费数据。

3、主题交换机

主题交换机和队列进行绑定时,可以通过通配符来进行路由键的模糊匹配。路由键可以使用topic.* 或者topic.#表示。其中*号代表了,句点后必须有1个或多个值。#号代表了,句点后必须有0个或多个值。

主题交换机是很强大的,它可以代替直连交换机和扇形交换机使用。若路由键中设置#号(说明路由键可以为空),则可以代替扇形交换机。若路由键不包含 *号或者#号(说明不存在模糊匹配,需要全匹配。)则可以代替直连交换机使用。

看一下以下的例子:

若有交换机绑定了队列1(topic.thing)、队列2(topic.
 此时若有消息载体携带topic.thing路由键,则消息就会进入队列1和队列2中。
 若有消息载体携带topic.other,则消息只会进入到队列2中,因为
号匹配了other。这就说明句点后可以输入任意字符。

主题交换机 及 队列配置代码

java 复制代码
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
/**
 * 直连交换机配置
 */
public class TopicConfig {
	@Bean
    Queue firstTopicQueue(){
        return new Queue("firstTopicQueue",true);
    }
    @Bean
    Queue secondTopicQueue(){
        return new Queue("secondTopicQueue",true);
    }

    @Bean
    TopicExchange createTopicExchange(){
        //选择已存在的交换机
        return new TopicExchange("amq.topic");
    }

    @Bean
    Binding bindingFirstTopicQueue(){
        //可以绑定自定义交换机或者绑定已存在的交换机
        //需要注意的是,扇形交换机with就不需要了
        return BindingBuilder.bind(firstTopicQueue()).to(createTopicExchange()).with("topic.thing");
    }
    @Bean
    Binding bindingSecondTopicQueue(){
        //可以绑定自定义交换机或者绑定已存在的交换机
        return BindingBuilder.bind(secondTopicQueue()).to(createTopicExchange()).with("topic.#");
    }
}

主题交换机的代码有所不同,在路由键中使用了通配符#

监听主题交换机 及 队列配置

因为有N个队列,此时需要创建N个监听器,或者在一个监听器中,监听N个队列,这样子简单的监听就没办法对不同的队列进行逻辑处理了。

FirstTopicListener.java

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"firstTopicQueue"})
public class FirstTopicListener {
    @RabbitHandler
    public void process(Map m){
        System.out.println("FirstTopicListener接受到的消息为:"+m.toString());
    }
}

SecondTopicListener.java

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
@RabbitListener(queues = {"secondTopicQueue"})
public class SecondTopicListener {
    @RabbitHandler
    public void process(Map m){
        System.out.println("SecondTopicListener接受到的消息为:"+m.toString());
    }
}

这两个类监听的是主题交换机绑定的两个队列,若有消息载体进入扇形交换机,携带绑定的路由键,就会进入到指定队列,不同的监听器就会实时消费。

发送信息

发送信息接口代码

交换机对应的简单的配置代码都已经完成了,接下来就写一下发送消息的接口。

java 复制代码
package com.mq.producter.mqproducter.action;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/mq")
public class ProducerAction {
    
    @Autowired
    RabbitTemplate rabbitTemplate;
    @RequestMapping("/sending")
    public void sending(){
        try{
            String message = "This is a first message!";
            Map<String,Object> m = commonMap(message);
            rabbitTemplate.convertAndSend("amq.direct","directRouting",m);
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    @RequestMapping("/sendtofanout")
    public void sendToFanout(){
        try{
            String message = "This is a second message to Fanout!";
            Map<String,Object> m = commonMap(message);
            rabbitTemplate.convertAndSend("amq.fanout",null,m);
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    @RequestMapping("/sendtotopic1")
    public void sendToTopicMan(){
        try{
            String message = "This is a first message to topic : Thing!";
            Map<String,Object> m = commonMap(message);
            rabbitTemplate.convertAndSend("amq.topic","topic.thing",m);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @RequestMapping("/sendtotopic2")
    public void sendToTopicWoman(){
        try{
            String message = "This is a first message to topic : other!";
            Map<String,Object> m = commonMap(message);
            rabbitTemplate.convertAndSend("amq.topic","topic.asdasd",m);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public Map<String,Object> commonMap(String message){
        String uuid = String.valueOf(UUID.randomUUID());
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date());
        Map<String,Object> m = new HashMap<>();
        m.put("messageId", uuid);
        m.put("createTime",date);
        m.put("message",message);
        return m;
    }
}

生产者和消费者都开发完成了,将两个项目都启动后,调用接口,往MQ中发送数据。

1.调用发送到直连交换机的接口

消费者端实时消费数据:

java 复制代码
DirectListener接受到的消息为:{createTime=2024-02-25 13:55, messageId=cf0b8188-6fb4-43cb-86f2-ad0286889ceb, message=This is a first message!}

2.调用发送到扇形交换机的接口

java 复制代码
FirstFanoutListener接受到的消息为:{createTime=2024-02-25 13:56, messageId=afe4206e-46e8-4e9c-9a99-d38d130b54e1, message=This is a second message to Fanout!}
SecondFanoutListener接受到的消息为:{createTime=2024-02-25 13:56, messageId=afe4206e-46e8-4e9c-9a99-d38d130b54e1, message=This is a second message to Fanout!}

两个监听器都实时消费了数据。

3.调用发送到主题交换机的两个接口

调用发送消息载体中携带topic.thing的接口:

java 复制代码
SecondTopicListener:{createTime=2024-02-25 14:02, messageId=7f412bbd-4032-476b-a578-cd7a0aa04e1d, message=This is a first message to topic : Man!}
FirstTopicListener接受到的消息为:{createTime=2024-02-25 14:02, messageId=7f412bbd-4032-476b-a578-cd7a0aa04e1d, message=This is a first message to topic : Man!}

明明topic.thing路由绑定的是队列firstTopicQueue,但是secondTopicQueue却也收到了,为什么呢?这是因为队列secondTopicQueue的路由键配置的是topic.*,那么句点后无论是什么都会和匹配成功。

若调用发送消息载体中携带topic.other的接口:

java 复制代码
SecondTopicListener:{createTime=2024-02-25 14:05, messageId=ad408aef-d678-4d06-be5c-7982403b3b48, message=This is a first message to topic : Woman!}

那么此刻只有队列2是匹配的。

以上就是简单的生产者与消费者的代码。

接下来是回调函数相关
SpringBoot整合RabbitMQ 回调函数 direct交换机、fanout交换机、topic交换机

相关推荐
一 乐7 小时前
校务管理|基于springboot + vueOA校务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
云和数据.ChenGuang7 小时前
Deepseek适配场景:OpenEuler系统下RabbitMQ安装与基础配置教程
分布式·rabbitmq·ruby
摇滚侠7 小时前
面试实战 问题三十四 对称加密 和 非对称加密 spring 拦截器 spring 过滤器
java·spring·面试
xqqxqxxq7 小时前
Java 集合框架之线性表(List)实现技术笔记
java·笔记·python
L0CK7 小时前
RESTful风格解析
java
程序员小假7 小时前
我们来说说 ThreadLocal 的原理,使用场景及内存泄漏问题
java·后端
何中应7 小时前
LinkedHashMap使用
java·后端·缓存
tryxr7 小时前
Java 多线程标志位的使用
java·开发语言·volatile·内存可见性·标志位
talenteddriver8 小时前
java: Java8以后hashmap扩容后根据高位确定元素新位置
java·算法·哈希算法