Bootstrap

RabbitMQ配置、使用、Spring整合、SpringBoot整合

1、RabbitMQ中的一些概念

1、JMS 是 JavaEE 13大规范中的一种,规定了java客户端与消息队列通信的一套API接口,是一个 Java 平台中关于面向消息中间件的API。类比jdbc,jdbc是java程序与数据库通信的一套API接口。

为什么要指定规范?有了规范,大家都遵循规范去实现,虽然底层实现不同,但使用起来都统一,就好像什么牌子的安卓手机都用type-c接口。

RabbitMQ官方是没有遵循JMS接口规范实现,没写提供JMS的实现包,但是在开源社区有人写了JMS的实现类。

在这里插入图片描述

AMQP 和 JMS不是一个概念上的东西,一个是协议,另一个是API接口规范。

2、安装RabbitMQ

安装RabbitMQ,无脑根据帖子安装

CentOS7安装RabbitMQ简单实用教程_centos7 安装rabbitmq_huang_sj502的博客-CSDN博客

启动命令

systemctl start rabbitmq-server

我们查看一下MQ的状态

systemctl status rabbitmq-server

启动后浏览器输入:192.168.240.130:15672,访问页面进行登录(记得关闭服务器防火墙)

登录后的界面

在这里插入图片描述

在这里插入图片描述

amqp是我们通过tcp连接访问的端口,换句话说就是我们写的java程序通过这个端口连接到RabbitMQ所在的Server上(RabbitMQ占用了服务器的5672端口用于连接服务)

clustering 25672是集群端口

RabbitMQ Management 15672是我们这个管控台页面服务的端口

3、RabbitMQ Java Client

Mq的基础框架图

在这里插入图片描述

使用rabbitmq java client也是遵循上面的框架图一步步连接到RabbitMQ Server中队列的。

简单工作模式

1.简单工作模式,生产者发送消息到队列

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();
// 5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1.queue:队列名称
2.durable:是否持久化,当mq重启后,还存在
3.exclusion:
    * 是否独占
    * 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.argument:删除队列的一些参数
 */
// 如果没有一个名字叫hello_world的队列,则会创建队列
channel.queueDeclare("hello_world", true, false, false, null);
// 6.发送消息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
    1.exchange:交换机的名称。简单模式下不使用默认的交换机则设置为""(简单模式下并不是没有交换机)
    2.routingKey:路由名称。如果使用的是默认的交换机,那路由名称需要和队列名称一样,才能路由到对应的队列中去
    3.props:配置信息
    4.body:发送的消息数据
 */

String body = "hello rabbitmq~~~";
channel.basicPublish("", "hello_world", null, body.getBytes());

// 7.释放资源,不关闭程序不会结束,connection也会一直存在
channel.close();
connection.close();

执行代码后,会得到一个hello_world队列
在这里插入图片描述

在视图管理界面可以看到hello_world队列,并且有一条消息未被消费。还可以点进去查看。

在这里插入图片描述

未关闭的connection

在这里插入图片描述

2.简单工作模式,消费者监听消息队列消费消息

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();
// 5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1.queue:队列名称
2.durable:是否持久化,当mq重启后,还存在
3.exclusion:
    * 是否独占
    * 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.argument:删除队列的一些参数
 */
// 如果没有一个名字叫hello_world的队列,则会创建队列(producer中已经创建了,所以这里也可以不用写)
channel.queueDeclare("hello_world", true, false, false, null);

/*
basicConsume(String queue, boolean autoAck, Consumer callback)
参数:
    1.queue:队列名称
    2.autoAck:是否自动确认,ack机制
    3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                注意不是java.util.function中的Consumer接口,是rabbitmq API中的
 */
// 接收消息
Consumer consumer = new DefaultConsumer(channel){
    /*
        回调方法,当收到消息后会自动执行该方法
        1.consumerTag:消息标识
        2.envelope:获取一些信息,如交换机、路由key...
        3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
        4.body:获取到的数据
     */
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("consumerTag = " + consumerTag);
        System.out.println("Exchange = " + envelope.getExchange());
        System.out.println("RoutingKey = " + envelope.getRoutingKey());
        System.out.println("properties = " + properties);
        System.out.println("body = " + new String(body));
    }
};
channel.basicConsume("hello_world", true, consumer);

