开发工具:IDEA 2020.1
操作系统:Centos7
RabbitMQ版本:Docker中rabbitmq:management
Docker版本:20.10.9
mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.2:generate -DarchetypeArtifactId="maven-archetype-webapp" -DarchetypeGroupId="org.apache.maven.archetypes" -DarchetypeVersion="1.4"
该命令创建的是传统maven工程,需要手动改造成SpringBoot工程
删除WebApp目录及下属内容,改造为下列文件结构
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.deangroupId>
<artifactId>SpringBoot_RabbitMQartifactId>
<version>1.0-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.5.RELEASEversion>
parent>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<finalName>SpringBoot_RabbitMQfinalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-pluginartifactId>
<version>3.1.0version>
plugin>
<plugin>
<artifactId>maven-resources-pluginartifactId>
<version>3.0.2version>
plugin>
<plugin>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.0version>
plugin>
<plugin>
<artifactId>maven-surefire-pluginartifactId>
<version>2.22.1version>
plugin>
<plugin>
<artifactId>maven-war-pluginartifactId>
<version>3.2.2version>
plugin>
<plugin>
<artifactId>maven-install-pluginartifactId>
<version>2.5.2version>
plugin>
<plugin>
<artifactId>maven-deploy-pluginartifactId>
<version>2.8.2version>
plugin>
plugins>
pluginManagement>
build>
project>
主要是
创建一个SpringBoot的测试用例
上面的操作中,我们已经在pom文件中添加了SpringBoot Test的依赖了
现在我们需要src目录下创建一个test目录,在test目录下写一个RabbitTest类进行测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitApplication.class)
public class RabbitTest {
@Resource
private RabbitTemplate rabbitTemplate;
//声明一个队列
@Test
public void createQueue() {
//创建RabbitAdmin 用于操作队列
RabbitAdmin admin = new RabbitAdmin(rabbitTemplate);
Queue springQueue = new Queue("SpringQueue");
admin.declareQueue(springQueue);
}
}
@Test
public void sendMsg() {
rabbitTemplate.convertAndSend("SpringQueue","Hello Spring Rabbit");
}
}
@Test
public void consumeMsg() {
rabbitAdmin = new RabbitAdmin(rabbitTemplate);
//队列的属性 Properties继承HashTable
Properties properties = rabbitAdmin.getQueueProperties("SpringQueue");
//由于不知道properties里面都有什么,所以输出看一下
/**
* 里面的内容如下:
* QUEUE_MESSAGE_COUNT
* QUEUE_CONSUMER_COUNT
* QUEUE_NAME
*/
Enumeration<?> names = properties.propertyNames();
while (names.hasMoreElements()) {
Object e = names.nextElement();
System.out.println(e);
}
//获取消息总数
String msg = properties.get("QUEUE_MESSAGE_COUNT").toString();
System.out.println(msg );
//清空队列 (也就是说本次是一次性消费)
rabbitAdmin.purgeQueue("SpringQueue");
}
@Test
public void routeBind() {
rabbitAdmin = new RabbitAdmin(rabbitTemplate);
//声明队列
//系统消息接收队列,userId在实际生产中为实际用户id
Queue sysQueue = new Queue("sys.msg.userId");
//一对一消息接收队列,sendUserId为实际向用户发送消息的用户id
Queue userQueue=new Queue("user.msg.sendUserId");
//声明交换机 Direct
DirectExchange directEx = new DirectExchange("notice.direct");
//声明绑定
Binding sysBind = BindingBuilder.bind(sysQueue).to(directEx).with("sys");
Binding userBind = BindingBuilder.bind(userQueue).to(directEx).with("user");
//创建队列
rabbitAdmin.declareQueue(sysQueue);
rabbitAdmin.declareQueue(userQueue);
//创建交换机
rabbitAdmin.declareExchange(directEx);
//创建绑定关系
rabbitAdmin.declareBinding(sysBind);
rabbitAdmin.declareBinding(userBind);
}
@Test
public void sendMsg2Route() {
rabbitTemplate.convertAndSend("notice.direct","sys", "系统通知");
rabbitTemplate.convertAndSend("notice.direct","sys", "系统通知");
rabbitTemplate.convertAndSend("notice.direct","user", "用户通知");
}
@Component
public class SysNoticeListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println(Thread.currentThread().getName()+": 监听到消息");
Thread.sleep(1000);
}
}
重点在于实现了ChannelAwareMessageListener类,重写了其方法OnMessage
@Configuration
public class RabbitConfig {
//注入自定义的监听器
@Resource
private SysNoticeListener sysNoticeListener;
@Bean
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
//创建监听器工厂
SimpleRabbitListenerContainerFactory factory=new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//ack机制 自动消费 或 手动消费
//自动消费:无论消费是否成功都进行确认
//手动消费:由程序员来判断消费是否成功
//设置为无ACK机制
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
//设定消费者的线程数
factory.setMaxConcurrentConsumers(5); //设置最大其实也不生效
factory.setConcurrentConsumers(3); //3个消费者
//使用工厂生成监听器容器
SimpleMessageListenerContainer container = factory.createListenerContainer();
//绑定监听队列 监听器监听哪个队列
container.setQueueNames("sys.msg.userId");
//绑定监听器 用哪个监听器来监听
container.setMessageListener(sysNoticeListener);
return container;
}
}
我们虚拟一个场景,用RabbitMQ实现一下
故事是这样的,当用户上线时,获得系统未读消息数量,上线期间,可以实时获取消息,下线后不再实时获取
也就是说,当用户上线时,返回当前队列中消息总数,上线期间,实时监听消息
@Configuration
public class RabbitConfig {
//注入自定义的监听器
@Resource
private SysNoticeListener sysNoticeListener;
@Resource
private RabbitTemplate rabbitTemplate;
@Bean("sysListenerContainer")
public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
//创建监听器工厂
SimpleRabbitListenerContainerFactory factory=new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
//ack机制 自动消费 或 手动消费
//自动消费:无论消费是否成功都进行确认
//手动消费:由程序员来判断消费是否成功
//设置为无ACK机制
factory.setAcknowledgeMode(AcknowledgeMode.NONE);
//设定消费者的线程数
factory.setMaxConcurrentConsumers(5); //设置最大其实也不生效
factory.setConcurrentConsumers(3); //3个消费者
//使用工厂生 成监听器容器
SimpleMessageListenerContainer container = factory.createListenerContainer();
//绑定监听队列 监听器监听哪个队列
//container.setQueueNames("sys.msg.userId");
//绑定监听器 用哪个监听器来监听
container.setMessageListener(sysNoticeListener);
return container;
}
/**
* 当服务器启动时,为每个用户生成一个队列用于接收系统消息
* 队列名称规则:[sys.msg.userId] sys.msg.1 sys.msg.2
*/
@Bean("rabbitAdmin")
public RabbitAdmin rabbitAdmin() {
RabbitAdmin admin = new RabbitAdmin(rabbitTemplate);
List<Queue> queues = new ArrayList<>();
//创建队列
IntStream.range(0,2).forEach(i->{
queues.add(new Queue("sys.msg."+i));
});
//创建交换机
//因是系统通知,所以创建Fanout交换机
FanoutExchange exchange = new FanoutExchange("sys.notice.fanout");
//交换机和队列绑定
List<Binding> bindings = new ArrayList<>();
for (Queue q : queues) {
bindings.add(BindingBuilder.bind(q).to(exchange));
}
admin.declareExchange(exchange);
IntStream.range(0,2).forEach(i->{
admin.declareQueue(queues.get(i));
admin.declareBinding(bindings.get(i));
});
return admin;
}
}
rabbitAdmin方法中可以根据实际情况,增大循环数,为每一个用户创建一个接收系统通知的队列
@RestController
@RequestMapping("user")
public class ConsumeController {
//注入容器
@Resource(name = "sysListenerContainer")
private SimpleMessageListenerContainer container;
//注入监听器
@Resource
private SysNoticeListener listener;
//注入RabbitAdmin
@Resource(name = "rabbitAdmin")
private RabbitAdmin rabbitAdmin;
/**
* 登录时首先获得未读消息数量 然后进行实时消费
* @param userId
* @return
*/
@GetMapping("login/{userId}")
public String login(@PathVariable String userId) {
/*
登录成功后 首先获取离线消息 即消息的数量
*/
//根据UserId拼接自己的队列名称
String queueName = "sys.msg." + userId;
Properties queueProperties = rabbitAdmin.getQueueProperties(queueName);
//通过属性值获取消息总量
int count = Integer.parseInt(queueProperties.get("QUEUE_MESSAGE_COUNT").toString());
System.out.println("您未上线的时间里,有" + count + "条消息在等您");
//清空队列 一定要清空
rabbitAdmin.purgeQueue(queueName);
/*
上线后,消息是实时消费的
*/
//监听容器绑定监听队列
container.setQueueNames(queueName);
return "success";
}
/**
* 退出登录时 移除监听队列
*
* @return
*/
@GetMapping("/logout/{userId}")
public String logout(@PathVariable("userId") String userId) {
String queueName = "sys.msg." + userId;
container.removeQueueNames(queueName);
return "logout success";
}
}
也就是在用户成功登录后才进行将监听容器和队列相绑定,在用户退出登录后,从监听容器中移除该队列
@Component
public class SysNoticeListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
/*
模拟业务中对实时消息的处理
*/
//根据队列名称获取用户id
MessageProperties messageProperties = message.getMessageProperties();
//获取队列名称
String consumerQueue = messageProperties.getConsumerQueue();
//获取消息体
byte[] body = message.getBody();
System.out.println(consumerQueue + ":收到实时消息【" + new String(body) + "]");
}
}
监听器实际上处理的是实时消息的处理