RabbitMQ(三):RabbitMQ快速入门(SpringBoot)

一、MQ的基本结构:

在这里插入图片描述
RabbitMQ中的一些角色:

  • publisher:生产者(消息发给交换机——交换机负责路由)
  • consumer:消费者(从队列中获取消息、处理消息)
  • exchange个:交换机,负责消息路由
  • queue:队列,存储消息
  • 【 virtualHost:虚拟主机】隔离不同租户的exchange、queue等资源的逻辑分组【消息的隔离】

二、RabbitMQ消息模型

RabbitMQ官方提供了5个不同的Demo示例,对应了不同的消息模型:
在这里插入图片描述

三、快速入门(基本消息队列)(了解即可)

基本队列模式的模型图:(RabbitMQ实现)
在这里插入图片描述
基本队列模式只包括三个角色:

  • publisher:消息发布者,将消息发送到队列queue
  • queue:消息队列,负责接受并缓存消息
  • consumer:订阅队列,处理队列中的消息

1)、创建SpringBoot项目,并创建两个子模块

包括三部分:

  • mq-demo:父工程,管理项目依赖
  • publisher:消息的发送者
  • consumer:消息的消费者
    在这里插入图片描述
    附:如果不会创建,可以查看下面文章:IDEA创建maven多级模块项目

父工程的pom.xml文件中

<!--父项目:所有的依赖交给SpringBoot管理-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/>
    </parent>
	<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <build>
        <plugins>
        	<!--打包插件-->
            <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skip>true</skip>
            </configuration>
            </plugin>
        </plugins>
    </build>

2)、publisher实现

  • 建立连接
  • 创建Channel
  • 声明队列
  • 发送消息
  • 关闭连接和channel
    在这里插入图片描述
    代码如下:

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class PublisherTest {
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("10.65.184.58");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("itcast");
        factory.setPassword("123321");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.发送消息
        String message = "hello, rabbitmq!";
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println("发送消息成功:【" + message + "】");

        // 5.关闭通道和连接
        channel.close();
        connection.close();
    }
}

3)consumer实现

前三步和消费者一样

  • 建立connection
  • 创建channel
  • 利用channel声明队列
  • 【订阅消息】:
    1、消费者定义consumer的消费行为handleDelivery()——这是一个回调函数
    2、利用channel将消费者与队列绑定
    hannel.basicConsume(queueName, true, new DefaultConsumer(channel){…}
    在这里插入图片描述

代码如下:

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerTest {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("10.65.184.58");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("itcast");
        factory.setPassword("123321");
        // 1.2.建立连接
        Connection connection = factory.newConnection();
        // 2.创建通道Channel
        Channel channel = connection.createChannel();
        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);
        // 4.订阅消息(消费/处理了一条消息,队列消息-1)
        //4.1channel.basicConsume(queueName, true, new DefaultConsumer(channel)方法:是绑定队列queueName和消费者
        //每当队列中有1条消息,会自动执行回调函数new DefaultConsumer(channel){....}里面的handleDelivery
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 4.2、处理消息(模拟处理,这里只是打印了一下)
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
        
    }
}

附:回调函数是什么?
回调函数:函数的其中一个参数也是一个函数
教程』回调函数是个啥?

下图:
hannel.basicConsume(…)方法
参数: new DefaultConsumer(channel){…}——匿名函数
在这里插入图片描述

4)、测试结果如下:

在这里插入图片描述

说明1:生产者和消费者的启动顺序是不确定先后的,所有都需要写创建队列,但是如果通道中已经有了队列simple.queue,那么就不会创建新的队列,而是使用已经存在的队列