// 关闭资源?消费者是一个监听程序,所以不要去关闭资源

在这里插入图片描述

不同的工作模式本质区别,其实就是消息路由的方式和策略不同

工作队列模式

在这里插入图片描述

1.工作模式,生产者发送消息到队列(代码基本和简单模式一样)

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();
// 5.创建队列
/*
queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
参数:
1.queue:队列名称
2.durable:是否持久化,当mq重启后,还存在
3.exclusion:
    * 是否独占
    * 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.argument:删除队列的一些参数
 */
// 如果没有一个名字叫hello_world的队列,则会创建队列
channel.queueDeclare("work_queues", true, false, false, null);
// 6.发送消息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
    1.exchange:交换机的名称。简单模式下不使用默认的交换机则设置为""(简单模式下并不是没有交换机)
    2.routingKey:路由名称。如果使用的是默认的交换机,那路由名称需要和队列名称一样,才能路由到对应的队列中去
    3.props:配置信息
    4.body:发送的消息数据
 */
for (int i = 1; i <= 10; i++) {
    String body = i + "hello rabbitmq~~~2";
    channel.basicPublish("", "work_queues", null, body.getBytes());
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}


// 7.释放资源,不关闭程序不会结束,connection也会一直存在
channel.close();
connection.close();

2.工作队列模式,消费者监听消息队列消费消息(同样的代码,再创建一个Consumer_WorkQueues2)

package com.heima.consumer;

import com.rabbitmq.client.*;

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

public class Consumer_WorkQueues1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置参数
        factory.setHost("192.168.240.130"); //ip 默认是localhost
        factory.setPort(5672); // 端口,默认值也是5672
        factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
        factory.setUsername("heima"); // 用户名,默认 guest
        factory.setPassword("heima"); // 密码,默认值 guest
        // 3.创建连接 Connection
        Connection connection = factory.newConnection();
        // 4.创建Channel
        Channel channel = connection.createChannel();
        // 5.创建队列
        /*
        queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
        参数:
        1.queue:队列名称
        2.durable:是否持久化,当mq重启后,还存在
        3.exclusion:
            * 是否独占
            * 当Connection关闭时,是否删除队列
        4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
        5.argument:删除队列的一些参数
         */
        // 如果没有一个名字叫hello_world的队列,则会创建队列(producer中已经创建了,所以这里也可以不用写)
        channel.queueDeclare("work_queues", true, false, false, null);

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1.queue:队列名称
            2.autoAck:是否自动确认,ack机制
            3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                        注意不是java.util.function中的Consumer接口,是rabbitmq API中的
         */
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel){
            /*
                回调方法,当收到消息后会自动执行该方法
                1.consumerTag:消息标识
                2.envelope:获取一些信息,如交换机、路由key...
                3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
                4.body:获取到的数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*System.out.println("consumerTag = " + consumerTag);
                System.out.println("Exchange = " + envelope.getExchange());
                System.out.println("RoutingKey = " + envelope.getRoutingKey());
                System.out.println("properties = " + properties);*/
                System.out.println("body = " + new String(body));
            }
        };
        channel.basicConsume("work_queues", true, consumer);

        // 关闭资源?消费者是一个监听程序,所以不要去关闭资源
    }
}

执行结果:
Consumer_WorkQueues1
在这里插入图片描述

Consumer_WorkQueues2
在这里插入图片描述

可以看到两个消费者之间对于同一个消息是竞争关系,通过负载均衡方式轮流消费消息。

在这里插入图片描述

Pub/Sub订阅模式

生产者把消息发给交换机,交换机再把消息路由分发给不同队列。消费者监听队列接收消息

在这里插入图片描述

(交换机和计网里面的交换机功能类似,都是分发,这里是消息,计网是报文)

交换机常见三种模式:广播、定向、通配符。
这里先介绍广播模式

