第六节 SpringBoot集成RabbitMQ综合运用(SSM框架集成RabbitMQ)

先展示当前项目的效果

        RabbitMQ一般不会单独使用。今天分享的是如何在基于SpringBoot的SSM框架中 集成RabbitMQ。

        点击下方图片,可以查看清晰效果图

核心配置

        (1)队列、Exchanger路由器交换机、路由键的名字由SpringBoot的配置文件维护。使用注解注入配置中的值。@Value("${queue.name")

        (2)集成RabbitMQ的application.yml的配置。可配置RabbitMQ的服务器的地址,接收端每次获取到的消息的数量等。

        (3)使用@Configuration来创建队列、路由器,并设置它们的绑定关系。定义消息传输格式为JSON。

        (4)创建发送端,注入进RabbitTemplate这个类,用于发消息。 

        (5)最后创建接收端,用于接收消息。根据配置,手动确认消息或者自动确认消息给RabbitMQ服务器。

一、队列、交换机的名字

        在application.yml中配置队列、路由器、路由键的名字。

#自定义参数
defineProps:
    rabbit: #MQ队列名称
        direct:
            exchange: local::mq06:exchange:e01
            routing.key:
                beauty: mq06::routeKey_love_beauty
                stock: mq06::routeKey_love_stock
                food: mq06::routeKey_love_food
            queue:
                queue01: local::mq06:queue:q01
                queue02: local::mq06:queue:q02

二、配置RabbitMQ服务器地址

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/basessm
        driverClassName: com.mysql.jdbc.Driver
        password: 3333
        username: root

    rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest
        virtual-host: /
        publisher-confirms: false #是否开启发送确认
        publisher-returns: true #是否开启发送失败退回
        listener:
            #SimpleMessageListenerContainer专门用于配置并发量
            simple:
                concurrency: 10 #消费者数量
                max-concurrency: 10 #最大消费者数量
                prefetch: 1 #限流(消费者每次从队列获取的消息数量)
                auto-startup: true  #启动时自动启动容器
                acknowledge-mode: manual #开启ACK手动确认模式

三、创建队列路由器及其绑定

package com.safesoft.ssm.rabbitconfig;

import lombok.Getter;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.transaction.RabbitTransactionManager;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author jay.zhou
 * @date 2019/4/25
 * @time 10:27
 */
@Configuration
@Getter
public class RabbitMQConfig {

    /**
     * 交换机名称
     */
    @Value("${defineProps.rabbit.direct.exchange}")
    private String directExchange;

    /**
     * 队列一名称,只对美女感兴趣
     */
    @Value("${defineProps.rabbit.direct.queue.queue01}")
    private String queue01;

    /**
     * 队列二名称,对股票和美食感兴趣
     */
    @Value("${defineProps.rabbit.direct.queue.queue02}")
    private String queue02;


    /**
     * 路由键(美女)
     */
    @Value("${defineProps.rabbit.direct.routing.key.beauty}")
    private String beautyRoutingKey;

    /**
     * 路由键(美食)
     */
    @Value("${defineProps.rabbit.direct.routing.key.food}")
    private String foodRoutingKey;

    /**
     * 路由键(股票)
     */
    @Value("${defineProps.rabbit.direct.routing.key.stock}")
    private String stockRoutingKey;

    /**
     * 定义交换器
     * 三个参数解释如下
     * name:交换机名称
     * durable:是否持久化,true表示交换机会被写入磁盘,即使RabbitMQ服务器宕机,也能恢复此交换机
     * autoDelete:表示消息交换机没有在使用时将被自动删除 默认是false
     *
     * @return DirectExchange
     */
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(directExchange, true, false);
    }


    /**
     * 定义队列01
     * 第一个参数是queue:要创建的队列名
     * 第二个参数是durable:是否持久化。如果为true,可以在RabbitMQ崩溃后恢复队列
     * 第三个参数是exclusive:true表示一个队列只能被一个消费者占有并消费
     * 第四个参数是autoDelete:true表示服务器不在使用这个队列是会自动删除它
     * 第五个参数是arguments:其它参数
     */
    @Bean
    public Queue queue01() {
        return new Queue(queue01, true, false, false, null);
    }

    /**
     * 定义队列02
     * 参数参考队列01
     */
    @Bean
    public Queue queue02() {
        return new Queue(queue02, true, false, false, null);
    }


    /**
     * 绑定-将队列绑定到路由器上,队列告诉路由器它感兴趣的话题(路由键)
     *
     * @param queue01        队列01
     * @param directExchange 交换器
     * @return Binding
     */
    @Bean
    public Binding queue01Binding(Queue queue01, DirectExchange directExchange) {
        //队列一绑定到路由器上,并告诉路由器,关于美女的消息,发送给它
        return BindingBuilder.bind(queue01).to(directExchange).with(beautyRoutingKey);
    }

    /**
     * 绑定-将队列绑定到路由器上,队列告诉路由器它感兴趣的话题(路由键)
     *
     * @param queue02        队列02
     * @param directExchange 交换器
     * @return Binding
     */
    @Bean
    public Binding queue02FoodBinding(Queue queue02, DirectExchange directExchange) {
        //队列二绑定到路由器上,并告诉路由器,关于美食的消息,发送给它
        return BindingBuilder.bind(queue02).to(directExchange).with(foodRoutingKey);
    }

    @Bean
    public Binding queue02StockBinding(Queue queue02, DirectExchange directExchange) {
        //队列二绑定到路由器上,并告诉路由器,关于股票的消息,发送给它
        return BindingBuilder.bind(queue02).to(directExchange).with(stockRoutingKey);
    }

    /**
     * 定义消息转换实例 ,转化成 JSON传输
     *
     * @return Jackson2JsonMessageConverter
     */
    @Bean
    public MessageConverter integrationEventMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    /**
     * 配置启用rabbitmq事务
     *
     * @param connectionFactory connectionFactory
     * @return RabbitTransactionManager
     */
    @Bean
    public RabbitTransactionManager rabbitTransactionManager(CachingConnectionFactory connectionFactory) {
        return new RabbitTransactionManager(connectionFactory);
    }
}

