Five stages of Java learning - rabbitmq springboot integration

Posted by dprichard on Sun, 19 Dec 2021 10:42:00 +0100

New project


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

main program

Delete automatically created main program

We create a package for each mode, create their own main program in each package, and test them separately

Simple mode

main program

The Queue class provided by Spring is the encapsulation object of the Queue, which encapsulates the parameter information of the Queue

The automatic configuration class of RabbitMQ will discover these Queue instances and define these queues in the RabbitMQ server

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() {
		/*
		 * The following forms can be used: 
		 * new Queue("helloworld") - Persistent, non exclusive, non automatic deletion
		 * new Queue("helloworld",false,false,false,null)
		 */
		return new Queue("helloworld",false);
	}
}

producer

AmqpTemplate is an encapsulation tool of rabbitmq client API, which provides a simple way to perform message operations

AmqpTemplate is automatically created by the auto configuration class

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() {
		// Here, a message is sent to the helloworld queue
		t.convertAndSend("helloworld", "Hello world!! "+System.currentTimeMillis());
		System.out.println("Message sent");
	}
}

consumer

Receive messages from the specified queue through @ RabbitListener

Use the @ RebbitHandler annotation to process messages

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("received: "+msg);
	}
}

Another form can be used here:

@Component
public class SimpleReceiver {
	@RabbitListener(queues = "helloworld")
	public void receive(String msg) {
		System.out.println("received: "+msg);
	}
}

In addition, queues can also be defined directly in the @ RabbitListener annotation:

	@RabbitListener(queuesToDeclare = @Queue(name = "helloworld",durable = "false"))

Test class

Create a test class in the directory where the test code is stored

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("[Press enter to end]");
		new Scanner(System.in).nextLine();
	}
}

Working mode

main program

Create a task in the main program_ Persistent queue for 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() {
		// The queue parameters created by this construction method are: persistent, non exclusive, and non automatic deletion
		return new Queue("task_queue");
	}
}

producer

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("input:");
			String s = new Scanner(System.in).nextLine();		
			//spring sets the DeliveryMode of the message to PERSISTENT by default,
			t.convertAndSend("task_queue", s);
		}
	}
}

In the rabbitmq api encapsulated by spring boot, the message sent is a persistent message by default
If you want to send non persistent messages, you need to make the following settings when sending messages:

  • Use MessagePostProcessor preprocessor parameters
  • Gets the property object of the message from the message
  • Set DeliveryMode to non persistent in the property
	//If you need to set the message to be non persistent, you can get the property object of the message and modify its deliveryMode property
	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;
		}
	});

consumer

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 - received: "+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 - received: "+s);
		for (int i = 0; i < s.length(); i++) {
			if (s.charAt(i) == '.') {
				Thread.sleep(1000);
			}
		}
	}
}

Test class

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 mode

Three confirmation modes are provided in spring boot:

  • NONE - automatic confirmation using rabbitmq
  • AUTO - using rabbitmq's manual confirmation, springboot will automatically send a confirmation receipt (default)
  • MANUAL - MANUAL confirmation using rabbitmq and must be performed manually

In the default AUTO mode, if the method of processing the message throws an exception, it means that the message has not been processed correctly, and the message will be sent again

Set ack mode

spring:
  rabbitmq:
    listener:
      simple:
        # acknowledgeMode: NONE # Automatic confirmation of rabbitmq
        acknowledgeMode: AUTO # For the manual confirmation of rabbitmq, springboot will automatically send a confirmation receipt (default)
        # acknowledgeMode: MANUAL # For the manual confirmation of rabbitmq, springboot does not send the receipt, but must encode and send the receipt by itself

Perform confirmation manually

If it is set to MANUAL mode, the confirmation operation must be performed manually

	@RabbitListener(queues="task_queue")
	public void receive1(String s, Channel c, @Header(name=AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
		System.out.println("receiver1 - received: "+s);
		for (int i = 0; i < s.length(); i++) {
			if (s.charAt(i) == '.') {
				Thread.sleep(1000);
			}
		}
		// Send confirmation receipt manually
		c.basicAck(tag, false);
	}

Grab quantity

In the working mode, in order to distribute data reasonably, qos needs to be set to 1, only one message is received at a time, and the next message is received after processing

spring boot is set through prefetch attribute. The default value of the modified attribute is 250

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # qos=1, default 250

Publish and subscribe mode

main program

Create a FanoutExcnahge instance to encapsulate the FanoutExcnahge type switch definition information

The auto configuration class of spring boot will automatically discover the switch instance and define the switch in the RabbitMQ server

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

producer

The producer sends data to the specified switch logs

There is no need to specify the queue name or routing key, even if it is specified, it is invalid, because the fanout switch will send data to all bound queues instead of selective sending

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("input:");
			String s = new Scanner(System.in).nextLine();
			// Specifies to send to the logs switch without specifying the queue name or routing key
			t.convertAndSend("logs","",s);
		}
	}
}

consumer

