一、准备工作

创建springboot项目,pom.xml文件添加依赖

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>

applications配置文件添加rabbitmq配置连接信息

spring:
  rabbitmq:
    host: 1.13.9.40
    port: 5672
    username: admin
    password: admin
    # virtual-host:

主程序
删除自动创建的主程序
我们为每种模式创建一个包,在每个包中创建各自的主程序,单独测试.

二、简单模式

1 主程序

Spring提供的Queue类,是队列的封装对象,它封装了队列的参数信息,RabbitMQ的自动配置类,会发现这些Queue实例,并在RabbitMQ服务器中定义这些队列.

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
    /**
     * 注入生产者,调用生产者的run()方法发送消息
     * 消费者是自动创建实例,自动注册成为消息者,自动启动开始接收消息,不需要手动调用
     */
    @Autowired
    private Producer p;
    @Bean
    public Queue helloworldQueue(){
        //把队列的参数封装到Queue实现,在自动配置类中,会连接服务器,使用这里的参数来创建队列
        return new Queue("helloworld",false);//非持久队列,默认为true
    }
    /**
     * spring的执行流程
     * 包扫描创建实例--->依赖注入--->@PostConstruct-->继续执行后续流程
     */
    @PostConstruct
    public void test(){
        p.send();
    }
}

2 生产者

AmqpTemplate是rabbitmq客户端API的一个封装工具,提供了简便的方法来执行消息操作.AmqpTemplate由自动配置类自动创建

@Component
public class Producer {
    @Autowired
    private AmqpTemplate t;//发送消息的封装工具
    //自定义的方法,需要自己手动调用
    public void send(){
        //向helloworld队列发送消息,转换并发送,先转成byte[]数组在发送
        t.convertAndSend("helloworld","Hello World");
    }
}

3 消费者

通过**@RabbitListener从指定的队列接收消息,使用@RebbitHandler**注解的方法来处理消息,

方式一

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

方式二
@RabbitListener 注解中也可以直接定义队列:
@RabbitListener(queuesToDeclare = @Queue(name = “helloworld”,durable = “false”))

@Component
public class Consumer {
    @RabbitListener(queues = "helloworld")
    public void receive(String msg){
        System.out.println("收到消息: "+msg);
    }
}

三、工作模式

1 主程序

在主程序中创建名为task_queue的持久队列

/**
 * 工作模式解决两个问题
 * 1.合理分发
 *     - 手动ack spring封装的api,默认就是手动ack,spring会自动执行发送回执的操作
 *     - qos=1 yml添加prefetch=1,spring默认是250
 * 2.持久化
 *    - 队列持久化 new Queue(队列名,true)
 *    - 消息持久化 spring封装的api默认把消息设置成持久消息
 */
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
    /**
     * 注入生产者,调用生产者的run()方法发送消息
     * 消费者是自动创建实例,自动注册成为消息者,自动启动开始接收消息,不需要手动调用
     */
    @Autowired
    private Producer p;
    @Bean
    public Queue taskQueue(){
        //把队列的参数封装到Queue实现,在自动配置类中,会连接服务器,使用这里的参数来创建队列
        return new Queue("task_queue",true);
    }
    /**
     * spring的执行流程
     * 包扫描创建实例--->依赖注入--->@PostConstruct-->继续执行后续流程
     */
    @PostConstruct
    public void test(){
        new Thread(()->{p.send();}).start();
    }
}

2 生产者

@Component
public class Producer {
    @Autowired
    private AmqpTemplate t;//发送消息的封装工具

    //自定义的方法,需要自己手动调用
    public void send() {
        while (true) {
            System.out.print("输入消息: ");
            String s =new Scanner(System.in).nextLine() ;
            //spring 默认将消息的 DeliveryMode 设置为 PERSISTENT 持久化,转换并发送,先转成byte[]数组在发送
            t.convertAndSend("task_queue", s);
        }
    }
}
//如果需要设置消息为非持久化,可以取得消息的属性对象,修改它的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;
		}
	});

3 消费者

@Component
public class Consumer {
    //每个@RabbitListener都会启动一个信息的消费者
    @RabbitListener(queues = "task_queue")
    public void receive1(String msg){
        System.out.println("消费者1收到消息: "+msg);
    }

    @RabbitListener(queues = "task_queue")
    public void receive2(String msg){
        System.out.println("消费者2收到消息: "+msg);
    }
}

4 ack模式

在 spring boot 中提供了三种确认模式:

  1. NONE - 使用rabbitmq的自动确认
  2. AUTO - 使用rabbitmq的手动确认, springboot会自动发送确认回执 (默认)
  3. 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);
	}

5 抓取数量

工作模式中, 为了合理地分发数据, 需要将 qos 设置成 1, 每次只接收一条消息, 处理完成后才接收下一条消息.
spring boot 中是通过 prefetch 属性进行设置

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 #默认250

