前期准备
- 项目结构
- 在父工程的pom文件中引入依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 在消息发送方和消息消费方的配置文件中做基本配置
spring:
rabbitmq:
host: xxx.xxx.xxx.xxx # 主机名(写自己的)
port: 5672 # 端口
virtual-host: / # 虚拟主机
username: xxx # 用户名(启动docker镜像时的)
password: xxx # 密码(启动docker镜像时的)
- 在消费方注入对象
@Autowired
private RabbitTemplate rabbitTemplate;
简单队列模型
- 发送方
@Test
void testSimpleQueue(){
//队列名称
String queueName="simple.queue";
//消息内容
String msg = "test simplequeue";
//发送消息
rabbitTemplate.convertAndSend(queueName,msg);
System.out.println("消息发送完毕");
}
- 消费方
@RabbitListener(queues = "simple.queue") //queues参数指定队列名称,监听该队列
public void listenSimple(String msg){
//输出收到的消息
System.out.println(msg);
}
工作队列模型
- 发送方
@Test
void testWorkQueue(){
//队列名称
String queueName="work.queue";
//消息内容
String msg = "test workqueue";
for (int i = 1; i <= 100; i++) {
//发送消息
rabbitTemplate.convertAndSend(queueName,msg+i);
}
System.out.println("消息发送完毕");
}
- 消费方
/**
* 多个消费者共同消费一个队列
*/
@Component
public class WorkListener {
/**
* 消费者1
* @param msg
*/
@RabbitListener(queues = "work.queue")
public void listenWork1(String msg){
System.out.println("消费者1:"+msg);
}
/**
* 消费者2
* @param msg
*/
@RabbitListener(queues = "work.queue")
public void listenWork2(String msg){
System.out.println("消费者2:"+msg);
}
}
- 补充
多个消费者(以两个为例)默认会均匀消费消息,这是不太合理的,有时候消费者的消费效率不一样,所以我们应该指定消费策略,在消费者方的配置文件中添加下方红色方框中的”prefetch“配置,只有当消费完前一条的消息才能拿到下一条消息。
发布订阅模型
声明队列和交换机
- 方式一:配置类型形式(以Fanout类型为例)
@Configuration
public class MqConfig {
/**
* 交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
//交换机名称:exchange.fanout
return new FanoutExchange("exchange.fanout");
}
/**
* 队列
* @return
*/
@Bean
public Queue queue1(){
//队列名称:fanout.queue1
return new Queue("fanout.queue1");
}
/**
* 队列
* @return
*/
@Bean
public Queue queue2(){
//队列名称:fanout.queue2
return new Queue("fanout.queue2");
}
/**
* 绑定
*/
@Bean
//注意参数列表中的参数名要和上面声明的交换机、队列参数名保持一致
public Binding binding1(FanoutExchange fanoutExchange,Queue queue1){
return BindingBuilder.bind(queue1).to(fanoutExchange);
}
/**
* 绑定
*/
@Bean
//注意参数列表中的参数名要和上面声明的交换机、队列参数名保持一致
public Binding binding2(FanoutExchange fanoutExchange,Queue queue2){
return BindingBuilder.bind(queue2).to(fanoutExchange);
}
}
- 方式二:注解形式
Fanout
- 声明交换机和队列
@Configuration
public class MqConfig {
/**
* 交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
//交换机名称:exchange.fanout
return new FanoutExchange("exchange.fanout");
}
/**
* 队列
* @return
*/
@Bean
public Queue queue1(){
//队列名称:fanout.queue1
return new Queue("fanout.queue1");
}
/**
* 队列
* @return
*/
@Bean
public Queue queue2(){
//队列名称:fanout.queue2
return new Queue("fanout.queue2");
}
/**
* 绑定
*/
@Bean
//注意参数列表中的参数名要和上面声明的交换机、队列参数名保持一致
public Binding binding1(FanoutExchange fanoutExchange,Queue queue1){
return BindingBuilder.bind(queue1).to(fanoutExchange);
}
/**
* 绑定
*/
@Bean
//注意参数列表中的参数名要和上面声明的交换机、队列参数名保持一致
public Binding binding2(FanoutExchange fanoutExchange,Queue queue2){
return BindingBuilder.bind(queue2).to(fanoutExchange);
}
}
- 发送方
@Test
void testFanout(){
//交换机名称
String exchange = "exchange.fanout";
//消息
String msg = "test fanout";
//发送消息到交换机,参数(交换机,routingKey,消息)
rabbitTemplate.convertAndSend(exchange,"",msg);
System.out.println("消息发送完毕");
}
- 消费方
@Component
public class FanoutListener {
@RabbitListener(queues = "fanout.queue1")
public void listenFanout1(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanout2(String msg){
System.out.println("消费者2:"+msg);
}
}
Direct
- 发送方
@Test
void testDirect(){
//交换机名称
String exchange = "exchange.direct";
//消息
String msg = "test direct";
//发送消息到交换机,参数(交换机,routingKey,消息)
rabbitTemplate.convertAndSend(exchange,"这里指定绑定key",msg);
System.out.println("消息发送完毕");
}
- 消费方
@Component
public class DirectListener {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "direct.queue1"), //指定绑定队列
exchange = @Exchange(name = "exchange.direct",type = ExchangeTypes.DIRECT), //指定交换机、交换机类型
key = {"blue","red"} //绑定key
)
)
public void listenDirect1(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "exchange.direct",type = ExchangeTypes.DIRECT),
key = {"yellow","red"}
)
)
public void listenDirect2(String msg){
System.out.println("消费者2:"+msg);
}
}
Topic
- 发送方
@Test
void testTopic(){
//交换机名称
String exchange = "exchange.topic";
//消息
String msg = "test topic";
//发送消息到交换机,参数(交换机,routingKey,消息)
rabbitTemplate.convertAndSend(exchange,"这里指定topic",msg);
System.out.println("消息发送完毕");
}
- 消费方
@Component
public class TopicListener {
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"), //指定绑定队列
exchange = @Exchange(name = "exchange.topic",type = ExchangeTypes.TOPIC), //指定交换机、交换机类型
key = {"china.#"} //topic
)
)
public void listenTopic1(String msg){
System.out.println("消费者1:"+msg);
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(name = "topic.queue2"), //指定绑定队列
exchange = @Exchange(name = "exchange.topic",type = ExchangeTypes.TOPIC), //指定交换机、交换机类型
key = {"#.news"} //topic
)
)
public void listenTopic2(String msg){
System.out.println("消费者2:"+msg);
}
}
消息转换器
使用默认的消息转换器出现的乱码问题
在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型
的消息,SpringAMQP会帮我们序列化为字节后发送;
Spring的对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。
众所周知,JDK序列化存在下列问题:
- 数据体积过大
- 有安全漏洞
- 可读性差
此时我们发送一个对象消息
@Test
public void testSendMap() throws InterruptedException {
// 准备消息
Map<String,Object> msg = new HashMap<>();
msg.put("name", "小明");
msg.put("age", 30);
msg.put("sex", "男");
// 发送消息
rabbitTemplate.convertAndSend("object.queue", msg);
}
获取消息变成乱码
解决方案
使用JSON方式来做序列化和反序列化
- 在发送方引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
- 在启动类配置一个消息转换器
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}