1.引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置application.properties文件:
spring.rabbitmq.virtual-host=/
#spring.rabbitmq.host=localhost
spring.rabbitmq.addresses=172.30.67.122:5672,172.30.67.122:5673,172.30.67.122:5674
#spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
#producer
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true
#consumer
##手工確認消費者消費的消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual
##設置Qos,即RabbitMQ服務器每次推送給消費者未ack消息的個數
spring.rabbitmq.listener.simple.prefetch=1
2.定義隊列、交換器、路由之間的綁定關係
package com.yaomy.control.rabbitmq.amqp.config;
import com.google.common.collect.Maps;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @Description: RabbitMQ生產者交換器、綁定、隊列聲明
* @Version: 1.0
*/
@SuppressWarnings("all")
@Configuration
public class RabbitConfig {
/**
* 聲明隊列
*/
@Bean
public Queue topicQueue(){
Map<String, Object> args = Maps.newHashMap();
/**
* 設置消息發送到隊列之後多久被丟棄,單位:毫秒
*/
//args.put("x-message-ttl", 60000);
/**
* 定義優先級隊列,消息最大優先級爲15,優先級範圍爲0-15,數字越大優先級越高
*/
args.put("x-max-priority", 15);
/**
* 設置持久化隊列
*/
return QueueBuilder.durable("test_queue2").withArguments(args).build();
}
/**
* 聲明Topic類型交換器
*/
@Bean
public TopicExchange topicExchange(){
TopicExchange exchange = new TopicExchange("test_exchange2");
return exchange;
}
/**
* Topic交換器和隊列通過bindingKey綁定
* @return
*/
@Bean
public Binding bindingTopicExchangeQueue(){
return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("*.topic.*");
}
}
3.定義生產者
package com.yaomy.control.rabbitmq.amqp;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.connection.PublisherCallbackChannel;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.UUID;
/**
* @Description: RabbitMQ生產者
* @ProjectName: spring-parent
* @Version: 1.0
*/
@SuppressWarnings("all")
@Component
public class RabbitSender {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 創建一個消息是否投遞成功的回調方法
*/
private final RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
/**
*
* @param correlationData 消息的附加信息
* @param ack true for ack, false for nack
* @param cause 是一個可選的原因,對於nack,如果可用,否則爲空。
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(!ack){
//可以進行日誌記錄、異常處理、補償處理等
System.err.println("異常ack-"+ack+",id-"+correlationData.getId()+",cause:"+cause);
}else {
//更新數據庫,可靠性投遞機制
System.out.println("正常ack-"+ack+",id-"+correlationData.getId());
try{
System.out.println(new String(correlationData.getReturnedMessage().getBody()));
} catch (Exception e){
}
}
}
};
/**
* 創建一個消息是否被隊列接收的監聽對象,如果沒有隊列接收發送出的消息,則調用此方法進行後續處理
*/
private final RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
/**
*
* @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.err.println("spring_returned_message_correlation:"+message.getMessageProperties().getHeaders().get(PublisherCallbackChannel.RETURNED_MESSAGE_CORRELATION_KEY)
+"return exchange: " + exchange
+ ", routingKey: "+ routingKey
+ ", replyCode: " + replyCode
+ ", replyText: " + replyText
+ ",message:" + message);
try {
System.out.println(new String(message.getBody()));
} catch (Exception e){
}
}
};
/**
* 發送消息
* @param exchange 交換器
* @param route 路由鍵
* @param message 消息
* @param properties
*/
public void sendMsg(String exchange, String routingKey, String message, MessageProperties properties){
try {
if(null == properties){
properties = new MessageProperties();
}
/**
* 設置消息唯一標識
*/
properties.setMessageId(UUID.randomUUID().toString());
/**
* 創建消息包裝對象
*/
Message msg = MessageBuilder.withBody(message.getBytes()).andProperties(properties).build();
/**
* 設置生產者消息publish-confirm回調函數
*/
this.rabbitTemplate.setConfirmCallback(confirmCallback);
/**
* 設置消息退回回調函數
*/
this.rabbitTemplate.setReturnCallback(returnCallback);
/**
* 將消息主題和屬性封裝在Message類中
*/
Message returnedMessage = MessageBuilder.withBody(message.getBytes()).build();
/**
* 相關數據
*/
CorrelationData correlationData = new CorrelationData();
/**
* 消息ID,全局唯一
*/
correlationData.setId(msg.getMessageProperties().getMessageId());
/**
* 設置此相關數據的返回消息
*/
correlationData.setReturnedMessage(returnedMessage);
/**
* 如果msg是org.springframework.amqp.core.Message對象的實例,則直接返回,否則轉化爲Message對象
*/
this.rabbitTemplate.convertAndSend(exchange, routingKey, msg, new MessagePostProcessor() {
/**
* 消息後置處理器,消息在轉換成Message對象之後調用,可以用來修改消息中的屬性、header
*/
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties msgProperties = message.getMessageProperties();
/**
* 設置消息發送到隊列之後多久被丟棄,單位:毫秒
* 此種方案需要每條消息都設置此屬性,比較靈活;
* 還有一種方案是在聲明隊列的時候指定發送到隊列中的過期時間;
* * Queue queue = new Queue("test_queue2");
* * queue.getArguments().put("x-message-ttl", 10000);
* 這兩種方案可以同時存在,以值小的爲準
*/
//msgProperties.setExpiration("10000");
/**
* 設置消息的優先級
*/
msgProperties.setPriority(9);
/**
* 設置消息發送到隊列中的模式,持久化|非持久化(只存在於內存中)
*/
msgProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
}, correlationData);
} catch (Exception e){
e.printStackTrace();
}
}
}
4.消費者
@RabbitListener註解標註指定的方法監聽指定的隊列、也可以標註在類上結合@RabbitHandler使用;監聽方法可以使用多種參數接收消息,查看源碼可以看到是允許六種參數:
* Annotated methods are allowed to have flexible signatures similar to what
* {@link MessageMapping} provides, that is
* <ul>
* <li>{@link com.rabbitmq.client.Channel} to get access to the Channel</li>
* <li>{@link org.springframework.amqp.core.Message} or one if subclass to get access to
* the raw AMQP message</li>
* <li>{@link org.springframework.messaging.Message} to use the messaging abstraction
* counterpart</li>
* <li>{@link org.springframework.messaging.handler.annotation.Payload @Payload}-annotated
* method arguments including the support of validation</li>
* <li>{@link org.springframework.messaging.handler.annotation.Header @Header}-annotated
* method arguments to extract a specific header value, including standard AMQP headers
* defined by {@link org.springframework.amqp.support.AmqpHeaders AmqpHeaders}</li>
* <li>{@link org.springframework.messaging.handler.annotation.Headers @Headers}-annotated
* argument that must also be assignable to {@link java.util.Map} for getting access to
* all headers.</li>
* <li>{@link org.springframework.messaging.MessageHeaders MessageHeaders} arguments for
* getting access to all headers.</li>
示例代碼:
package com.yaomy.control.rabbitmq.amqp;
import com.rabbitmq.client.Channel;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Description: RabbitMQ消息消費者
* @Version: 1.0
*/
@SuppressWarnings("all")
@Component
public class RabbitReceiver {
/**
*
* @param channel 信道
* @param message 消息
* @throws Exception
*/
@RabbitListener(queues = "test_queue2")
public void onMessage(Channel channel, Message message) throws Exception {
System.out.println("--------------------------------------");
System.out.println("消費端Payload: " + message.getPayload()+"-ID:"+message.getHeaders().getId()+"-messageId:"+message.getHeaders());
Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//手工ACK,獲取deliveryTag
channel.basicAck(deliveryTag, false);
}
/**
*
* @param channel 信道
* @param message 消息
* @throws Exception
*/
@RabbitListener(queues = "test_queue2")
public void onMessage(Channel channel, org.springframework.amqp.core.Message message) throws Exception {
System.out.println("--------------------------------------");
System.out.println("消費端Payload: " + new String(message.getBody())+"-messageId:"+message.getMessageProperties().getMessageId());
message.getMessageProperties().getHeaders().forEach((key, value)->{
System.out.println("header=>>"+key+"="+value);
});
Long deliveryTag = message.getMessageProperties().getDeliveryTag();
//手工ACK,獲取deliveryTag
channel.basicAck(deliveryTag, false);
}
/**
*
* @param channel 信道
* @param body 負載
* @param amqp_messageId 消息唯一標識
* @param headers 消息header
* @throws Exception
*/
//獲取特定的消息
@RabbitListener(queues = "test_queue2")
//@RabbitHandler
public void handleMessage(Channel channel, @Payload byte[] body, @Header String amqp_messageId, @Headers Map<String, Object> headers) throws Exception{
System.out.println("====消費消息===amqp_messageId:"+amqp_messageId);
headers.keySet().forEach((key)->{
System.out.println("header=>>"+key+"="+headers.get(key));
});
System.out.println(new String(body));
Long deliveryTag = NumberUtils.toLong(headers.get("amqp_deliveryTag").toString());
/**
* 手動Ack
*/
channel.basicAck(deliveryTag, false);
}
/**
*
* @param channel 信道
* @param body 負載
* @param headers 消息header
* @throws Exception
*/
@RabbitListener(queues = "test_queue2")
//@RabbitHandler
public void handleMessage(Channel channel, @Payload byte[] body, MessageHeaders headers) throws Exception{
System.out.println("====消費消息===amqp_messageId:"+headers);
headers.keySet().forEach((key)->{
System.out.println("header=>>"+key+"="+headers.get(key));
});
System.out.println(new String(body));
Long deliveryTag = NumberUtils.toLong(headers.get("amqp_deliveryTag").toString());
/**
* 手動Ack
*/
channel.basicAck(deliveryTag, false);
}
}
使用MessageHeaders參數類型接收header示例如下:
header=>>amqp_receivedDeliveryMode=PERSISTENT
header=>>amqp_receivedExchange=test_exchange2
header=>>amqp_deliveryTag=1
header=>>amqp_consumerQueue=test_queue2
header=>>amqp_redelivered=false
header=>>priority=9
header=>>amqp_receivedRoutingKey=test.topic.key
header=>>number=12345
header=>>spring_listener_return_correlation=34b20ba3-9fb0-49e5-b57e-5484e773e9b3
header=>>send_time=2019-12-27 16:08:55
header=>>spring_returned_message_correlation=cba66847-5c31-4ecb-bf97-07ba65238b9a
header=>>amqp_messageId=cba66847-5c31-4ecb-bf97-07ba65238b9a
header=>>id=b58e6adb-e5b2-9513-45c5-8376311cf82f
header=>>amqp_consumerTag=amq.ctag-hPKHjdTdj0NY4awa4bFkAA
header=>>contentType=application/octet-stream
header=>>timestamp=1577434135312
其headers中有一個id屬性,這個屬性是消費者接收消息new MessageHeaders的時候生成的隨機UUID,不可以作爲整個系統中消息的唯一標識,只可以作爲消費端的唯一標識。
GitHub地址:https://github.com/mingyang66/spring-parent/tree/master/spring-boot-control-rabbitmq-service