说明2:利用channel将消费者与队列绑定,
步骤4.1中,channel.basicConsume(queueName, true, new DefaultConsumer(channel)方法:是绑定队列queueName和消费者;此时并不会执行回调函数里面的方法,
而是监听队列,当队列中有消息,会自动执行回调函数new DefaultConsumer(channel){…}里面的handleDelivery方法(异步处理)

四、SpringAMQP

从上面的代码可以看出,publisher和consumer的实现有很多重复部分,现在只是一个发布者和消费者,以后每次都得写这种冗余的代码?我们应该如何优化他们呢?
这里就要引入SpringAMQP

什么是SpringAMQP?
Spring AMQP 是 Spring 对 AMQP 协议的封装和扩展,提供了消息发送和接收的模板。Spring AMQP项目将核心Spring概念应用于基于 AMQP 的消息传递解决方案的开发,以便更容易和简单的管理AMQO资源。

什么是AMQP?高级消息队列协议
Advanced Message Queuing ProtocoL, 是【用于在应用程序之间传递业务消息的开放标准】,
【该协议与语言和平台无关,更符合微服务中独立性的要求】

总结:
AMQP:是高级消息队列协议——消息队列的规范
SpringAMQP是Spring对AMQP的一种实现(且提供了一种模板/API规范,来发送和接收消息)

SpringAMQP提供了三个功能:

  • RabbitAdmin用于【自动声明队列】、交换机及其绑定关系
    (手动创建队列就像上面的入门案例一样)
  • 基于注解的监听器(容器)模式,用于异步接收消息
  • 封装了RabbitTemplate工具,一般用于发送消息 (也可以接收,但是接收一般是用监听器容器去做)

如果SpringAMQP如何完成这些功能感兴趣,可以查看下面文章
Spring AMQP简介和使用

这三个帮助我们优化上面代码

1、使用SpringAMQP优化(基本消息队列)

同上,基本队列模式的模型图:
在这里插入图片描述

步骤1:在父工程mq-demo中引入依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

步骤2:声明队列

优化1:【运行ConsumerApplication时】自动声明队列、交换机及其绑定关系(注解方式)
位置:(一般写在消费者模块的)在配置类中,声明队列(也可以声明交换机、以及队列和交换机的绑定关系)

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

@Configuration
public class CommonConfig {
    @Bean
    public Queue simpleQueue(){
        return new Queue("simple.queue1");
    }
}

备注:这里需要运行ConsumerApplication才会自动创建该队列

步骤3:publish发送消息

位置:publish模块

3.1application.yml配置

首先配置MQ地址,在publisher服务的application.yml中添加配置:
【application.yml该段配置目的:Spring容器自动创建消息队列的连接】

优化1:配置MQ地址:将冗余的连接参数抽取到yml文件中

spring:
  rabbitmq:
    host: 10.65.184.58 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机(用户消息隔离)
    username: itcast # 用户名
    password: 123321 # 密码

Spring容器自动创建消息队列的连接

3.2 消息发送逻辑

1、注入RabbitTemplate
2、(向指定的队列)发送消息
备注:此时该队列还没有声明

RabbitTemplate:
SpringAMQP封装了RabbitTemplate工具,一般用于发送消息 (也可以接收,但是接收一般是用监听器容器去做)

package cn.itcast.mq.listener;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) //加上这个是为了获得spring环境的上下文支持,不加可能会报错
@SpringBootTest
public class SpringAmqpTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        // 队列名称
        String queueName = "simple.queue1";
        // 消息
        String message = "hello, spring amqp!";
        // (向指定的队列)发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }
}

步骤4:消息接收

4.1 application.yml配置

首先配置MQ地址,在consumer服务的application.yml中添加配置:
【application.yml该段配置目的:Spring容器自动创建消息队列的连接】

spring:
  rabbitmq:
    host: 10.65.184.58 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
4.2、消息接收处理逻辑(监听队列)

说明: Spring中自动建立了消息队列的连接、因此我们只需要在Spring定义消息处理的方法,这里,SpringAMQP提供了基于注解的监听器(容器)模式,用于异步接收消息

代码位置:consumer模块下的listener包中
目的:监听指定队列,处理队列中的消息
1、告知Spring容器该方法——@Component
2、该方法需要添加注解:@RabbitListener(queues = “simple.queue1”)表明监听的队列名称
3、该方法定义了处理消息的行为

