开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
4.RabbitMQ 的第一个程序
4.0 AMQP协议的回顾
4.1 RabbitMQ支持的消息模型
4.2 引入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
application.yml 配置文件:
spring:
application:
name: rabbitmq-springboot
rabbitmq:
host: 192.168.204.129 # rabbitmq主机
port: 5672 # 端口
stream:
username: ems # rabbitmq用户名
password: 123 # rabbitmq密码
virtual-host: /ems # 虚拟主机
我们需要提前在 rabbitmq 中创建虚拟主机,所谓虚拟主机就类似于数据库中的库,用来为我们的项目做一一的映射,日后不同的项目建立不同的虚拟主机。例:我们使用rabbitmq的web管理界面创建了一个 /ems 虚拟主机,然后创建一个用户(例如:ems) ,然后我们要让创建的用户绑定虚拟主机,使当前用户可以访问指定的虚拟主机。
4.3 第一种模型(直连)
在上图的模型中,有以下概念:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
1. 开发生产者
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("10.15.0.9");
connectionFactory.setPort(5672);
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
connectionFactory.setVirtualHost("/ems");
Connection connection = connectionFactory.newConnection();
//创建通道
Channel channel = connection.createChannel();
//参数1: 是否持久化 参数2:是否独占队列 参数3:是否自动删除 参数4:其他属性
channel.queueDeclare("hello",true,false,false,null);
channel.basicPublish("","hello", null,"hello rabbitmq".getBytes());
channel.close();
connection.close();
测试类:
public class Provider {
// 生产消息
@Test
public void testSendMessage() throws IOException, TimeoutException {
/* // 创建连接mq的连接工厂对象
ConnectionFactory connectionFactory = new ConnectionFactory();
// 设置连接rabbitmq主机
connectionFactory.setHost("192.168.182.128");
// 设置端口号
connectionFactory.setPort(5672);
// 设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/ems");
// 设置访问虚拟主机的用户名和密码(和虚拟主机绑定的用户)
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
// 通过上面的操作就和rabbitmq建立了连接
// 获取连接对象
Connection connection = connectionFactory.newConnection();*/
// 通过工具类获取连接对象
Connection connection = RabbitMQUtils.getConnection();
// 获取连接中的通道对象 (真正传递消息是建立连接之后连接通过通道去传递消息,所以我们需要通过连接获取连接中的通道)
Channel channel = connection.createChannel(); //
// 通道绑定对应消息队列 (通道和队列之间一定要有一个声明或者一个绑定,通道才知道把消息发送给哪个消息队列)
// 参数1:队列名称 如果队列不存在自动创建
// 参数2:用来定义队列的特性是否要持久化,true 持久化队列(存到磁盘中) false 不持久化 (仅仅是队列持久化,不保证队列中消息持久化)
// 参数3:exclusive 是否独占队列 true 独占队列 false 不独占(这个队列是否只有当前连接可用,其他连接不可以再用)
// 参数4:autoDelete 是否在消费完成后自动删除队列 true 自动删除 false 不自动删除
// 参数5:额外附加参数
channel.queueDeclare("hello", true, false, false, null);
/*
并不是某个通道绑定哪个队列才只能往这个队列发送消息,通道不绑定某个队列也可以向这个队列发送消息
*/
// 通过通道发布消息
// 参数1:交换机名称(因为我们这次写的是点对点,直接到队列,没有交换机,写 "" )
// 参数2:队列名称
// 参数3:传递消息额外设置,我们可以传入一个属性(MessageProperties.PERSISTENT_TEXT_PLAIN),让我们队列中的消息进行持久化
// 参数4:消息的具体内容
channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello word".getBytes());
/*// 释放资源
channel.close();
connection.close();*/
// 调用工具类关闭
RabbitMQUtils.closeConnectionAndChanel(channel, connection);
}
}
2. 开发消费者
消费者得和生产者绑定同一个虚拟主机,紧接着在生产者发的队列中拿到消息,所以消费者也要找到对应的虚拟主机、对应的队列才能去消费。
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("10.15.0.9");
connectionFactory.setPort(5672);
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
connectionFactory.setVirtualHost("/ems");
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello", true, false, false, null);
channel.basicConsume("hello",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
测试
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.182.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/ems");
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
// 创建连接对象
Connection connection = connectionFactory.newConnection();
// 创建通道
Channel channel = connection.createChannel();
// 通道绑定队列
channel.queueDeclare("hello", false, false, false, null); // 消费端一定要和生产端对应上
// 消费消息
// 参数1:消费哪一个队列的消息
// 参数2:是否开启消息自动确认机制
// 参数3:消费消息时的回调接口,把当前的通道传进去
channel.basicConsume("hello", true, new DefaultConsumer(channel){
// 这里第三个参数使用了匿名内部类实现
@Override // 参数1:标签 参数2:消息传递的信封 参数3:传的一些属性 参数4 body :消息队列中取出的消息
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("new String(body) = " + new String(body));
}
});
// 释放资源
// (如果不释放的话会一直监听我们的队列,建议不释放,因为我们要一直做处理,如果释放可能还没来得及执行回调方法,main线程已经关闭了)
// channel.close();
// connection.close();
}
}
我们可以发现,上面生产者和消费者有代码冗余,所以我们可以封装工具类RabbitMQUtils,并将生产者和消费者相应冗余代码替换掉。
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RabbitMQUtils {
private static ConnectionFactory connectionFactory;
static {
// 创建连接工厂,连接工厂属于重量级资源 (静态代码块中的资源是在类加载的时候执行,只执行一次)
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.182.128");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/ems");
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
}
// 定义提供连接对象的方法
public static Connection getConnection(){
try {
// 返回连接对象
return connectionFactory.newConnection();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 关闭通道和关闭连接工具方法
public static void closeConnectionAndChanel(Channel channel, Connection conn){
try {
if(channel != null) channel.close();
if(conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 参数的说明
channel.queueDeclare("hello",true,false,false,null);
'参数1':用来声明通道对应的队列
'参数2':用来指定是否持久化队列
'参数3':用来指定是否独占队列
'参数4':用来指定是否自动删除队列
'参数5':对队列的额外配置