测试案例说明:生产者生产日志消息,两个消费者接收消息,消费者1将消息打印到控制台,消费者2将消息保存到数据库。

有人可能会问消费者都接收消息,那还要交换机和两个队列干嘛,用一个队列的工作队列模式不就行了?
其实不然,别忘了**“两个消费者之间对于同一个消息是竞争关系”**,这句话是适用在同一个队列上的,如果仅有一个消息队列,消费者1和2就只能有一个抢到并消费消息,所以这里需要交换机将消息“复制”成两份,发送到两个消息队列上,且每个消费者监听一个消息队列,就如上图所示。

1.订阅模式,生产者发送消息到交换机,交换机分发消息到队列

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();

// 5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
    1.exchange:交换机名称
    2.type:交换机类型。BuiltinExchangeType是枚举类型
        DIRECT("direct"):定向
        FANOUT("fanout"):扇形(广播),发送消息到每一个与此交换机绑定的队列
        TOPIC("topic"):通配符的方式
        HEADERS("headers"):参数匹配
    3.durable:是否持久化
    4.autoDelete:自动删除
    5.internal:内部使用。一般false
    6.arguments:参数
 */
String exchangeName = "test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);

// 6.创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);

// 7.绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
    1.queue:队列名称
    2.exchange:交换机名称
    3.routingKey:路由键,绑定规则
        特别的,如果交换机的类型为fanout广播,则routingKey设置为""就行,交换机会把消息分发到所有与之绑定的队列上(这种情况routingKey指定了也没有用)
 */
channel.queueBind(queue1Name, exchangeName, "");
channel.queueBind(queue2Name, exchangeName, "");

// 8.发送消息
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
channel.basicPublish(exchangeName, "", null, body.getBytes());

// 9.释放资源
channel.close();
connection.close();

在这里插入图片描述

amq开头的是系统默认自带的交换机,test_fanout是我们刚刚创建的交换机
在这里插入图片描述

点击test_fanout进入详情页可查看与之绑定的队列

在这里插入图片描述

两个队列都收到了消息

2.订阅模式,消费者从队列中获取消息

public class Consumer_PubSub1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置参数
        factory.setHost("192.168.240.130"); //ip 默认是localhost
        factory.setPort(5672); // 端口,默认值也是5672
        factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
        factory.setUsername("heima"); // 用户名,默认 guest
        factory.setPassword("heima"); // 密码,默认值 guest
        // 3.创建连接 Connection
        Connection connection = factory.newConnection();
        // 4.创建Channel
        Channel channel = connection.createChannel();

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1.queue:队列名称
            2.autoAck:是否自动确认,ack机制
            3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                        注意不是java.util.function中的Consumer接口,是rabbitmq API中的
         */
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel){
            /*
                回调方法,当收到消息后会自动执行该方法
                1.consumerTag:消息标识
                2.envelope:获取一些信息,如交换机、路由key...
                3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
                4.body:获取到的数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*System.out.println("consumerTag = " + consumerTag);
                System.out.println("Exchange = " + envelope.getExchange());
                System.out.println("RoutingKey = " + envelope.getRoutingKey());
                System.out.println("properties = " + properties);*/
                System.out.println("body = " + new String(body));
                System.out.println("将日志信息打印到控制台.....");
            }
        };
        channel.basicConsume("test_fanout_queue1", true, consumer);

        // 关闭资源?消费者是一个监听程序,所以不要去关闭资源
    }
}

---------------------------------------------------------------------------------------