The consumer needs to do the following:

  1. Define random queues (randomly named, non persistent, exclusive, auto delete)
  2. Define the switch (it can be omitted and has been defined in the main program)
  3. Bind queue to switch

spring boot completes the above operations through annotations:

@RabbitListener(bindings = @QueueBinding( //Binding settings are performed here
	value = @Queue, //Random queues are defined here. The default attributes are random naming, non persistent, exclusive, and automatic deletion
	exchange = @Exchange(name = "logs", declare = "false") //The logs switch is specified because it has been defined in the main program, so it is not defined here
))
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 - received: "+s);
	}
	@RabbitListener(bindings = @QueueBinding(value = @Queue, exchange = @Exchange(name = "logs", declare = "false")))
	public void receive2(String s) throws Exception {
		System.out.println("receiver2 - received: "+s);
	}
}

Test class

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

Routing mode

Similar to the publish and subscribe mode code, only the following three adjustments are made:

  1. Using direct switch
  2. When the queue is bound to the switch, set the binding key
  3. When sending a message, specify the routing key

main program

In the main program, the DirectExcnahge object is used to encapsulate the switch information. The spring boot automatic configuration class will automatically discover this object and define the switch on the RabbitMQ server

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

producer

The producer sends a message to the specified switch and specifies the routing key

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("Input message:");
			String s = new Scanner(System.in).nextLine();
			System.out.print("Enter routing key:");
			String key = new Scanner(System.in).nextLine();
			// The second parameter specifies the routing key
			t.convertAndSend("direct_logs",key,s);
		}
	}
}

consumer

The consumer defines the random queue through annotation, binds it to the switch, and specifies the binding key:

@RabbitListener(bindings = @QueueBinding( // Binding settings are made here
	value = @Queue, // Define queue, randomly named, non persistent, exclusive, auto delete
	exchange = @Exchange(name = "direct_logs", declare = "false"), // Specify the bound switch. The queue has been defined in the main program. It is not defined here
	key = {"error","info","warning"} // Set binding key
))
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 - received: "+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 - received: "+s);
	}
}

Test class

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

Theme mode

The topic mode is just a routing mode with special rules. The code is basically the same as the routing mode, with the following adjustments:

  1. Using topic switch
  2. Use special binding and routing key rules

main program

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

producer

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("Input message:");
			String s = new Scanner(System.in).nextLine();
			System.out.print("Enter routing key:");
			String key = new Scanner(System.in).nextLine();		
			t.convertAndSend("topic_logs",key,s);
		}
	}
}

consumer

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 - received: "+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 - received: "+s);
	}
}

Test class

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

RPC asynchronous call

main program

Two queues are defined in the main program

  • Queue to send call information: rpc_queue
  • Queue that returns results: randomly named
package cn.tedu.m6;
import java.util.UUID;
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 sendQueue() {
		return new Queue("rpc_queue",false);
	}
	@Bean
	public Queue rndQueue() {
		return new Queue(UUID.randomUUID().toString(), false);
	}
}

Server

From rpc_queue receives the call data, performs the operation, calculates the Fibonacci number, and returns the calculation result
@Rabbitlistener annotation for methods with return values:

  • The replyTo attribute is automatically obtained
  • Get correlationId property automatically
  • Send the calculation result to the queue specified by replyTo attribute, and carry the correlationId attribute
package cn.tedu.m6;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class RpcServer {
	@RabbitListener(queues = "rpc_queue")
	public long getFbnq(int n) {
		return f(n);
}
	private long f(int n) {
		if (n==1 || n==2) {
			return 1;
		}
		return f(n-1) + f(n-2);
	}
}

client

Get random queue name using SPEL expression: '#{rndQueue.name}'

When sending call data, the random queue name and correlationId are carried

Receive the call result from the random queue and get the correlationId

package cn.tedu.m6;
import java.util.UUID;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
@Component
public class RpcClient {
	@Autowired
	AmqpTemplate t;	
	@Value("#{rndQueue.name}")
	String rndQueue;	
	public void send(int n) {
		// When sending the call information, set the message properties through the pre message processor, and add the return queue name and association id
		t.convertAndSend("rpc_queue", (Object)n, new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				MessageProperties p = message.getMessageProperties();
				p.setReplyTo(rndQueue);
				p.setCorrelationId(UUID.randomUUID().toString());
				return message;
			}
		});
	}	
	//Receive calculation results from random queue
	@RabbitListener(queues = "#{rndQueue.name}")
	public void receive(long r, @Header(name=AmqpHeaders.CORRELATION_ID) String correlationId) {
		System.out.println("\n\n"+correlationId+" - received: "+r);
	}
}

Test class

package cn.tedu.m6;
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 TopicTests {
	@Autowired
	RpcClient client;
	@Test
	void test1() throws Exception {
		while (true) {
			System.out.print("Find the Fibonacci number: ");
			int n = new Scanner(System.in).nextInt();
			client.send(n);
		}
	}
}

Topics: Java RabbitMQ Spring Boot message queue