SpringBoot整合RabbitMq

444 阅读4分钟

SpringBoot整合RabbitMQ

1.引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 引入AMQP场景,RabbitAutoConfiguration就会自动生效
  2. RabbitAutoConfiguration会给容器中自动配置RabbitTemplate、AmqpAdmin、CachingConnectionFactory、RabbitMessagingTemplate等组件

image.png

2.添加配置文件

spring:
  rabbitmq:
    host: 47.106.xxx.xxx
    port: 5672
    virtual-host: / #虚拟主机,默认/

image.png

3.开启RabbitMQ功能 @EnableRabbit

@EnableRabbit
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}

4.创建队列、交换机、绑定关系

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderApplicationTests {

    @Autowired
    AmqpAdmin amqpAdmin;

    /**
     * 创建队列
     */
    @Test
    public void createQueue() {
        /**
         * Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
         * name : 队列名称
         * durable : 是否持久化
         * exclusive : 是否排它,如果有一条链接使用了这个队列,其他链接就无法使用
         * autoDelete : 是否自动删除
         */
        Queue queue = new Queue("hello-java-queue", true, false, false, null);
        amqpAdmin.declareQueue(queue);
    }

    /**
     * 创建交换机
     */
    @Test
    public void createExchange() {
        /**
         * DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments)
         * name : 交换机名称
         * durable : 是否持久化
         * autoDelete : 是否自动删除
         */
        Exchange exchange = new DirectExchange("hello-java-exchange", true, false, null);
        amqpAdmin.declareExchange(exchange);
    }

    /**
     * 创建绑定关系
     */
    @Test
    public void createBinging() {
        /**
         * Binding(String destination, Binding.DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments)
         * destination : 目的地(队列名称或者交换机名称)
         * DestinationType : 目的地类型
         * exchange : 交换机
         * routingKey : 路由键
         * 将exchange指定的交换机和destination目的地进行绑定,使用routingKey作为路由键
         */
        Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null);
        amqpAdmin.declareBinding(binding);
    }
    
}

5.生产端发送消息

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     */
    @Test
    public void sendMessage() {
        /**
         * convertAndSend(String exchange, String routingKey, Object object)
         * 1.如果发送的消息是一个对象,我们会使用序列化机制,将对象写出去。对象必须实现Serializable接口
         * 2.如果发送的对象要是一个json格式,修改消息发送类型MessageConverter
         * 3.new CorrelationData(UUID.randomUUID().toString()) 指定发送消息的唯一id
         */
        //String msg = "Hello word!";
        OrderEntity msg = new OrderEntity();
        msg.setOrderSn(UUID.randomUUID().toString());
        msg.setCommentTime(new Date());
        rabbitTemplate.convertAndSend("hello-java-exchange", "hello.java", msg, new CorrelationData(UUID.randomUUID().toString()));
    }
}

6.创建RabbitConfig

  1. 如果发送的对象要是一个json格式,修改消息发送类型MessageConverter
@Configuration
public class MyRabbitConfig {

    /**
     * 使用JSON序列化机制,进行消息转换
     *
     * @return
     */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

7.消费的消费消息

@RabbitListener 标注类或者方法上(监听哪些队列)
@RabbitHandler 标注在方法上(重载方法进行区分不同的消息类型)

/**
 * queues 需要监听的队列
 */
@Component
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = {"hello-java-queue"})
public class RabbitListener {

    /**
     * org.springframework.amqp.core.Message
     * 参数可以写以下类型
     * 1.Message message原生消息类型 头+体
     * 2.T<发送的消息类型> OrderEntity order
     * 3.Channel channel : 当前传输数据的通道
     * <p>
     * Queue 可以有很多人监听,只要收到消息 队列就会删除消息,而且只能有一个人收到消息
     * 场景:
     * 1.订单服务启动多个服务。同一个消息,只能有一个客户端收到消息
     * 2.只有一个消息完全处理完,方法运行结束,我能就可以接收到下一个消息
     *
     * @param message
     */
    @RabbitHandler
    public void receiverMessage(Message message, OrderEntity order, Channel channel) {
        System.out.println("Message===>" + message + "OrderEntity==>" + order);
    }
}

8.消息可靠投递

添加配置文件信息

spring:
  rabbitmq:
    host: 47.106.130.110
    port: 5672
    virtual-host: /
    publisher-confirms: true #开启发送端消息确认
    publisher-returns: true #开启发送端消息抵达队列确认
    template:
      mandatory: true #只要抵达队列,以异步发送优先回调我们这个returnConfirm
    listener:
      simple:
        acknowledge-mode: manual #设置消费端手动确认消息

8.1保证消费端发送消息到broker端

服务端broker收到消息就回调

@Configuration
public class MyRabbitConfig {

    @Autowired
    RabbitTemplate rabbitTemplate;
    
    /**
     * 定制rabbitTemplate
     * @PostConstruct MyRabbitConfig对象创建完成,执行这个方法
     */
    @PostConstruct
    public void initRabbitTemplate() {
        //设置确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 只要消息抵达Broker就ack=ture
             * @param correlationData 当前消息的唯一关联数据(消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("correlationData===>" + correlationData + "ack===>" + ack + "cause===>" + cause);
                if(!ack){
                     System.out.println("发送失败....");
                }
            }
        });
    }
}

8.2. 保证服务端正确发送消息到队列

消息正确抵达队列进行回调

@Configuration
public class MyRabbitConfig {

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 定制rabbitTemplate
     * @PostConstruct MyRabbitConfig对象创建完成,执行这个方法
     */
    @PostConstruct
    public void initRabbitTemplate() {
        //设置生产者发送消息确认回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            /**
             * 只要消息抵达Broker就ack=ture
             * @param correlationData 当前消息的唯一关联数据(消息的唯一id)
             * @param ack 消息是否成功收到
             * @param cause 失败原因
             */
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("correlationData===>" + correlationData + "ack===>" + ack + "cause===>" + cause);
                if(!ack){
                     System.out.println("发送失败....");
                }
            }
        });
    
        //设置消息抵达队列的回调,发送消息到队列失败回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             * 只要消息没有投递给指定队列,就触发这个失败回调
             * @param message 投递失败的消息详细信息
             * @param replyCode 回复的状态码
             * @param replyText 回复的文本内容
             * @param exchange 当前这个消息发送给哪个交换机
             * @param routingKey 当前这个消息用哪个路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                System.out.println("Fail Massage==>" + message + "replyCode==>" + replyCode + "replyText==>" + replyText + "exchange==>" + exchange + "routingKey==>" + routingKey);
            }
        });
    }
}

8.3. 保证消费端收到消息

/**
 * queues 需要监听的队列
 */
@Component
@org.springframework.amqp.rabbit.annotation.RabbitListener(queues = {"hello-java-queue"})
public class RabbitListener {

    /**
     * @param message
     */
    @RabbitHandler
    public void receiverMessage(Message message, OrderEntity order, Channel channel) {
        System.out.println("Message===>" + message + "OrderEntity==>" + order);
        MessageProperties messageProperties = message.getMessageProperties();
        long deliveryTag = messageProperties.getDeliveryTag();
        try {
            /**
             * deliveryTag 消息发送的序号
             * multiple 是否批量签收 false非批量签收,true批量签收
             */
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                /**
                 * deliveryTag 消息发送的序号
                 * multiple 是否批量签收 false非批量签收,true批量签收
                 * requeue 是否重回队列 true重回队列 false不重回队列,丢弃
                 */
                channel.basicNack(deliveryTag,false,false);
            } catch (IOException ex) {
            }
        }
    }
}