public class Consumer_PubSub2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置参数
        factory.setHost("192.168.240.130"); //ip 默认是localhost
        factory.setPort(5672); // 端口,默认值也是5672
        factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
        factory.setUsername("heima"); // 用户名,默认 guest
        factory.setPassword("heima"); // 密码,默认值 guest
        // 3.创建连接 Connection
        Connection connection = factory.newConnection();
        // 4.创建Channel
        Channel channel = connection.createChannel();

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1.queue:队列名称
            2.autoAck:是否自动确认,ack机制
            3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                        注意不是java.util.function中的Consumer接口,是rabbitmq API中的
         */
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel){
            /*
                回调方法,当收到消息后会自动执行该方法
                1.consumerTag:消息标识
                2.envelope:获取一些信息,如交换机、路由key...
                3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
                4.body:获取到的数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*System.out.println("consumerTag = " + consumerTag);
                System.out.println("Exchange = " + envelope.getExchange());
                System.out.println("RoutingKey = " + envelope.getRoutingKey());
                System.out.println("properties = " + properties);*/
                System.out.println("body = " + new String(body));
                System.out.println("将日志信息保存到数据库.....");
            }
        };
        channel.basicConsume("test_fanout_queue2", true, consumer);

        // 关闭资源?消费者是一个监听程序,所以不要去关闭资源
    }
}

在这里插入图片描述

在这里插入图片描述

Routing路由模式

在这里插入图片描述

1.Routing路由模式,生产者发送消息到交换机,并指定RoutingKey,交换机分发消息到routingKey匹配的队列

这里测试时,发送消息时指定 RoutingKey=info

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();

// 5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
    1.exchange:交换机名称
    2.type:交换机类型。BuiltinExchangeType是枚举类型
        DIRECT("direct"):定向
        FANOUT("fanout"):扇形(广播),发送消息到每一个与此交换机绑定的队列
        TOPIC("topic"):通配符的方式
        HEADERS("headers"):参数匹配
    3.durable:是否持久化
    4.autoDelete:自动删除
    5.internal:内部使用。一般false
    6.arguments:参数
 */
String exchangeName = "test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);

// 6.创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);

// 7.绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
    1.queue:队列名称
    2.exchange:交换机名称
    3.routingKey:路由键,绑定规则
        如果交换机的类型为fanout广播,则routingKey设置为""就行,交换机会把消息分发到所有与之绑定的队列上
 */
// 队列1绑定 error
channel.queueBind(queue1Name, exchangeName, "error");
// 队列2绑定 info,error, warning
channel.queueBind(queue2Name, exchangeName, "info");
channel.queueBind(queue2Name, exchangeName, "error");
channel.queueBind(queue2Name, exchangeName, "warning");

// 8.发送消息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
    1.exchange:交换机的名称。简单模式下不使用默认的交换机则设置为""(简单模式下并不是没有交换机)
    2.routingKey:路由名称。如果使用的是默认的交换机,那路由名称需要和队列名称一样,才能路由到对应的队列中去
    3.props:配置信息
    4.body:发送的消息数据
 */
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
channel.basicPublish(exchangeName, "info", null, body.getBytes());

// 9.释放资源
channel.close();
connection.close();

运行结果:

在这里插入图片描述

只有test_direct_queue2队列收到了消息

在这里插入图片描述

只有消费者2从队列2中获取到了消息

在这里插入图片描述

在这里插入图片描述

测试发送消息的RoutingKey为error时的结果:

这次消费者1,2都收到了消息

在这里插入图片描述

在这里插入图片描述

Topics通配符模式(主题模式)

在这里插入图片描述

通配符举例:

在这里插入图片描述

​ usa.#表示能接受以usa开头的所有消息

​ *注意:通配符 匹配一个单词,#匹配多个单词
​ user.haha.hehe 可以由user.#匹配到,user.*匹配不到,因为user后面有两个单词

1.Topics通配符模式,创建的交换机先使用包含通配符的RoutingKey和不同队列绑定。接着生产者指定RoutingKey发送消息到交换机,交换机便根据匹配规则将消息分发到正确的队列上

注意交换机类型一定要改为TOPIC

// 1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 2.设置参数
factory.setHost("192.168.240.130"); //ip 默认是localhost
factory.setPort(5672); // 端口,默认值也是5672
factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
factory.setUsername("heima"); // 用户名,默认 guest
factory.setPassword("heima"); // 密码,默认值 guest
// 3.创建连接 Connection
Connection connection = factory.newConnection();
// 4.创建Channel
Channel channel = connection.createChannel();

