Rabbitmq加Spring boot
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置,写的是jvm中rabbit的地址和自己设置rabbit账户
spring:
rabbitmq:
host: 192.168.120.150
username: admin
password: admin
# virtual-host: 自己的空间
Spring提供的Queue类,是队列的封装对象,它封装了队列的参数信息.
RabbitMQ的自动配置类,会发现这些Queue实例,并在RabbitMQ服务器中定义这些队列.
创建一个容器管理的队列,斜面都系要,只是名字不同
/*创建 spring 的 Queue 对象,用来封装队列的参数,
RabbitAutoConfiguration 自动配置类,会自动发现 Queue 实例,
使用这里的参数,连接服务器在服务器上创建队列
*/
@Bean
public Queue task_queue() {
/*
* 可用以下形式: (默认)
* new Queue("helloworld") - 持久,非排他,非自动删除
* new Queue("helloworld",ture,false,false,null)
*/
return new Queue("helloworld");
}
第一、简单模式
qizgogn AmqpTemplate是rabbitmq客户端API的一个封装工具,由自动配置类自动创建
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Product {
// spring提供的用来发送消息的工具对象
//在 RabbitAutoConfiguration中创建
@Autowired
AmqpTemplate t;
public void send() {
// 这里向 helloworld 队列发送消息
//如果说是对象的话,需要将对象序列化(byte[])
t.convertAndSend("helloworld", "Hello world!! "+System.currentTimeMillis());
System.out.println("消息已发送");
}
}
2.通过@RabbitListener从指定的队列接收消息,使用@RebbitHandler处理消息
方式一
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "helloworld")
public class Consumer {
@RabbitHandler
public void receive(String msg) {
System.out.println("收到: "+msg);
}
}
方式二、同时也可以只使用@RebbitHandler直接接收和处理消息
@Component
public class Consumer {
@RabbitListener(queues = "helloworld")
public void receive(String msg) {
System.out.println("收到: "+msg);
}
}
@RabbitListener也能直接定义队列
@RabbitListener(queuesToDeclare = @Queue(name = "helloworld",durable = "false"))
测试,工作模式和ASK可用
试一试代码效果
import java.util.Scanner;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ProductTests {
@Autowired
Product p ;
@Test
void test1() throws Exception {
p.send();
System.out.println("[按回车结束]");
new Scanner(System.in).nextLine();
}
}
或者在启动类测试哦
// 添加测试代码,调用生产者发送消息
@Autowired
private Producer p;
/*
spring 的执行流程:
包扫描创建所有实例 --> 完成所有的依赖注入 --> @PostConstruct --> 后续流程自动配置类,消费者.....
*/
@PostConstruct
public void test() {
// new Thread(new Runnable() {
// @Override
// public void run() {
// p.send();
// }
// }).start();
// lambda表达式,是匿名内部类的简化语法
new Thread(() -> p.send()).start();
}
第二、工作模式
现在需要创建持久队列
因为上面使用过名为helloworld的队列,如果还需要使用该队列名,只能把注册的队列删除之后再创建
springboot中queue默认是,持久,非独占,非自动删除
@Component
public class Product {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.print("输入:");
String s = new Scanner(System.in).nextLine();
//spring 默认将消息的 DeliveryMode 设置为 PERSISTENT 持久化,
t.convertAndSend("task_queue", s);
// t.convertAndSend( "task_queue",s,消息预处理器,可以重新设置消息的属性);
}
}
}
如果需要将发送非持久化消息,
则使用 MessagePostProcessor 前置处理器参数,在属性中把 DeliveryMode 设置为非持久化
//如果需要设置消息为非持久化,可以取得消息的属性对象,修改它的deliveryMode属性
t.convertAndSend("task_queue", (Object) s, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties props = message.getMessageProperties();
props.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
return message;
}
});
设置两个消费者接受消息
@Component
public class Consumer {
@RabbitListener(queues="task_queue")
public void receive1(String s) throws Exception {
System.out.println("receiver1 - 收到: "+s);
}
@RabbitListener(queues="task_queue")
public void receive2(String s) throws Exception {
System.out.println("receiver2 - 收到: "+s);
}
第三、合理分发ack模式,懂?哈哈,另外一篇简单测试有介绍
ACK\QOS
ack-- spring封装的rabbitmq,已经是手动ack模式,
spring会自动发送回执
qos=1 – spring默认设置的qos是250,
yml中可以改变
配置一下
spring:
rabbitmq:
listener:
simple:
# acknowledgeMode: NONE # rabbitmq的自动确认
acknowledgeMode: AUTO # rabbitmq的手动确认, springboot会自动发送确认回执 (默认)
# acknowledgeMode: MANUAL # rabbitmq的手动确认, springboot不发送回执, 必须自己编码发送回执
抓取数量的话,还要配置
qos 设置成 1, 每次只接收一条消息, 处理完成后才接收下一条消息
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # qos=1, 默认250
手动执行确认操作
MANUAL 模式,必须手动执行确认操作
@RabbitListener(queues="task_queue")
public void receive1(String s, Channel c, @Header(name=AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
System.out.println("receiver1 - 收到: "+s);
// 手动发送确认回执
c.basicAck(tag, false);
}
第四、发布和订阅模式(广播模式)
创建的是Fanout交换机
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("logs");
}
发送消息
import java.util.Scanner;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Publisher {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.print("输入:");
String s = new Scanner(System.in).nextLine();
// 指定向 logs 交换机发送, 不指定队列名或路由键
//对于fanout交换机来说,""时无效的
t.convertAndSend("logs","",s);
}
}
}
接收的消费者
package cn.tedu.m3;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Subscriber {
@RabbitListener(bindings = @QueueBinding( //这里进行绑定设置
value = @Queue, //这里定义随机队列,默认属性: 随机命名,非持久,排他,自动删除
exchange = @Exchange(name = "logs", declare = "false") //指定 logs 交换机,因为主程序中已经定义,这里不进行定义
))
public void receive1(String s) throws Exception {
System.out.println("receiver1 - 收到: "+s);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue, exchange = @Exchange(name = "logs", declare = "false")))
public void receive2(String s) throws Exception {
System.out.println("receiver2 - 收到: "+s);
}
}
测试吗
@Test
void test1() throws Exception {
publisher.send();
}
路由模式
使用 direct 交换机
@Bean
public DirectExchange fanoutExchange() {
return new DirectExchange("direct_logs");
}
@Component
public class RouteProduct {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String key = new Scanner(System.in).nextLine();
// 第二个参数指定路由键
t.convertAndSend("direct_logs",key,s);
}
}
}
@Component
public class RouteReceiver {
@RabbitListener(bindings = @QueueBinding( // 这里做绑定设置
value = @Queue, // 定义队列, 随机命名,非持久,排他,自动删除
exchange = @Exchange(name = "direct_logs", declare = "false"), // 指定绑定的交换机,主程序中已经定义过队列,这里不进行定义
key = {"error","info","warning"} // 设置绑定键
))
public void receive1(String s) throws Exception {
System.out.println("receiver1 - 收到: "+s);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue, exchange = @Exchange(name = "direct_logs", declare = "false"),key = {"error","info","warning"}))
public void receive2(String s) throws Exception {
System.out.println("receiver2 - 收到: "+s);
}
}
主题模式
使用 topic 交换机
@Bean
public TopicExchange fanoutExchange() {
return new TopicExchange("topic_logs");
}
@Component
public class TopicSender {
@Autowired
AmqpTemplate t;
public void send() {
while (true) {
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
System.out.print("输入路由键:");
String key = new Scanner(System.in).nextLine();
t.convertAndSend("topic_logs",key,s);
}
}
}
@Component
public class TopicReceiver {
@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "topic_logs", declare = "false"),key = {"*.orange.*"}))
public void receive1(String s) throws Exception {
System.out.println("receiver1 - 收到: "+s);
}
@RabbitListener(bindings = @QueueBinding(value = @Queue, exchange = @Exchange(name = "topic_logs", declare = "false"),key = {"*.*.rabbit","lazy.#"}))
public void receive2(String s) throws Exception {
System.out.println("receiver2 - 收到: "+s);
}
}
最麻烦最难的 RPC异步调用
需要定义两个队列
生产者发送到调用队列,在发送到消息上需要携带两个数据(1.返回的队列名2关联id标识)
关联i的话,是一个生产者发出多条消息时,后端负载均衡处理,返回时没有标识将不知道是第几次发出的响应消息。也可以设置每条消息发出将有一个新的返回队列,但这样太消耗内存
可以负载均衡方式接受处理队列消息
创建两个队列
@Bean
public Queue sendQueue() {
return new Queue("rpc_queue",false);
}
@Bean
public Queue rndQueue() {
return new Queue(UUID.randomUUID().toString(), false);
}
处理:
@Rabbitlistener注解对于具有返回值的方法:
会自动获取 replyTo 属性
自动获取 correlationId 属性
向 replyTo 属性指定的队列发送计算结果, 并携带 correlationId 属性
@Component
public class Server {
/*
如果处理消息的方法不是 void,有返回值,
那么 spring 会把返回值,通过返回队列发回到客户端,并携带关联id
*/
@RabbitListener(queues = "rpc-queue")
public long receive(int n) {
long r = f(n);
return r;
}
private long f(int n) {
long a = 1;
long b = 1;
for (int i = 3; i <= n; i++) {
b = a+b;
a = b-a;
}
return b;
}
}
/*
SPEL --- Spring Expression Language
直接访问 spring 容器中的对象
#{rndQueue.name}
OGNL --- Object Graph Navigation Language
Struts2 中的一种标记
${}
*/
@Component
public class Client {
@Autowired
private AmqpTemplate t;
@Value("#{rndQueue.name}")
private String replyTo;
public void send(int n) {
t.convertAndSend("rpc-queue", n, (message) -> {
MessageProperties p = message.getMessageProperties();
p.setReplyTo(replyTo);
p.setCorrelationId(UUID.randomUUID().toString());
return message;
});
}
@RabbitListener(queues = "#{rndQueue.name}")
public void receive(long r, @Header(name = AmqpHeaders.CORRELATION_ID) String cid) {
System.out.println(cid + "--- 结果:"+r);
}
}