SpringBoot关于RabbitMQ的简单使用

89 阅读6分钟

消息模型

  • Publisher:生产者,不再发送消息到队列中,而是发给交换机
  • Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
  • Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
  • Consumer:消费者,与以前一样,订阅队列,没有变化

交换机类型

  • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
  • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
  • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
  • Headers:头匹配,基于MQ的消息头匹配,用的较少。

创建项目

1.引入MQ依赖 创建项目

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

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit-test</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>

2.定义消费者和生产者

2.1发送方

生产者用来发送消息 消费者用来接受消息 发送方可以发送到任意交换机 如TOPIC FANOUT DIRECT 具体实现到Test包下调用查看

import cn.dsxriiiii.publisher.BaseEvent;
import cn.dsxriiiii.publisher.publish.EventPublish;
import com.alibaba.fastjson2.JSONObject;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @ProjectName: rabbit-demo
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 13:40
 */
@SpringBootTest
public class DirectExchangeTest {

    private final Logger logger = LoggerFactory.getLogger(DirectExchangeTest.class);

    @Resource
    private AwardEvent awardEvent;

    @Resource
    private EventPublish eventPublish;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Test
    public void send_award_message(){
        for (int i = 0; i < 10; i++) {
            BaseEvent.BaseEventMessage<Long> longBaseEventMessage = awardEvent.buildBaseEventMessage((long) i);
            eventPublish.publish(awardEvent.topic(),longBaseEventMessage);
            logger.info("send_award_message 发送mq消息成功 for循环节点:{},发送消息为:{}",i, JSONObject.toJSONString(longBaseEventMessage));
        }
    }

    @Test
    public void testExchangeDirect_award(){
        String exchangeName = "dome.direct";
        String message = "hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~";
        rabbitTemplate.convertAndSend(exchangeName,"award",message);
    }

    @Test
    public void testExchangeDirect_rebate(){
        String exchangeName = "dome.direct";
        String message = "hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~";
        rabbitTemplate.convertAndSend(exchangeName,"rebate",message);
    }

    @Test
    public void testExchangeTopic_all(){
        String exchangeName = "dome.topic";
        String message = "hello,我是topic,dome下的都要接收到 (⊙o⊙)!";
        rabbitTemplate.convertAndSend(exchangeName,"dome.#",message);
    }

    @Test
    public void testExchangeTopic_rebate(){
        String exchangeName = "dome.topic";
        String message = "hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ";
        rabbitTemplate.convertAndSend(exchangeName,"#.rebate",message);
    }

    @Test
    public void testExchangeFanout(){
        String exchangeName = "dome.fanout";
        String message = "我是广播 都要接收到 (╯▔皿▔)╯";
        rabbitTemplate.convertAndSend(exchangeName,"",message);
    }
}

2.1.1定义消息模板

定义抽象方法BaseEvent 抽象出不同领域下对应不同MQ发送方

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @ProjectName: rabbit-demo
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 9:18
 */
@Data
public abstract class BaseEvent<T> {

    public abstract BaseEventMessage<T> buildBaseEventMessage();

    public abstract String topic();

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class BaseEventMessage<T>{
        private String id;
        private Date timm;
        private T data;
    }
}

直接选择继承即可

@Component
public class AwardEvent extends BaseEvent<Long>{

    @Value("${mq.server.Award}")
    private String topic;


    @Override
    public BaseEventMessage<Long> buildBaseEventMessage(Long data) {
        return BaseEventMessage.<Long>builder()
                .id(RandomStringUtils.randomNumeric(11))
                .time(new Date())
                .data(data)
                .build();
    }

    @Override
    public String topic() {
        return topic;
    }

}

如果自定义消息体 使用泛型即可

import cn.dsxriiiii.publisher.BaseEvent;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @ProjectName: rabbit-demo
 * @Description:
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 9:44
 * @Email: lihh53453@hundsun.com
 */
@Component
public class RebateEvent extends BaseEvent<RebateEvent.RebateMessage> {
    
    @Value("${mq.server.rebate}")
    private String topic;
    
    @Override
    public BaseEventMessage<RebateMessage> buildBaseEventMessage(RebateMessage data) {
        return BaseEventMessage.<RebateMessage>builder()
                .id(RandomStringUtils.random(11))
                .time(new Date())
                .data(data)
                .build();
    }

    @Override
    public String topic() {
        return topic;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class RebateMessage{
        private String id;
        private String info;
    }
}
2.1.2项目中定义接收消息方法
import cn.dsxriiiii.publisher.BaseEvent;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @ProjectName: rabbit-demo
 * @Description:
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 10:25
 * @Email: lihh53453@hundsun.com
 */
@Component
public class EventPublish {

    private final Logger logger = LoggerFactory.getLogger(EventPublish.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void publish(String topic, BaseEvent.BaseEventMessage<?> eventMessage){
        try{
            String messageJson = JSON.toJSONString(eventMessage);
            rabbitTemplate.convertAndSend(topic,messageJson);
            logger.info("发送MQ消息 topic:{} message:{}", topic, messageJson);
        }catch (Exception e){
            logger.error("发送MQ消息 topic:{} message:{}", topic, JSON.toJSONString(eventMessage),e);
            throw e;
        }
    }

    public void publish(String topic, String eventMessageJSON){
        try {
            rabbitTemplate.convertAndSend(topic, eventMessageJSON);
            logger.info("发送MQ JSON数据 topic:{} message:{}", topic, eventMessageJSON);
        } catch (Exception e) {
            logger.error("发送MQ JSON数据失败 topic:{} message:{}", topic, eventMessageJSON, e);
            throw e;
        }
    }

}

2.2接收方

2.2.1 FANOUT交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
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;

/**
 * @ProjectName: rabbit-demo
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 14:12
 */
@Component
public class FanoutListener {