// 5.创建交换机
/*
exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
参数:
    1.exchange:交换机名称
    2.type:交换机类型。BuiltinExchangeType是枚举类型
        DIRECT("direct"):定向
        FANOUT("fanout"):扇形(广播),发送消息到每一个与此交换机绑定的队列
        TOPIC("topic"):通配符的方式
        HEADERS("headers"):参数匹配
    3.durable:是否持久化
    4.autoDelete:自动删除
    5.internal:内部使用。一般false
    6.arguments:参数
 */
String exchangeName = "test_topic";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC, true, false, false, null);

// 6.创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name, true, false, false, null);
channel.queueDeclare(queue2Name, true, false, false, null);

// 7.绑定队列和交换机
/*
queueBind(String queue, String exchange, String routingKey)
参数:
    1.queue:队列名称
    2.exchange:交换机名称
    3.routingKey:路由键,绑定规则
        如果交换机的类型为fanout广播,则routingKey设置为""就行,交换机会把消息分发到所有与之绑定的队列上
 */

// routing key 我们这里分成两部分:系统的名称.日志的级别
// 需求:所有error级别的日志存入数据库,所有order系统的日志存入数据库
// 通配符#代表0个或多个单词,*代表1个单词。这里routingKey只有两部分,所以用#或*匹配都行
channel.queueBind(queue1Name, exchangeName, "#.error");
channel.queueBind(queue1Name, exchangeName, "order.*");
channel.queueBind(queue2Name, exchangeName, "*.*");

// 8.发送消息
/*
basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
参数:
    1.exchange:交换机的名称。简单模式下不使用默认的交换机则设置为""(简单模式下并不是没有交换机)
    2.routingKey:路由名称。如果使用的是默认的交换机,那路由名称需要和队列名称一样,才能路由到对应的队列中去
    3.props:配置信息
    4.body:发送的消息数据
 */
String body = "日志信息:张三调用了findAll方法...日志级别:info...";
channel.basicPublish(exchangeName, "order.info", null, body.getBytes());

// 9.释放资源
channel.close();
connection.close();

在这里插入图片描述

2.消费者

public class Consumer_Topic1 {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置参数
        factory.setHost("192.168.240.130"); //ip 默认是localhost
        factory.setPort(5672); // 端口,默认值也是5672
        factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
        factory.setUsername("heima"); // 用户名,默认 guest
        factory.setPassword("heima"); // 密码,默认值 guest
        // 3.创建连接 Connection
        Connection connection = factory.newConnection();
        // 4.创建Channel
        Channel channel = connection.createChannel();

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1.queue:队列名称
            2.autoAck:是否自动确认,ack机制
            3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                        注意不是java.util.function中的Consumer接口,是rabbitmq API中的
         */
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel){
            /*
                回调方法,当收到消息后会自动执行该方法
                1.consumerTag:消息标识
                2.envelope:获取一些信息,如交换机、路由key...
                3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
                4.body:获取到的数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*System.out.println("consumerTag = " + consumerTag);
                System.out.println("Exchange = " + envelope.getExchange());
                System.out.println("RoutingKey = " + envelope.getRoutingKey());
                System.out.println("properties = " + properties);*/
                System.out.println("body = " + new String(body));
                System.out.println("将日志信息存储到数据库.....");
            }
        };
        channel.basicConsume("test_topic_queue1", true, consumer);

        // 关闭资源?消费者是一个监听程序,所以不要去关闭资源
    }
}

----------------------------------------------------------------------