四、发布和订阅模式

1 主程序

创建 FanoutExcnahge 实例, 封装 fanout 类型交换机定义信息.
spring boot 的自动配置类会自动发现交换机实例, 并在 RabbitMQ 服务器中定义该交换机.

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
    /**
     * 注入生产者,调用生产者的run()方法发送消息
     * 消费者是自动创建实例,自动注册成为消息者,自动启动开始接收消息,不需要手动调用
     */
    @Autowired
    private Producer p;
    @Bean
     FanoutExchange logsExchange(){
        return new FanoutExchange("logs",false,false);//非持久,不自动删除
    }

    /**
     * spring的执行流程
     * 包扫描创建实例--->依赖注入--->@PostConstruct-->继续执行后续流程
     */
    @PostConstruct
    public void test(){
        new Thread(()->{p.send();}).start();
    }
}

2 生产者

生产者向指定的交换机 logs 发送数据。不需要指定队列名或路由键, 即使指定也无效, 因为 fanout 交换机会向所有绑定的队列发送数据, 而不是有选择的发送.

@Component
public class Producer {

    @Autowired
    private AmqpTemplate t;//发送消息的封装工具
    //自定义的方法,需要自己手动调用
    public void send() {
        while (true) {
            System.out.print("输入消息: ");
            String s =new Scanner(System.in).nextLine() ;
            //转换并发送,先转成byte[]数组在发送
            /**
             * 第一个参数,向指定的交换机发送消息
             * 第二个参数队列名,不指定队列,由消费者向交换机绑定队列,对于fanout交换机无效
             */
            t.convertAndSend("logs","", s);
        }
    }
}

3 消费者

/**
 - 消费者需要执行以下操作:
 - 定义随机队列(随机命名,非持久,排他,自动删除)
 - 定义交换机(可以省略, 已在主程序中定义)
 - 将队列绑定到交换机
 */
@Component
public class Consumer {

    //每个@RabbitListener都会启动一个新的消费者
    @RabbitListener(bindings = @QueueBinding(这里进行绑定设置
            value = @Queue,//队列,默认属性,随机命名,非持久,独占,排他。自动删除
            exchange = @Exchange(name = "logs",declare = "false")///指定logs交换机,不创建交换机,因为主程序已经定义,只引用已经存在的交换机
    ))
    public void receive1(String msg){
        System.out.println("消费者1收到消息: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,//队列,随机命名,非持久,独占,自动删除
            exchange = @Exchange(name = "logs",declare = "false")///交换机,不创建交换机,只引用已经存在的交换机
    ))
    public void receive2(String msg){
        System.out.println("消费者2收到消息: "+msg);
    }
}

五、路由模式

与发布和订阅模式代码类似, 只是做以下三点调整:

  • 使用 direct 交换机
  • 队列和交换机绑定时, 设置绑定键
  • 发送消息时, 指定路由键

1 主程序

主程序中使用 DirectExcnahge 对象封装交换机信息, spring boot 自动配置类会自动发现这个对象, 并在 RabbitMQ 服务器上定义这个交换机.

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
    /**
     * 注入生产者,调用生产者的run()方法发送消息
     * 消费者是自动创建实例,自动注册成为消息者,自动启动开始接收消息,不需要手动调用
     */
    @Autowired
    private Producer p;
    @Bean
    DirectExchange logsExchange(){
        return new DirectExchange("direct_logs",false,false);//非持久,不自动删除
    }
    /**
     * spring的执行流程
     * 包扫描创建实例--->依赖注入--->@PostConstruct-->继续执行后续流程
     */
    @PostConstruct
    public void test(){
        new Thread(()->{p.send();}).start();
    }
}

2 生产者

生产者向指定的交换机发送消息, 并指定路由键.

@Component
public class Producer {
    @Autowired
    private AmqpTemplate t;//发送消息的封装工具
    //自定义的方法,需要自己手动调用
    public void send() {
        while (true) {
            System.out.print("输入消息: ");
            String s =new Scanner(System.in).nextLine() ;
            System.out.print("输入路由键: ");
            String k=new Scanner(System.in).nextLine() ;
            /**
             * 第一个参数,向指定的交换机发送消息
             * 第二个参数队列名,不指定队列,由消费者向交换机绑定队列,对于fanout交换机无效
             */
            t.convertAndSend("direct_logs",k, s);
        }
    }
}

3 消费者

消费者通过注解来定义随机队列, 绑定到交换机, 并指定绑定键:

@Component
public class Consumer {

