文章目錄
新建項目
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>rabbitmq-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-springboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</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>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
spring:
rabbitmq:
host: 192.168.64.140
username: admin
password: admin
主程序
刪除自動創建的主程序
我們爲每種模式創建一個包,在每個包中創建各自的主程序,單獨測試.
簡單模式
主程序
Spring提供的Queue類,是隊列的封裝對象,它封裝了隊列的參數信息.
RabbitMQ的自動配置類,會發現這些Queue實例,並在RabbitMQ服務器中定義這些隊列.
package cn.tedu.m1;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue task_queue() {
/*
* 可用以下形式:
* new Queue("helloworld") - 持久,非排他,非自動刪除
* new Queue("helloworld",false,false,false,null)
*/
return new Queue("helloworld",false);
}
}
生產者
AmqpTemplate是rabbitmq客戶端API的一個封裝工具,提供了簡便的方法來執行消息操作.
AmqpTemplate由自動配置類自動創建
package cn.tedu.m1;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SimpleSender {
@Autowired
AmqpTemplate t;
public void send() {
// 這裏向 helloworld 隊列發送消息
t.convertAndSend("helloworld", "Hello world!! "+System.currentTimeMillis());
System.out.println("消息已發送");
}
}
消費者
通過@RabbitListener
從指定的隊列接收消息
使用@RebbitHandler
註解的方法來處理消息
package cn.tedu.m1;
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 SimpleReceiver {
@RabbitHandler
public void receive(String msg) {
System.out.println("收到: "+msg);
}
}
這裏還可以使用另一種形式:
@Component public class SimpleReceiver { @RabbitListener(queues = "helloworld") public void receive(String msg) { System.out.println("收到: "+msg); } }
另外,
@RabbitListener
註解中也可以直接定義隊列:@RabbitListener(queuesToDeclare = @Queue(name = "helloworld",durable = "false"))
測試類
在存放測試代碼的目錄中,創建測試類
package cn.tedu.m1;
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 SimpleTests {
@Autowired
SimpleSender simpleSender;
@Test
void test1() throws Exception {
simpleSender.send();
System.out.println("[按回車結束]");
new Scanner(System.in).nextLine();
}
}
工作模式
主程序
在主程序中創建名爲task_queue
的持久隊列
package cn.tedu.m2;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue task_queue() {
// 這個構造方法創建的隊列參數爲: 持久,非排他,非自動刪除
return new Queue("task_queue");
}
}
生產者
package cn.tedu.m2;
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 WorkSender {
@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);
}
}
}
spring boot封裝的 rabbitmq api 中, 發送的消息默認是持久化消息.
如果希望發送非持久化消息, 需要在發送消息時做以下設置:
- 使用 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; } });
消費者
package cn.tedu.m2;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class WorkReceiver1 {
@RabbitListener(queues="task_queue")
public void receive1(String s) throws Exception {
System.out.println("receiver1 - 收到: "+s);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
Thread.sleep(1000);
}
}
}
@RabbitListener(queues="task_queue")
public void receive2(String s) throws Exception {
System.out.println("receiver2 - 收到: "+s);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
Thread.sleep(1000);
}
}
}
}
測試類
package cn.tedu.m2;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WorkTests {
@Autowired
WorkSender workSender;
@Test
void test1() throws Exception {
workSender.send();
}
}
ack模式
在 spring boot 中提供了三種確認模式:
- NONE - 使用rabbitmq的自動確認
- AUTO - 使用rabbitmq的手動確認, springboot會自動發送確認回執 (默認)
- MANUAL - 使用rabbitmq的手動確認, 且必須手動執行確認操作
默認的 AUTO
模式中, 處理消息的方法拋出異常, 則表示消息沒有被正確處理, 該消息會被重新發送.
設置 ack 模式
spring:
rabbitmq:
listener:
simple:
# acknowledgeMode: NONE # rabbitmq的自動確認
acknowledgeMode: AUTO # rabbitmq的手動確認, springboot會自動發送確認回執 (默認)
# acknowledgeMode: MANUAL # rabbitmq的手動確認, springboot不發送回執, 必須自己編碼發送回執
手動執行確認操作
如果設置爲 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);
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '.') {
Thread.sleep(1000);
}
}
// 手動發送確認回執
c.basicAck(tag, false);
}
抓取數量
工作模式中, 爲了合理地分發數據, 需要將 qos 設置成 1, 每次只接收一條消息, 處理完成後才接收下一條消息.
spring boot 中是通過 prefetch
屬性進行設置, 改屬性的默認值是 250.
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # qos=1, 默認250
發佈和訂閱模式
主程序
創建 FanoutExcnahge
實例, 封裝 fanout
類型交換機定義信息.
spring boot 的自動配置類會自動發現交換機實例, 並在 RabbitMQ 服務器中定義該交換機.
package cn.tedu.m3;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("logs");
}
}
生產者
生產者向指定的交換機 logs
發送數據.
不需要指定隊列名或路由鍵, 即使指定也無效, 因爲 fanout
交換機會向所有綁定的隊列發送數據, 而不是有選擇的發送.
package cn.tedu.m3;
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 交換機發送, 不指定隊列名或路由鍵
t.convertAndSend("logs","",s);
}
}
}
消費者
消費者需要執行以下操作:
- 定義隨機隊列(隨機命名,非持久,排他,自動刪除)
- 定義交換機(可以省略, 已在主程序中定義)
- 將隊列綁定到交換機
spring boot 通過註解完成以上操作:
@RabbitListener(bindings = @QueueBinding( //這裏進行綁定設置
value = @Queue, //這裏定義隨機隊列,默認屬性: 隨機命名,非持久,排他,自動刪除
exchange = @Exchange(name = "logs", declare = "false") //指定 logs 交換機,因爲主程序中已經定義,這裏不進行定義
))
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")))
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);
}
}
測試類
package cn.tedu.m3;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class PublishSubscribeTests {
@Autowired
Publisher publisher;
@Test
void test1() throws Exception {
publisher.send();
}
}
路由模式
與發佈和訂閱模式代碼類似, 只是做以下三點調整:
- 使用
direct
交換機 - 隊列和交換機綁定時, 設置綁定鍵
- 發送消息時, 指定路由鍵
主程序
主程序中使用 DirectExcnahge
對象封裝交換機信息, spring boot 自動配置類會自動發現這個對象, 並在 RabbitMQ 服務器上定義這個交換機.
package cn.tedu.m4;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public DirectExchange fanoutExchange() {
return new DirectExchange("direct_logs");
}
}
生產者
生產者向指定的交換機發送消息, 並指定路由鍵.
package cn.tedu.m4;
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 RouteSender {
@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);
}
}
}
消費者
消費者通過註解來定義隨機隊列, 綁定到交換機, 並指定綁定鍵:
@RabbitListener(bindings = @QueueBinding( // 這裏做綁定設置
value = @Queue, // 定義隊列, 隨機命名,非持久,排他,自動刪除
exchange = @Exchange(name = "direct_logs", declare = "false"), // 指定綁定的交換機,主程序中已經定義過隊列,這裏不進行定義
key = {"error","info","warning"} // 設置綁定鍵
))
package cn.tedu.m4;
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 RouteReceiver {
@RabbitListener(bindings = @QueueBinding(value = @Queue,exchange = @Exchange(name = "direct_logs", declare = "false"),key = {"error"}))
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);
}
}
測試類
package cn.tedu.m4;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RouteTests {
@Autowired
RouteSender sender;
@Test
void test1() throws Exception {
sender.send();
}
}
主題模式
主題模式不過是具有特殊規則的路由模式, 代碼與路由模式基本相同, 只做如下調整:
- 使用
topic
交換機 - 使用特殊的綁定鍵和路由鍵規則
主程序
package cn.tedu.m5;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public TopicExchange fanoutExchange() {
return new TopicExchange("topic_logs");
}
}
生產者
package cn.tedu.m5;
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 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);
}
}
}
消費者
package cn.tedu.m5;
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 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);
}
}
測試類
package cn.tedu.m5;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TopicTests {
@Autowired
TopicSender sender;
@Test
void test1() throws Exception {
sender.send();
}
}