public class Consumer_Topic2 {
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置参数
        factory.setHost("192.168.240.130"); //ip 默认是localhost
        factory.setPort(5672); // 端口,默认值也是5672
        factory.setVirtualHost("/itcast"); // 虚拟机,不是指Linux虚拟机,是指消息队列里虚拟机(独立的空间),默认是 / 虚拟机
        factory.setUsername("heima"); // 用户名,默认 guest
        factory.setPassword("heima"); // 密码,默认值 guest
        // 3.创建连接 Connection
        Connection connection = factory.newConnection();
        // 4.创建Channel
        Channel channel = connection.createChannel();

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
            1.queue:队列名称
            2.autoAck:是否自动确认,ack机制
            3.callback:回调对象。是一个消费型接口,监听,接收到消息自动执行接口方法。
                        注意不是java.util.function中的Consumer接口,是rabbitmq API中的
         */
        // 接收消息
        Consumer consumer = new DefaultConsumer(channel){
            /*
                回调方法,当收到消息后会自动执行该方法
                1.consumerTag:消息标识
                2.envelope:获取一些信息,如交换机、路由key...
                3.properties:配置信息。是生产者发送消息时设置的props参数,生产者没有设置那属性全为null
                4.body:获取到的数据
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                /*System.out.println("consumerTag = " + consumerTag);
                System.out.println("Exchange = " + envelope.getExchange());
                System.out.println("RoutingKey = " + envelope.getRoutingKey());
                System.out.println("properties = " + properties);*/
                System.out.println("body = " + new String(body));
                System.out.println("将日志信息打印到控制台.....");
            }
        };
        channel.basicConsume("test_topic_queue2", true, consumer);

        // 关闭资源?消费者是一个监听程序,所以不要去关闭资源
    }
}

结果:

在这里插入图片描述

在这里插入图片描述

将发送的消息RoutingKey改为goods.info。结果就是只有消费2从队列2中获取到消息

在这里插入图片描述

4、Spring整合RabbitMQ

在这里插入图片描述

1.1 生产者spring配置文件:
在spring启动时会将配置文件中定义的连接、交换机、队列都创建加载

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    <!--定义管理交换机、队列-->
    <rabbit:admin connection-factory="connectionFactory"/>

    <!--定义持久化队列,不存在则自动创建;不绑定到交换机则绑定到默认交换机
    默认交换机类型为direct,名字为:"",路由键为队列的名称
    -->
    <!--
     参数:
        id:bean的名称
        name:队列的名称
        auto-declare:自动创建
        auto-delete:自动删除。最后一个消费者和该队列断开连接后,自动删除队列
        exclusive:是否独占连接。同一时间只能有一个消费者与该队列连接
        durable:是否持久化
    -->
    <rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~广播;所有队列都能收到消息~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_1" name="spring_fanout_queue_1" auto-declare="true"/>

    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_fanout_queue_2" name="spring_fanout_queue_2" auto-declare="true"/>

    <!--定义广播类型交换机;并绑定上述两个队列-->
    <rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
        <!--交换机和队列进行绑定,广播类型的交换机不需要RoutingKey,只需要一个队列名就行-->
        <rabbit:bindings>
            <rabbit:binding queue="spring_fanout_queue_1"/>
            <rabbit:binding queue="spring_fanout_queue_2"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>
    
    
    <!-- ~~~~~~~~~~~~~~路由(定向);消息交哥RoutingKey完全匹配的队列 ~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_direct_queue_star" name="spring_direct_queue_star" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_direct_queue_well" name="spring_direct_queue_well" auto-declare="true"/>

    <rabbit:direct-exchange id="spring_direct_exchange" name="spring_direct_exchange" auto-declare="true">
        <!--交换机和队列进行绑定,定向类型的交换机需要指定一个路由Key-->
        <rabbit:bindings>
            <rabbit:binding key="info" queue="spring_direct_queue_star"/>
            <rabbit:binding key="info" queue="spring_direct_queue_well"/>
            <rabbit:binding key="error" queue="spring_direct_queue_well"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- ~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~ -->
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_star" name="spring_topic_queue_star" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well" name="spring_topic_queue_well" auto-declare="true"/>
    <!--定义广播交换机中的持久化队列,不存在则自动创建-->
    <rabbit:queue id="spring_topic_queue_well2" name="spring_topic_queue_well2" auto-declare="true"/>

    <rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding pattern="heima.*" queue="spring_topic_queue_star"/>
            <rabbit:binding pattern="heima.#" queue="spring_topic_queue_well"/>
            <rabbit:binding pattern="itcast.#" queue="spring_topic_queue_well2"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
    <rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