    //每个@RabbitListener都会启动一个新的消费者
    @RabbitListener(bindings = @QueueBinding(//这里做绑定设置
            value = @Queue,//队列,随机命名,非持久,独占,自动删除
            exchange = @Exchange(name = "direct_logs",declare = "false"), 指定绑定的交换机,主程序中已经定义过队列,这里不进行定义
            key = {"error"}//设置路由键
    ))
    public void receive1(String msg){
        System.out.println("消费者1收到消息: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,//队列,随机命名,非持久,独占,自动删除
            exchange = @Exchange(name = "direct_logs",declare = "false"),///交换机,不创建交换机,只引用已经存在的交换机
            key = {"info","error","warning"}
    ))
    public void receive2(String msg){
        System.out.println("消费者2收到消息: "+msg);
    }
}

六、主题模式

主题模式不过是具有特殊规则的路由模式, 代码与路由模式基本相同, 只做如下调整:

  • 使用 topic 交换机
  • 使用特殊的绑定键和路由键规则

1 主程序

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class,args);
    }
    /**
     * 注入生产者,调用生产者的run()方法发送消息
     * 消费者是自动创建实例,自动注册成为消息者,自动启动开始接收消息,不需要手动调用
     */
    @Autowired
    private Producer p;
    @Bean
    TopicExchange logsExchange(){
        return new TopicExchange("top_logs",false,false);//非持久,不自动删除
    }
    /**
     * spring的执行流程
     * 包扫描创建实例--->依赖注入--->@PostConstruct-->继续执行后续流程
     */
    @PostConstruct
    public void test(){
        new Thread(()->{p.send();}).start();
    }
}

2 生产者

@Component
public class Producer {
    @Autowired
    private AmqpTemplate t;//发送消息的封装工具
    //自定义的方法,需要自己手动调用
    public void send() {
        while (true) {
            System.out.print("输入消息: ");
            String s =new Scanner(System.in).nextLine() ;
            System.out.print("输入路由键: ");
            String k=new Scanner(System.in).nextLine() ;
            //转换并发送,先转成byte[]数组在发送
            /**
             * 第一个参数,向指定的交换机发送消息
             * 第二个参数队列名,不指定队列,由消费者向交换机绑定队列,对于fanout交换机无效
             */
            t.convertAndSend("top_logs",k, s);
        }
    }
}

3 消费者

@Component
public class Consumer {
    //每个@RabbitListener都会启动一个新的消费者
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,//队列,随机命名,非持久,独占,自动删除
            exchange = @Exchange(name = "top_logs",declare = "false"),///交换机,不创建交换机,只引用已经存在的交换机
            key = {"*.orange.*"}
    ))
    public void receive1(String msg){
        System.out.println("消费者1收到消息: "+msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue,//队列,随机命名,非持久,独占,自动删除
            exchange = @Exchange(name = "top_logs",declare = "false"),///交换机,不创建交换机,只引用已经存在的交换机
            key = {"*.*.rabbit","lazy.#"}
    ))
    public void receive2(String msg){
        System.out.println("消费者2收到消息: "+msg);
    }
}

七、RPC异步调用

1 主程序

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
    /**
     * 发送调用信息的队列: rpc_queue
     */
    @Bean
    public Queue sendQueue() {
        return new Queue("rpc_queue",false);
    }
    /**
     * 返回结果的队列: 随机命名
     */
    @Bean
    public Queue rndQueue() {
        return new Queue(UUID.randomUUID().toString(), false);
    }
}

2 服务端

从rpc_queue接收调用数据, 执行运算求斐波那契数,并返回计算结果.
@Rabbitlistener注解对于具有返回值的方法:

  • 会自动获取 replyTo 属性
  • 自动获取 correlationId 属性
  • 向 replyTo 属性指定的队列发送计算结果, 并携带 correlationId 属性
@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);
    }
}

3 客户端

使用 SPEL 表达式获取随机队列名: “#{rndQueue.name}
发送调用数据时, 携带随机队列名和correlationId
从随机队列接收调用结果, 并获取correlationId

@Component
public class RpcClient {
    @Autowired
    AmqpTemplate t;

    @Value("#{rndQueue.name}")
    String rndQueue;

    public void send(int n) {
        // 发送调用信息时, 通过前置消息处理器, 对消息属性进行设置, 添加返回队列名和关联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;
            }
        });
    }
    //从随机队列接收计算结果
    @RabbitListener(queues = "#{rndQueue.name}")
    public void receive(long r, @Header(name= AmqpHeaders.CORRELATION_ID) String correlationId) {
        System.out.println("\n\n"+correlationId+" - 收到: "+r);
    }
}

测试类测试

@SpringBootTest
class TopicTests {
	@Autowired
	RpcClient client;

	@Test
	void test1() throws Exception {
		while (true) {
			System.out.print("求第几个斐波那契数: ");
			int n = new Scanner(System.in).nextInt();
			client.send(n);
		}
	}
}