package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component//1、告知Spring容器该方法
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue1")//2、表明监听的队列名称
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
    	//3、打印消息,模拟处理消息
        System.out.println("spring 消费者接收到消息:【" + msg + "】");
    }
    

备注:因为这里使用了Spring容器来接收消息、所以需要打开Application开启Spring,才能接收消息
在这里插入图片描述
步骤5:开始测试
发送消息
在这里插入图片描述
在这里插入图片描述

2、工作消息队列模型WorkQueue

背景:当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。当队列满了,消息无法进入队列,【消息会被丢弃】
此时就可以使用work 模型,多个消费者共同处理消息处理,速度就能大大提高了。

Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息在这里插入图片描述
工作消息队列模型和基本消息队列基本一致,区别是消费者consumer模块的linstener中,有多个监听方法同时监听同一个队列

多个消费者监听同一队列

在这里插入图片描述
代码如下,
模拟2个消费者

@RabbitListener(queues = "simple.queue1")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(20);//每接收一次消息就休眠20毫秒、模拟每秒处理50条消息
    }

    @RabbitListener(queues = "simple.queue1")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());//【err红色打印】
        Thread.sleep(200);//每接收一次消息就休眠200毫秒、模拟每秒处理5条消息
    }

模拟发布者:

向simple.queue1队列中不停发送消息,模拟消息堆积

/**
     * workQueue
     * 向simple.queue队列中不停发送消息,模拟消息堆积。
     */
    @Test
    public void testWorkQueue() throws InterruptedException {
        // 队列名称
        String queueName = "simple.queue1";
        // 消息
        String message = "hello, message_";
        for (int i = 0; i < 50; i++) {
            // 发送消息
            rabbitTemplate.convertAndSend(queueName, message + i);
            System.out.println("发送成功" + i);
            Thread.sleep(20);
        }
    }

测试

启动ConsumerApplication后,在执行publisher服务中刚刚编写的发送测试方法testWorkQueue。
结果如下:

结果表明:可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。(消费者1不会去帮忙处理消费者2的消息)

这是因为消息预取机制:消息是先平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然是有问题的,那么,应该如何解决呢?

消费预取限制

我们可以通过修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限,这就是消费预取限制。
每次消费者从消息队列中最多预取1条消息。

spring:
  rabbitmq:
    host: 10.65.184.58 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机(用户消息隔离)
    username: itcast # 用户名
    password: 123321 # 密码
    listener:
      simple:
        prefetch: 1  # 每次消费者从消息队列中最多预取1条消息。

再次测试
在这里插入图片描述

发布/订阅

背景/需求:【一条消息需要通知多个消费者】但是上面的简单队列和工作队列都无法满足该需求(消息被处理后就消失了——阅后即焚)

发布订阅的模型如图:

1、发布者将消息给交换机【发布者不用跟队列打交道了】
2、交换机将消息给多个队列(这里就完成了同一条消息给多个消费者了)(根据交换机类型的不同,发送给不同的队列)

Fanout Exchange:广播:发送消息给所有绑定了该交换机的队列
Direct Exchange:路由:根据绑定关系中的routingKey发送给指定队列
Topic Exchange:主题:根据绑定关系中的routingKey发送给指定队列(支持通配符)

3、队列(消费者)处理消息
在这里插入图片描述

各个角色介绍
在订阅模型中,多了一个exchange角色,而且过程略有变化:

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。【Exchange有以下3种类型:】
    Fanout:广播,将消息交给所有绑定到交换机的队列
    Direct:定向,把消息交给符合指定routing key 的队列
    Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  • Consumer:消费者,与以前一样,订阅队列,没有变化
  • Queue:消息队列也与以前一样,接收消息、缓存消息。

备注:Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

【发布/订阅操作步骤】:

1、绑定交换机和消息队列(及其路由规则)当消息发送到交换机后,交换机.会自动根据路由规则routingkey将消息发送到队列中(实现同一条消息可同时发送到多个队列)

2、消息发布者 发送消息到交换机的方法(1发送什么消息、2发送到那台交换机、3路由规则routingkey)