</beans>

1.2 代码中注入RabbitTemplate对象,使用其发送消息

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {

    // 1.注入 RabbitTemple
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testHelloWorld(){
        // 2.发送消息
        rabbitTemplate.convertAndSend("spring_queue", "hello world spring..."); //消息也不用自己序列化了
    }

    /**
     * 发送fanout消息
     */
    @Test
    public void testFanout(){
        // 2.发送消息
        rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "spring fanout....");
    }

    /**
     * 发送direct消息
     */
    @Test
    public void testRouting(){
        rabbitTemplate.convertAndSend("spring_direct_exchange", "info", "spring direct....");
    }

    /**
     * 发送Topic消息
     */
    @Test
    public void testTopic(){
        // 2.发送消息
        rabbitTemplate.convertAndSend("spring_topic_exchange", "heima.hehe.haha", "spring Topic....");
    }
}

2.1 消费者Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">
    <!--加载配置文件-->
    <context:property-placeholder location="classpath:rabbitmq.properties"/>

    <!-- 定义rabbitmq connectionFactory -->
    <rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
                               port="${rabbitmq.port}"
                               username="${rabbitmq.username}"
                               password="${rabbitmq.password}"
                               virtual-host="${rabbitmq.virtual-host}"/>
    
	<!-- 定义监听器bean -->
    <bean id="springQueueListener" class="com.heima.rabbitmq.listener.SpringQueueListener"/>
    <bean id="fanoutListener1" class="com.heima.rabbitmq.listener.FanoutListener1"/>
    <bean id="fanoutListener2" class="com.heima.rabbitmq.listener.FanoutListener2"/>
    <bean id="topicListenerStar" class="com.heima.rabbitmq.listener.TopicListenerStar"/>
    <bean id="topicListenerWell" class="com.heima.rabbitmq.listener.TopicListenerWell"/>
    <bean id="topicListenerWell2" class="com.heima.rabbitmq.listener.TopicListenerWell2"/>

    <!-- 设置监听器bean监听的队列 -->
    <rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
        <rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
        <rabbit:listener ref="fanoutListener1" queue-names="spring_fanout_queue_1"/>
        <rabbit:listener ref="fanoutListener2" queue-names="spring_fanout_queue_2"/>
        <rabbit:listener ref="topicListenerStar" queue-names="spring_topic_queue_star"/>
        <rabbit:listener ref="topicListenerWell" queue-names="spring_topic_queue_well"/>
        <rabbit:listener ref="topicListenerWell2" queue-names="spring_topic_queue_well2"/>
    </rabbit:listener-container>
</beans>

2.2 Spring整合后,消费者可通过监听器来监听队列的消息,监听器类需实现MessageListener接口中的onMessage方法。在消费者富所在程序启动后配置文件中声明创建的监听器对象就会一直监听对应的队列,当队列有消息了,就会从中获取。

​ 每个监听类的写法基本一样,监听获取到消息,执行onMessage消耗掉消息

// 监听器类
public class FanoutListener1 implements MessageListener {

    @Override
    public void onMessage(Message message) {
        System.out.println(new String(message.getBody()));
    }
}
// 简单用死循环模拟Web程序启动
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-consumer.xml")
public class ConsumerTest {

    @Test
    public void test1(){
        boolean flag = true;
        while (flag) {

        }
    }
}

在这里插入图片描述

小结:

Spring整合RabbitMQ后,代码量大量减少,生产者通过RabbitTemplate对象完成消息的发送。
缺点:配置文件一大堆的配置

5、SpringBoot整合RabbitMQ

在这里插入图片描述

在这里插入图片描述

组件选择:

在这里插入图片描述

配置文件:

#配置RabbitMQ的基本信息  ip 端口 username password
spring:
  rabbitmq:
    host: 192.168.240.130  #默认localhost
    port: 5672
    username: heima  #默认guest
    password: heima  #默认guest
    virtual-host: /itcast

1.生产者:

配置类创建交换机、队列、绑定关系 bean