    private static final Logger log = LoggerFactory.getLogger(FanoutListener.class);

    /**
     *  fanout交换机匹配 
     *  与fanout交换机绑定的消息队列都可以收到消息
     * @param msg 传递的消息
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.fanout.award}"),
            exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
    ))
    public void direct_award(String msg){
        log.info("FANOUT 广播 mq.fanout.award队列接收到消息{}",msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.fanout.rebate}"),
            exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
    ))
    public void direct_rebate(String msg){
        log.info("FANOUT 广播 mq.fanout.rebate队列接收到消息{}",msg);
    }
}

2.2.2 DIRECT交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * @ProjectName: rabbit-demo
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 13:16
 */
@Component
public class DirectListener {

    private static final Logger log = LoggerFactory.getLogger(DirectListener.class);

    /**
     *  topic交换机匹配 
     *  根据routingKey 匹配消息队列 
     * @param msg 传递的消息
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.direct.award}"),
            exchange = @Exchange(name = "dome.direct"),
            key = "award"
    ))
    public void direct_award(String msg){
        log.info("DIRECT 广播 mq.direct.award队列接收到消息{}",msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.direct.rebate}"),
            exchange = @Exchange(name = "dome.direct"),
            key = "rebate"
    ))
    public void direct_rebate(String msg){
        log.info("DIRECT 广播 mq.direct.rebate队列接收到消息{}",msg);
    }
}

2.2.3 TOPIC交换机
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
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;

/**
 * @ProjectName: rabbit-demo
 * @Author: DSXRIIIII
 * @CreateDate: 2024/7/19 13:58
 */
@Component
public class TopicListener {

    private static final Logger log = LoggerFactory.getLogger(TopicListener.class);


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.topic.award}"),
            exchange = @Exchange(name = "dome.topic", type = ExchangeTypes.TOPIC),
            key = "dome.#"
    ))
    public void topic_award(String msg){
        log.info("TOPIC 广播 mq.topic.award队列接收到消息{}",msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "${mq.listener.topic.rebate}"),
            exchange = @Exchange(name = "dome.topic",type = ExchangeTypes.TOPIC),
            key = "#.rebate"
    ))
    public void topic_rebate(String msg){
        log.info("TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息{}",msg);
    }
}

2.3消息转换器

将传入的消息对象转化为JSON 实现代码如下:

@Bean
public MessageConverter messageConverter(){
    // 1.定义消息转换器
    Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
    // 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
    jackson2JsonMessageConverter.setCreateMessageIds(true);
    return jackson2JsonMessageConverter;
}

publisherconsumer两个服务中都引入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>

注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。

2.4使用@BEAN注解定义交换机类型以及绑定队列代码

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ProjectName: rabbit-demo
 * @Description: 非注解形式绑定队列 防止Bean冲突 这里注释了注解
 * @Author: DSXRIIIII
 * @CreateDate: 2024/5/23 10:08
 * @Email: lihh53453@hundsun.com
 **/
@Configuration
public class DirectConfig {

    @Value("${mq.listener.direct.award}")
    private String queue_award;

    @Value("${mq.listener.direct.rebate}")
    private String queue_rebate;

    @Bean
    public DirectExchange directExchange(){
        return ExchangeBuilder.directExchange("dome.direct").build();
    }

//    @Bean
//    public TopicExchange topicExchange(){
//        return ExchangeBuilder.topicExchange("dome.topic").build();
//    }

    //@Bean
    public Queue queue_award(){
        return new Queue(queue_award);
    }

    //@Bean
    public Queue queue_rebate(){
        return new Queue(queue_rebate);
    }

    //@Bean
    public Binding QueueAwardBindingDirect(Queue queue_award, DirectExchange directExchange){
        return BindingBuilder.bind(queue_award).to(directExchange).with("award");
    }

    //@Bean
    public Binding QueueRebateBindingDirect(Queue queue_rebate, DirectExchange directExchange){
        return BindingBuilder.bind(queue_rebate).to(directExchange).with("rebate");
    }

}

2.5实现结果

1.启动消费者和生产者o( ̄▽ ̄)ブ 运行Application方法 注意修改mq地址和账户密码⚠ 2.调用Test方法发送消息,接收方打印日志

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.5)

2024-07-19T15:55:44.415+08:00  INFO 31892 --- [ntContainer#2-1] c.d.customer.exchange.FanoutListener     : FANOUT 广播 mq.fanout.rebate队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
2024-07-19T15:55:44.415+08:00  INFO 31892 --- [ntContainer#3-1] c.d.customer.exchange.FanoutListener     : FANOUT 广播 mq.fanout.award队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
2024-07-19T15:55:44.425+08:00  INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ
2024-07-19T15:55:44.430+08:00  INFO 31892 --- [ntContainer#0-1] c.d.customer.exchange.DirectListener     : DIRECT 广播 mq.direct.rebate队列接收到消息hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~
2024-07-19T15:55:44.672+08:00  INFO 31892 --- [ntContainer#4-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.award队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
2024-07-19T15:55:44.673+08:00  INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
2024-07-19T15:55:44.678+08:00  INFO 31892 --- [ntContainer#1-1] c.d.customer.exchange.DirectListener     : DIRECT 广播 mq.direct.award队列接收到消息hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~