四、创建发送端,注入进RabbitTemplate

package com.safesoft.ssm.rabbitMQ;

import com.safesoft.ssm.rabbitconfig.RabbitMQConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;

/**
 * @author jay.zhou
 * @date 2019/4/25
 * @time 10:54
 */
@Component
public class RabbitMQSender implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQSender.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @PostConstruct
    private void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
        rabbitTemplate.setChannelTransacted(true);
    }


    @Transactional
    public Boolean sendMessageToQueue01AboutBeauty(Object msg) {
        rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getBeautyRoutingKey(), msg);
        return Boolean.TRUE;
    }

    @Transactional
    public Boolean sendMessageToQueue02AboutFood(Object msg) {
        rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getStockRoutingKey(), msg);
        return Boolean.TRUE;
    }

    @Transactional
    public Boolean sendMessageToQueue02AboutStock(Object msg) {
        rabbitTemplate.convertAndSend(rabbitMQConfig.getDirectExchange(), rabbitMQConfig.getFoodRoutingKey(), msg);
        return Boolean.TRUE;
    }


    /**
     * 消息发送到交换器Exchange后触发回调。
     * 使用该功能需要开启确认,spring-boot中配置如下:
     * spring.rabbitmq.publisher-confirms = true
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (ack) {
            LOGGER.info("消息已确认 cause:{} - {}", cause, correlationData);
        } else {
            LOGGER.error("消息未确认 cause:{} - {}", cause, correlationData);
        }
    }

    /**
     * 通过实现ReturnCallback接口,
     * 如果消息从交换器发送到对应队列失败时触发
     * 比如根据发送消息时指定的routingKey找不到队列时会触发
     * 使用该功能需要开启确认,spring-boot中配置如下:
     * spring.rabbitmq.publisher-returns = true
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        LOGGER.error("消息被退回:{}", message);
        LOGGER.error("消息使用的交换机:{}", exchange);
        LOGGER.error("消息使用的路由键:{}", routingKey);
        LOGGER.error("描述:{}", replyText);
    }
}

五、创建接收端

package com.safesoft.ssm.rabbitMQ;

import com.rabbitmq.client.Channel;
import com.safesoft.ssm.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author jay.zhou
 * @date 2019/4/25
 * @time 11:07
 * 监听队列01的消息,关于美女的消息,此队列都感兴趣
 */
@Component
@RabbitListener(queues = {"${defineProps.rabbit.direct.queue.queue01}"})
public class BeautyListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(BeautyListener.class);

    @RabbitHandler
    public void receiver(@Payload UserEntity msg, @Headers Channel channel, Message message) throws IOException {
        LOGGER.info("接收到的消息:{}", msg);
        try {
            // 确认消息已经消费成功

            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            LOGGER.info("确认处理消息:{}", msg);
        } catch (IOException e) {
            LOGGER.error("消费处理异常:{} - {}", msg, e);
            // 拒绝当前消息,并把消息返回原队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
}
package com.safesoft.ssm.rabbitMQ;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author jay.zhou
 * @date 2019/4/25
 * @time 11:07
 * 监听队列02的消息,关于美食和股票的消息,此消费者都感兴趣
 */
@Component
@RabbitListener(queues = {"${defineProps.rabbit.direct.queue.queue02}"})
public class FoodListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(FoodListener.class);

    @RabbitHandler
    public void receiver(@Payload String msgAboutFoodOrStock, @Headers Channel channel, Message message) throws IOException {
        LOGGER.info("接收到的消息:{}", msgAboutFoodOrStock);
        try {
            // 确认消息已经消费成功
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            LOGGER.info("确认处理消息:{}", msgAboutFoodOrStock);
        } catch (IOException e) {
            LOGGER.error("消费处理异常:{} - {}", msgAboutFoodOrStock, e);
            // 拒绝当前消息,并把消息返回原队列
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
}

源代码下载 

        源代码地址:https://github.com/hairdryre/Study_RabbitMQ

        下一篇:第七节 用户商城抢单并发实战

        阅读更多:从头开始学RabbimtMQ目录贴

 

 

posted @ 2022-07-17 12:14  小大宇  阅读(133)  评论(0编辑  收藏  举报