@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "boot_topic_exchange";
    public static final String QUEUE_NAME = "boot_topic_queue";

    // 1.交换机
    @Bean("bootExchange")
    public Exchange bootExchange(){
        // topic通配符类型交换机  链式风格创建  durable()持久化
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    // 2.Queue队列
    @Bean("bootQueue")
    public Queue bootQueue(){
        // durable(QUEUE_NAME) 持久化+命名
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    // 3.队列和交换机绑定关系 Binging
    /*
        1.知道哪个队列
        2.知道哪个交换机
        3.routing key
     */
    @Bean // binging不需要注入,所以起不起名字无所谓(其实上面方法名同名也可以省略)
    public Binding bingQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange){
        // bind(queue).to(exchange)将队列和交换机绑定   with("boot.#")指定routingKey   noargs()无参 / and(Map<String, Object> map)有参
        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
    }
}

测试类发送消息:

/**
 * 关于 @RunWith(SpringRunner.class):
 * 如果在Spring项目中的Test测试类要使用注入的类,比如@Autowired注入的类或者spring管理的bean的时候,
 * 测试类在运行前,需要spring容器运行起来,加上这个@RunWith(SpringRunner.class)注解,就是先运行起来spring容器,再开始运行测试类
 *
 * 这里不加上@RunWith(SpringRunner.class)运行test会报空指针异常,表示rabbitTemplate没有注入
 * 解决方法 1.加上@RunWith(SpringRunner.class)  2.该用JUnit5
 *
 * SpringBoot2.4.0之后,spring-boot-starter-test默认仅支持JUnit5,去掉了兼容JUnit4引擎:org.junit.vintage:junit-vintage-engine,
 * 无需添加@RunWith(SpringRunner.class)
 */
@SpringBootTest
//@RunWith(SpringRunner.class)
public class ProducerTest {

    // 1.注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend(){
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "boot.hah", "boot mq hello~~~");
    }

}

1.消费者:

监听器。这里使用了@RabbitListener(queues = “boot_queue”)注解,指定ListenerQueue方法是监听队列名为"boot_queue"的队列。

前面Spring中是实现MessageListener接口,以接口中的onMessage方法作为监听回调方法,且需要在xml配置文件中配置监听器与队列的判断关系。比对一下,直接使用@RabbitListener(queues = “boot_queue”)快捷方便了很多。

@Component
public class RabbitMQListener {

    @RabbitListener(queues = "boot_topic_queue")
    public void ListenerQueue(Message message){
        System.out.println(message);
        System.out.println(new String(message.getBody()));
    }
}
/**
 *@RunWith(SpringRunner.class)注解,就是先运行起来spring容器,再开始运行测试类
 *
 * 这里不加上@RunWith(SpringRunner.class)运行test会报空指针异常,表示rabbitTemplate没有注入
 * 解决方法 1.加上@RunWith(SpringRunner.class)  2.该用JUnit5
 *
 * SpringBoot2.4.0之后,spring-boot-starter-test默认仅支持JUnit5,去掉了兼容JUnit4引擎:org.junit.vintage:junit-vintage-engine,
 * 无需添加@RunWith(SpringRunner.class)
 */
@SpringBootTest
//@RunWith(SpringRunner.class)
public class ProducerTest {

    // 1.注入RabbitTemplate
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSend(){
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "boot.hah", "boot mq hello~~~");
    }

}

1.消费者:

监听器。这里使用了@RabbitListener(queues = “boot_queue”)注解,指定ListenerQueue方法是监听队列名为"boot_queue"的队列。

前面Spring中是实现MessageListener接口,以接口中的onMessage方法作为监听回调方法,且需要在xml配置文件中配置监听器与队列的判断关系。比对一下,直接使用@RabbitListener(queues = “boot_queue”)快捷方便了很多。

@Component
public class RabbitMQListener {

    @RabbitListener(queues = "boot_topic_queue")
    public void ListenerQueue(Message message){
        System.out.println(message);
        System.out.println(new String(message.getBody()));
    }
}

在这里插入图片描述

;