3、消费者通过Spring容器监听指定队列中的消息(@RabbitListener(queues = “simple.queue”)+方法),一旦指定的队列中有消息,执行消费者消费消息的方法(处理消息)

备注:步骤1可以单独定义一个配置类+@Bean来完成队列和交换机的绑定,然后(步骤3)消费者通过Spring容器监听指定队列并处理队列里面的消息。
也可以使用@RabbitListener注解将步骤1和3合并完成,后续会进行说明

3、Fanout广播

【广播模式下(即交换机类型声明为Fanout),与广播交换机绑定的队列都可以接收到消息。】在这里插入图片描述
从上图可知,发布/订阅模型和基本/工作消息队列的区别是【新增了exchange交换机以及 需要绑定交换机和队列】

【步骤1】:声明队列、交换机、关系

2个队列、1个交换机(声明为fanout)、2个绑定关系(队列和交互机的绑定关系)

package cn.itcast.mq.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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutConfig {


    /**
     * 声明交换机
     * @return Fanout类型交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("itcast.fanout");
    }

    /**
     * 第1个队列
     */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    /**
     * 绑定队列1和交换机
     */
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    /**
     * 第2个队列
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定队列2和交换机
     */
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

步骤2:消息发送

在publisher服务的SpringAmqpTest类中添加测试方法:

备注:广播模式下,交换机的routingkey不管写什么都会发送到与之绑定的每一个队列下,因此发送消息时也可以不写routingkey

@Test
public void testFanoutExchange() {
    // 名称
    String exchangeName = "itcast.fanout";
    // 消息
    String message = "hello, everyone!";
    //往交换机发送消息,参数分别是:交互机名称、RoutingKey(暂时为空)、消息
    rabbitTemplate.convertAndSend(exchangeName, "", message);
    //rabbitTemplate.convertAndSend(exchangeName, message);
   //备注:广播模式下,交换机的routingkey不管写什么都会发送到与之绑定的每一个队列下,因此发送消息时也可以不写routingkey
}

步骤3:消息接收

在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:

@RabbitListener(queues = "fanout.queue1")//表明监听的队列名称
    public void listenFanoutQueue1(String msg) throws InterruptedException {
        System.out.println("spring 消费者1接收到fanout.queue1消息:【" + msg + "】");
    }
    @RabbitListener(queues = "fanout.queue2")//表明监听的队列名称
    public void listenFanoutQueue2(String msg) throws InterruptedException {
        System.out.println("spring 消费者2接收到fanout.queue2消息:【" + msg + "】");
    }

测试结果:
在这里插入图片描述

使用@RabbitListener注解完成步骤1和步骤3(声明队列、交换机、绑定关系+消息处理)

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue1"),
            exchange = @Exchange(name = "itcast.fanout", type = ExchangeTypes.FANOUT))
    )
    public void listenFanoutQueue1(String msg){
        System.out.println("spring 消费者1接收到fanout.queue1消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue2"),
            exchange = @Exchange(name = "itcast.fanout", type = ExchangeTypes.FANOUT))
    )
    public void listenFanoutQueue2(String msg) throws InterruptedException {
        System.out.println("spring 消费者2接收到fanout.queue2消息:【" + msg + "】");
    }

对比一下之前的步骤1+步骤3简单了许多
在这里插入图片描述

4、Direct路由

需求:在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。

在Direct模型下:
在这里插入图片描述
该图可完成需求:key为red时,两个消费者都可以收到,blue/yellw则只有指定的消费者可以收到。

与广播模式的区别:

  • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
  • 消息的发送方在向Exchange发送消息时,也必须指定消息的 RoutingKey。
  • Exchange不再把消息交给每一个绑定的队列,而是交换机根据消息的 Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似(即广播)

实现步骤:

1、监听+绑定关系

在这里插入图片描述

 @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }

    //绑定队列direct.queue2和交换机itcast.direct、类型为DIRECT
    //当发送到交换机的消息的key为red和yellw时,采取下面的消费者处理方法
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
    }
