上一章讲到了rabbitmq在普通java项目中的消费端与生产端
这一章讲解一下 在springboot中的注解方式
默认已经安装完成rabbitmq
上一章得出的使用rabbitmq顺序如下:
1:定义exchange
2定义queue
3将queue与exchange绑定起来
那么在注解中我们从简到难也按照这样的顺序进行学习:
首先讲解使用rabbitadmin接入
首先在maven中引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
新建一个config类 用于配置rabbitmq相关选项
@Configuration
//本类地址
@ComponentScan({"com.rabbitmqstu.integration.config"})
public class RabbitmqConfig {
/**
* 获取连接工厂
* @return
*/
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
//rabbitmq服务的地址
connectionFactory.setHost("192.168.106.70");
connectionFactory.setPort(5672);
// connectionFactory.setPassword("guest");
// connectionFactory.setUsername("guset");
connectionFactory.setVirtualHost("/");
return connectionFactory;
}
/**
*
* @param connectionFactory
* @return
*/
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
}
在spring容器中注入了rabbitadmin后 接下来开始rabbitadmin的使用
我这里使用junit的测试类进行定义
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = TestDeclare.class)
@ComponentScan
public class TestDeclare {
@Resource
private RabbitAdmin rabbitAdmin;
/**
* 定义exchange 定义queue 定义绑定
*
*/
@Test
public void test1() {
/** 1声明交换机 */
rabbitAdmin.declareExchange(new DirectExchange("springboottest.direct", true
, true));
rabbitAdmin.declareExchange(new TopicExchange("springboottest.topic", true
, true));
rabbitAdmin.declareExchange(new FanoutExchange("springboottest.fanout", true
, true));
/** 1声明队列 */
rabbitAdmin.declareQueue(new Queue("test.direct.queue", false));
rabbitAdmin.declareQueue(new Queue("test.topic.queue", false));
rabbitAdmin.declareQueue(new Queue("test.fanout.queue", false));
/** 1声明绑定 */
rabbitAdmin.declareBinding(new Binding("test.direct.queue",
Binding.DestinationType.QUEUE, "springboottest.direct",
"direct", null));
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.topic.queue", false))//创建队列
.to(new TopicExchange("springboottest.topic", false, false))//创建路由
.with("user.#"));//创建routingkey
rabbitAdmin.declareBinding(BindingBuilder.bind(new Queue("test.fanout.queue", false))//创建队列
.to(new FanoutExchange("springboottest.fanout", false, false))//创建路由
);
}
}
执行这个方法后 查看rabbitmq网页可以看到已经定义成功了
当然也可以使用另一种方式定义 下面展示方式2:
/**
* 声明交换机、队列、绑定的方式
*/
@Configuration
public class MyRabbitConfig {
/**
* 声明交换机001
* @return
*/
@Bean
public TopicExchange exchange001(){
return new TopicExchange("exchange-topic-001",true,true);
}
/**
* 声明队列001
* @return
*/
@Bean
public Queue queue001(){
return new Queue("queue-topic-001",true);
}
/**
* 声明绑定001
* 如果是rabbit.study.* 就路由到队列001
* @return
*/
@Bean
public Binding binding001(){
return BindingBuilder.bind(queue001()).to(exchange001()).with("annotation.#");
}
/**
* 声明交换机002
* @return
*/
@Bean
public DirectExchange exchange002(){
return new DirectExchange("exchange-topic-002",true,true);
}
/**
* 声明队列002
* @return
*/
@Bean
public Queue queue002(){
return new Queue("queue-topic-002",true);
}
/**
* 声明绑定001
* 如果是rabbit.study.* 就路由到队列001
* @return
*/
@Bean
public Binding binding002(){
return BindingBuilder.bind(queue002()).to(exchange002()).with("annotation.002");
}
//todo:
// 这里是消息接收
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueues(queue001(),queue002());
//当前消费者数量
container.setConcurrentConsumers(1);
//最大消费者数量
container.setMaxConcurrentConsumers(5);
//重回队列
container.setDefaultRequeueRejected(false);
//签收模式
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//
//container.setConsumerTagStrategy(queue -> queue+"_"+ UUID.randomUUID().toString());
//默认
// container.setMessageListener(new ChannelAwareMessageListener() {
// @Override
// public void onMessage(Message message, Channel channel) throws Exception {
// String body = new String(message.getBody());
// System.err.println("消费者接收信息:"+body);
// }
// });
//使用自定义消息接收方式
MyMessageAdapter myMessageAdapter = new MyMessageAdapter();
//消息适配器
container.setMessageListener(myMessageAdapter);
return container;
}
}
上图中可以看到使用了spring的注解@bean 这样在项目启动时 如果类上有@configuration注解 就会将这些bean放入spring容器之中 一样可以完成exchange queue的定义与绑定
而最后一个bean其实就是消息的监听 从代码中可以看到这个监听了两个队列 分别是上面定义的两个queue、其中有一个参数是重回队列也就是当消息消费失败 打回到队列的末尾 这样以后还可以继续消费、这经常会出现问题、因为你一次消费失败 可能下一次还是失败 建议对失败数据进行持久化
这里有一个关键的信息就是这一行:
//签收模式
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
这个是什么意思呢?比如你收快递当你收到了快递(消息)
你可以选择自动签收
AcknowledgeMode.AUTO
手动签收:
AcknowledgeMode.MANUAL
自动签收就是当消息接收到之后 自动准备接收下一条消息、而手动签收就必须自己手动执行签收之后才能继续签收
这也可以做到防止服务器过载
上面的代码中最后我定义了一个我自定义的消息接收类:
/**
* 自定义消息接收类
*
*
*/
public class MyMessageAdapter implements ChannelAwareMessageListener {
/**
* 消息接收时执行的函数
* @param message
* @param channel
* @throws Exception
*/
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//接收到的消息
String body = new String(message.getBody());
MessageProperties messageProperties = message.getMessageProperties();
Map<String, Object> headers = messageProperties.getHeaders();
//如果header中有数据 就循环输出
if (headers != null) {
headers.entrySet().stream().forEach((e) -> System.out.println("key:" + e.getKey() + "----value:" + e.getValue()));
}
System.err.println("消费者接收信息:" + body);
//---------------如果是手工ack 就需要下面的代码
//---------------如果是手工ack 就需要下面的代码
//---------------如果是手工ack 就需要下面的代码
//消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
// channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认成功收到消息
//ack返回false,并重新回到队列,api里面解释得很清楚
// channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
//拒绝消息
// channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
}
@Override
public void onMessage(Message message) {
String body = new String(message.getBody());
MessageProperties messageProperties = message.getMessageProperties();
Map<String, Object> headers = messageProperties.getHeaders();
if (headers != null) {
Set<Map.Entry<String, Object>> entries = headers.entrySet();
Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> next = iterator.next();
System.out.println("key:" + next.getKey() + "----value:" + next.getValue());
}
}
System.err.println("消费者接收信息:" + body);
}
}
上面可以看到第一个方法有channel 第二个方法没有
这个channel是用于手动签收的、当签收模式为手动签收时如果你没有进行签收、则不会继续接收消息!!!
消费者和定义就讲完了 接下来讲消息的发送:
这里一样使用junit的测试类
package com.rabbitmqstu;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import javax.annotation.Resource;
import java.util.Map;
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = TestSend.class)
@ComponentScan
public class TestSend {
@Resource
private RabbitAdmin rabbitAdmin;
@Resource
private RabbitTemplate rabbitTemplate;
/**
* 发送消息
* <p>
* 添加额外设置
*/
@Test
public void testSend0() {
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("desc", "信息描述");
messageProperties.getHeaders().put("type", "定义消息类型");
//定义发送的消息
Message message = new Message("发送消息".getBytes(), messageProperties);
//设置成功或失败返回 用于可靠性投递
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
rabbitTemplate.convertAndSend("springboottest.topic", "user.testtopic", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
System.out.println("测试额外添加设置");
message.getMessageProperties().getHeaders().put("test", "测试额外添加消息");
return message;
}
});
}
/**
* 发送消息
* <p>
* 添加额外设置
*/
@Test
public void testSend1() {
rabbitTemplate.convertAndSend("springboottest.topic", "user.testtopic", "test send message", new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().getHeaders().put("desc", "信息描述");
message.getMessageProperties().getHeaders().put("type", "定义消息类型");
return message;
}
});
}
/**
* 发送消息
*/
@Test
public void testSend2() {
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("desc", "信息描述");
messageProperties.getHeaders().put("type", "定义消息类型");
//定义发送的消息
Message message = new Message("测试发送消息:我老婆是古力娜扎".getBytes(), messageProperties);
rabbitTemplate.send("exchange-topic-001", "annotation.test", message);
}
final RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
/**
* 当没有路由成功进入这里
* 比如我发送了消息给rabbitmq服务器 但是没有队列能够收到这条消息 就会返回回来
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("***********return callback************");
System.out.println("message:" + message.getBody().toString() + " replyCode:" + replyCode + " replyText:" + replyText + " exchange:" + exchange + " routingKey:" + routingKey);
System.out.println("***********return callback************");
}
};
final RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
/**
* 当ack成功或失败进入这里
*
* @param correlationData 唯一业务参数
* @param ack 是否ack成功
* @param cause 原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("***********confirm callback************");
System.out.println("correlationData:" + correlationData);
//如果消费端选择 noack 不签收
if (!ack) {
System.err.println("ack失败!ack:" + ack + "异常处理");
}
System.out.println("***********confirm callback************");
}
};
}
我上面定义了多种消息发送 其实也就是是否多传参数
但是得注意两个 我定义了两个对象 一个是confirmCallBack 一个是returnCallBack
注释中已经进行了解释 这里主要是为了说明:
当消息发送时我们可以针对消息签收成功与否? 消息是否被消费? 进行消息的管理 可以一定程度上的让消息发送更稳定
消息失败也有记录
上述已经完成了springboot项目中的 队列 交换机的定义、绑定、消息发送、消息接收
其实下面还有一个更简单的监听方式:
@Component
public class RabbitReceiver {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "queueName", declare = "true"),
exchange = @Exchange(value = "exchangeName", durable = "true", type = "topic",
ignoreDeclarationExceptions = "true"),
key = "springboot.testroutingkeys"
))
@RabbitHandler
public void onMessage(Message message, Channel channel) throws IOException {
String body = new String(message.getBody());
MessageProperties messageProperties = message.getMessageProperties();
Map<String, Object> headers = messageProperties.getHeaders();
if (headers != null) {
Set<Map.Entry<String, Object>> entries = headers.entrySet();
entries.stream().forEach(kv -> System.out.println("key:"+kv.getKey()+"val:"+kv.getValue()));
}
System.err.println("消费者接收信息:" + body);
//ack:手动ack确认成功收到消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
直接在注解之中定义了queue exchange 并且使用routingKey进行了绑定
消息接收
如此rabbitmq三部曲就完成了、其实这只是简单的入门、还有许多地方可以针对优化和设置、比如对象的转化(发送对象)、死信队列(失败的消息放到一个专门管理死亡消息的exchange)、消息限流、消息ttl
如果日后有需求 我看看是否在逐一进行讲解