RabbitMQ - Spring boot 整合

新建項目

新建項目
選擇依賴

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);
		}
	}
}

消費者

消費者需要執行以下操作:

  1. 定義隨機隊列(隨機命名,非持久,排他,自動刪除)
  2. 定義交換機(可以省略, 已在主程序中定義)
  3. 將隊列綁定到交換機

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();
	}
}

在這裏插入圖片描述

路由模式

與發佈和訂閱模式代碼類似, 只是做以下三點調整:

  1. 使用 direct 交換機
  2. 隊列和交換機綁定時, 設置綁定鍵
  3. 發送消息時, 指定路由鍵

主程序

主程序中使用 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();
	}
}

在這裏插入圖片描述

主題模式

主題模式不過是具有特殊規則的路由模式, 代碼與路由模式基本相同, 只做如下調整:

  1. 使用 topic 交換機
  2. 使用特殊的綁定鍵和路由鍵規則

主程序

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();
	}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章