2、发送消息(携带routingkey)

发送消息需要携带routingkey

@Test
    public void testSendDirectExchange() {
        // 交换机名称
        String exchangeName = "itcast.direct";
        // 消息
        String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "red", message);
    }

5、Topic话题

Topic类型Exchange和Direct类型的类似,区别是可以让队列在绑定Routing key 的时候使用通配符;
使用通配符的好处,我们可以从下图看出在这里插入图片描述
假如使用Direct与queue1绑定的key为:china.news、china.whether;(2个)
其他同理,总共需要8个,这还是只有两个类型/两个国家的情况下,假如我们有10多个类型,每次都要写10多个routingkey就会很麻烦;
使用Topic类型的交换机,我们可以指定queue1和交换机绑定的key为china.#;

通配符规则:
#:匹配一个或多个词
*:匹配1个词
举例:
item.#:能够匹配item.spu.insert 或者 item.spu
item.*:只能匹配item.spu

操作步骤:

1、绑定关系+监听

(这里只写第一个队列和第四个队列)
(修改key和交换机类型)

@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
            key = "china.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue4"),
            exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
            key = "#.news"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
    }
发送消息
/**
     * topicExchange
     */
    @Test
    public void testSendTopicExchange() {
        // 交换机名称
        String exchangeName = "itcast.topic";
        // 消息
        String message = "喜报!孙悟空大战哥斯拉,胜!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
    }

消息转换器

查看我们测试时rabbitTemplate.convertAndSend()的消息,方法中的消息的类型是Object,所以可以是任意类型,那么也可以是对象。在这里插入图片描述

但是,Spring会把你【发送的消息】【序列化为字节】发送给MQ,
【接收消息】的时候,还会把【字节反序列化为Java对象】。
默认情况下Spring采用的序列化方式是JDK序列化。JDK序列化存在下列问题:

  • 有安全漏洞(注入问题)
  • 数据体积过大,导致 可读性差且数据传输性能差

下面开始测试默认转换器(JDK序列化)

1、在spring中声明队列(位置:consumer模块-config)

这里为了保证消息不被消费
(便于在控制台的队列查看消息被序列化的格式长什么样)
(Spring容器监听方式会立刻消费掉消息)
所以在config中声明队列(队列存储消息,没有队列就没有存储消息来查看)

@Bean
public Queue objectQueue(){
    return new Queue("object.queue");
}

声明完队列记得启动consumerApplication(Spring帮你声明队列)

2、这里直接发送到队列中(简单队列模型)

我们修改消息发送的代码,发送一个Map对象:

/**
     * 测试消息为对象类型,
     */
    @Test
    public void testSendMap() throws InterruptedException {
        // 准备消息
        Map<String,Object> msg = new HashMap<>();
        msg.put("name", "Jack");
        msg.put("age", 21);
        // 发送消息
        rabbitTemplate.convertAndSend("object.queue", msg);
    }

发送消息后查看RabbitMQ控制台:

没有按照期望的显示出一个对象,而是消息被JDK序列化为字节在这里插入图片描述

优化:配置JSON转换器(publisher中)

发送消息时,我们不想要JDK序列化方式。显然,发送消息时,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。在这里插入图片描述
1、在publisher和consumer两个服务中都引入依赖:(也可以直接引入父工程–去掉版本)

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

2、配置消息转换器
在【publisher启动类】中添加一个Bean即可:

//MessageConverter 导入的是amqp包下的
//Jackson2JsonMessageConverter这个类里面又依赖了jackson,所以要引入jackson的依赖

@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

再次发送数据,结果显示如下,完成JSON方式来做序列化和反序列化。
在这里插入图片描述


消息队列专栏文章:

RabbitMQ(一)初识消息队列(MQ)

RabbitMQ(二):RabbitMQ的安装(Linux、基于docker安装)及其插件安装

RabbitMQ(三):RabbitMQ快速入门(SpringBoot)

RabbitMQ(四):RabbitMQ高级特性

文章整理自:黑马教学视频

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值