项目结构
rabbitmq-common子项目
rabbitmq-common 就是一个普通的maven项目
这个项目中我就只创建了 Order这个实体类, 主要是拿这个实体类当消息传输来用的。
特别注意一点,此类一定要实现 Serializable
这个接口,因为当我们要传输消息的时候是将对象序列化后进行传输的 、接收消息就是反序列化
package com.cpc.rabbitmqcommon.entity;
import java.io.Serializable;
/**
* 这是用来做消息传递的 实体类
*/
public class Order implements Serializable {
private String id;
private String name;
public Order() {
}
public Order(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
rabbitmq-springcloud-consumer子项目
这个工程扮演的就是我们的微服务中的消息消费者
消费端核心配置
-
首先配置手工确认模式,用于ACK的手工处理,这样我们可以保证消息的可靠性送达,或者在消费端消费失败的时候可以做到重回队列、根据业务记录日志等处理
-
可以设置消费端的监听个数和最大个数,用于控制消费端的并发情况
Pom依赖
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.cpc</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
application.properties
# 这是 链接主机 + 端口
spring.rabbitmq.addresses=192.168.43.184:5672
# 用户名和密码
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
# 虚拟主机 配置
spring.rabbitmq.virtual-host=/
# 这是 链接超时时间设置
spring.rabbitmq.connection-timeout=15000
# 其他的一些配置 略过
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=5
spring.rabbitmq.listener.simple.max-concurrency=10
# 这是通过配置文件的形式来配置我们的消费者 (别急待会看下面代码你就知道了 )
spring.rabbitmq.listener.order.queue.name=queue-2
spring.rabbitmq.listener.order.queue.durable=true
spring.rabbitmq.listener.order.exchange.name=exchange-2
spring.rabbitmq.listener.order.exchange.durable=true
spring.rabbitmq.listener.order.exchange.type=topic
spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
spring.rabbitmq.listener.order.key=springboot.*
RabbitReceiver
这是我们消费消息的类,里面定义了一2个接收消息的回掉函数 onMessage
和 onOrderMessage
。并且通过注解配置了消费者的相关信息。一旦对应的队列中有消息,就会调用这个回掉函数来消费消息
package com.cpc.rabbitmq.consumer.conusmer;
import com.cpc.rabbitmqcommon.entity.Order;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class RabbitReceiver {
/**
*这是写死的一种做法
* @param message
* @param channel
* @throws Exception
*/
@RabbitListener(bindings = @QueueBinding(
// queue-1 这是要消费队列的名字
value = @Queue(value = "queue-1",
// durable="true" 是否持久化
durable="true"),
// exchange-1 这是指定交换器
exchange = @Exchange(value = "exchange-1",
durable="true",
//制定交换机类型
type= "topic",
//忽略异常
ignoreDeclarationExceptions = "true"),
//这是指定 路由 key
key = "springboot.*"
)
)
@RabbitHandler
public void onMessage(Message message, Channel channel) throws Exception {
System.err.println("--------------------------------------");
System.err.println("消费端Payload: " + message.getPayload());
Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
/**
* 这和我们刚刚的配置文件相对应
* spring.rabbitmq.listener.order.queue.name=queue-2
spring.rabbitmq.listener.order.queue.durable=true
spring.rabbitmq.listener.order.exchange.name=exchange-1
spring.rabbitmq.listener.order.exchange.durable=true
spring.rabbitmq.listener.order.exchange.type=topic
spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions=true
spring.rabbitmq.listener.order.key=springboot.*
* @param order
* @param channel
* @param headers
* @throws Exception
*/
//下面的配置和上面的配置一样,但是这里是从配置文件中读取配置
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "${spring.rabbitmq.listener.order.queue.name}",
durable="${spring.rabbitmq.listener.order.queue.durable}"),
exchange = @Exchange(value = "${spring.rabbitmq.listener.order.exchange.name}",
durable="${spring.rabbitmq.listener.order.exchange.durable}",
type= "${spring.rabbitmq.listener.order.exchange.type}",
ignoreDeclarationExceptions = "${spring.rabbitmq.listener.order.exchange.ignoreDeclarationExceptions}"),
key = "${spring.rabbitmq.listener.order.key}"
)
)
@RabbitHandler
public void onOrderMessage(@Payload Order order,
Channel channel,
@Headers Map<String, Object> headers) throws Exception {
System.err.println("--------------------------------------");
System.err.println("消费端order: " + order.getId());
Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG);
//手工ACK
channel.basicAck(deliveryTag, false);
}
}
@RabbitListener注解的使用
消费端监听@RabbitListener注解,这个对于在实际工作中非常的好用,@RabbitListener是一个组合注解,里面可以注解配置(@QueueBinding、@Queue、@Exchange)直接通过这个组合注解一次性搞定消费端交换机、队列、绑定、路由、并且配置监听功能等
ps:由于类配置写在代码里非常不友好,所以强烈建议大家使用配置文件配置
rabbitmq-springcloud-producer子项目
rabbitmq-springcloud-producer 这个项目就是我们微服务中的消息生成者啦
pom.xml
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.cpc</groupId>
<artifactId>rabbitmq-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
application.properties(rabbitmq的配置和 消费者的配置差不多,这里就略过了)
# 和上面的配置一样
spring.rabbitmq.addresses=192.168.43.184:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
spring.rabbitmq.connection-timeout=15000
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true
注意一点,在发送消息的时候对template进行配置mandatory=true保证监听有效
生产端还可以配置其他属性,比如发送重试,超时时间、次数、间隔等
RabbitSender.java (说明都在注释中 )
package com.cpc.rabbitmq.producer;
import com.cpc.rabbitmqcommon.entity.Order;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ConfirmCallback;
import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class RabbitSender {
//自动注入RabbitTemplate模板类
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 回调函数: confirm确认
* 确认消息发送成功,通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调,
* 使用该功能需要开启确认,spring-boot中配置如下: spring.rabbitmq.publisher-confirms = true
*/
final ConfirmCallback confirmCallback = new ConfirmCallback() {
/**
*
* @param correlationData 回调的相关数据
* @param ack 消息确认结果
* @param cause 可选原因,如果存在,则为nack,否则为null(就是如果消息为 nack 这里就是附带的原因)
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.err.println("correlationData: " + correlationData);
System.err.println("ack: " + ack);
if(!ack){
System.err.println("异常处理....");
}
}
};
/**
* 回调函数: return返回
* 通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发),
* 使用该功能需要开启确认,spring-boot中配置如下:spring.rabbitmq.publisher-returns = true
*/
final ReturnCallback returnCallback = new ReturnCallback() {
/**
* @param message 返回的消息
* @param replyCode 回复码
* @param replyText 回复文本
* @param exchange 这是对应的交换机
* @param routingKey 这是 routingKey
*/
@Override
public void returnedMessage(org.springframework.amqp.core.Message message, int replyCode, String replyText,
String exchange, String routingKey) {
System.err.println("return exchange: " + exchange + ", routingKey: "
+ routingKey + ", replyCode: " + replyCode + ", replyText: " + replyText);
}
};
/**
* 发送消息方法调用: 构建Message消息
* @param message 这是要发送的消息
* @param properties 这是 properties 参数 (消息由 message + properties 构成)
* @throws Exception
*/
public void send(Object message, Map<String, Object> properties) throws Exception {
MessageHeaders mhs = new MessageHeaders(properties);
Message msg = MessageBuilder.createMessage(message, mhs);
//设置回掉
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
//id + 时间戳 全局唯一
CorrelationData correlationData = new CorrelationData("1234567890");
rabbitTemplate.convertAndSend("exchange-1", "springboot.abc", msg, correlationData);
}
/**
* 发送消息方法调用: 构建自定义对象消息
* @param order 这是要发送的对象
* @throws Exception
*/
public void sendOrder(Order order) throws Exception {
//设置回掉
rabbitTemplate.setConfirmCallback(confirmCallback);
rabbitTemplate.setReturnCallback(returnCallback);
//id + 时间戳 全局唯一
CorrelationData correlationData = new CorrelationData("0987654321");
rabbitTemplate.convertAndSend("exchange-2", "springboot.def", order, correlationData);
}
}
消息生产者测试代码
package com.cpc.rabbitmq.producer;
import com.cpc.rabbitmqcommon.entity.Order;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@RunWith(SpringRunner.class)
@SpringBootTest
class RabbitmqSpringcloudProducerApplicationTests {
@Autowired
private RabbitSender rabbitSender;
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@Test
public void testSender1() throws Exception {
Map<String, Object> properties = new HashMap<>();
properties.put("number", "12345");
properties.put("send_time", simpleDateFormat.format(new Date()));
rabbitSender.send("Hello RabbitMQ For Spring Boot!", properties);
}
@Test
public void testSender2() throws Exception {
Order order = new Order("001", "第一个订单");
rabbitSender.sendOrder(order);
}
}
测试
首先我们先启动我们的消息消费者,启动后会自动创建对应的交换机、和队列。
下面就启动我们生产者端对应的测试方法来测试一下吧
首先我们测试一下 testSender1 方法
消费者端控制台输出:
生产者端控制提输出:
testSender2 测试
消费者端控制台输出:
生产者端控制提输出: