SpringBoot集成RabbitMQ实现消息收发

927 阅读6分钟

上篇文章描述了使用RabbitMQ原生API来实现消息的处理,在实际项目开发中,略显繁琐。我们就可以使用springboot的RabbitMQ插件进行简单配置就可以实现。

一、springboot集成RabbitMQ

1-1、添加依赖

首先创建一个springboot项目,然后添加下面依赖

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

1-2、添加配置信息

spring.rabbitmq.host=192.168.253.131
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/mirror

1-3、创建队列及交换机

创建队列及交换机并进行绑定除了在控制台进行操作外,还可以利用程序进行创建,下面来分别介绍一下

1-3-1、使用@Configuration在项目启动时进行创建

使用@Configuration需要注意的是下面声明queue和exchange以及绑定 的方法需要添加@Bean

这种方法创建的好处就是项目启动之后就可以完成创建,缺点是如果想动态创建就无法实现了

绑定交换机和队列使用BindingBuilder.bind(队列).to(交换机)

1-3-1-1、创建fanout交换机

@Configuration
public class FanoutConfig {

   //声明队列
   @Bean
   public Queue fanoutQ1() {
      return new Queue("finout.queue1");
   }
   
   //声明exchange
   @Bean
   public FanoutExchange setFanoutExchange() {
      return new FanoutExchange("fanout.exchange");
   }
   //声明Binding,exchange与queue的绑定关系
   @Bean
   public Binding bindQ1() {
      return BindingBuilder.bind(fanoutQ1()).to(setFanoutExchange());
   }
   
}

1-3-1-2、创建topic交换机

绑定交换机和队列使用BindingBuilder.bind(队列).to(交换机).with(路由键)

@Configuration
public class TopicConfig {

   //声明队列
   @Bean
   public Queue topicQ1() {
      return new Queue("topic.queue1");
   }
  
   //声明exchange
   @Bean
   public TopicExchange setTopicExchange() {
      return new TopicExchange("topic.exchange");
   }
   //声明binding,需要声明一个roytingKey
   @Bean
   public Binding bindTopicHebei1() {
      return BindingBuilder.bind(topicQ1()).to(setTopicExchange()).with("hunan.*");
   }
  }

1-3-1-3、创建header交换机

绑定交换机和队列有三种方式如下:

BindingBuilder.bind(队列).to(交换机).where(key).matches(val)

BindingBuilder.bind(队列).to(交换机).whereAny(Map).match()

BindingBuilder.bind(队列).to(交换机).whereAll(Map).match()

实现代码

@Configuration
public class HeaderConfig {

   //声明queue
   @Bean
   public Queue headQueueTxTyp1() {
      return new Queue("txTyp1");
   }
   @Bean
   public Queue headQueueBusTyp1() {
      return new Queue("busTyp1");
   }
   @Bean
   public Queue headQueueTxBusTyp() {
      return new Queue("txbusTyp1");
   }
   //声明exchange
   @Bean
   public HeadersExchange setHeaderExchange() {
      return new HeadersExchange("headerExchange");
   }
   //声明Binding
   //绑定header中txtyp=1的队列。header的队列匹配可以用mathces和exisits
   @Bean
   public Binding bindHeaderTxTyp1() {
      return BindingBuilder.bind(headQueueTxTyp1()).to(setHeaderExchange()).where("txTyp").matches("1");
   }
   //绑定Header中busTyp=1的队列。
   @Bean 
   public Binding bindHeaderBusTyp1() {
      return BindingBuilder.bind(headQueueBusTyp1()).to(setHeaderExchange()).where("busTyp").matches("1");
   }
   //绑定Header中txtyp=1或者busTyp=1的队列。
   @Bean 
   public Binding bindHeaderTxBusTyp1() {
      Map<String,Object> condMap = new HashMap<>();
      condMap.put("txTyp", "1");
      condMap.put("busTyp", "1");
      return BindingBuilder.bind(headQueueTxBusTyp()).to(setHeaderExchange()).whereAny(condMap).match();
   }
}

1-3-2、使用AmqpAdmin

使用AmqpAdmin优点是可以随时进行队列及路由的创建和绑定

需要注意的是,创建交换机需要使用各自类型的交换机进行创建:

new FanoutExchange()

new TopicExchange()

new HeadersExchange()

@Autowired
private AmqpAdmin amqpAdmin;


@GetMapping("initFanoutExchangeQueue")
public Object initQueue(){
   String queueName="fanout.queue1";
   String exchangeName="fanout.exchange";
   //创建队列
   Queue queue = new Queue(queueName, false, false, false, null);
   amqpAdmin.declareQueue(queue);

   //创建交换机
   FanoutExchange fanoutExchange = new FanoutExchange("fanout.exchange", false, false, null);
   amqpAdmin.declareExchange(fanoutExchange);

   //绑定
   amqpAdmin.declareBinding(new Binding(queueName, Binding.DestinationType.QUEUE,exchangeName,"",null));

   return "success";
}

1-4、发送消息

RabbitMQ使用起来和redis很类似,redis使用RedisTemplate,RabbitMQ则使用RabbitTemplate,下面来看下如何使用

在springboot中使用RabbitTemplate无法发送stream队列消息,因为发送stream队列消息,需要设置basicQos属性,而basicQos属性需要通过channel来设置,RabbitTemplate已经进行封装,使用的时候无法获取channel,因此无法发送stream类型队列消息;

1-4-2、给指定队列发送消息

首先设置消息的相关参数,最后调用rabbitTemplate.send(队列名称,消息内容,请求参数)进行发送

      String message="hello word";
//设置部分请求参数
      MessageProperties messageProperties = new MessageProperties();
      messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
      //发消息
      rabbitTemplate.send("directqueue",new Message(message.getBytes("UTF-8"),messageProperties));

1-4-3、使用exchange的fanout发送消息

向交换机发送消息和向队列发送类似

第一步:设置消息相关参数

MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);

第二步:发送消息

rabbitTemplate.send(队列名称,路由名称,消息,消息相关参数)

String message="fanoutmessage";
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
//fanout模式只往exchange里发送消息。分发到exchange下的所有queue
rabbitTemplate.send(MyConstants.EXCHANGE_FANOUT, "", new Message(message.getBytes("UTF-8"),messageProperties));

1-4-4、使用exchange的topic发送消息

String routingKey="routingkey";
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
//发送消息
rabbitTemplate.send("topicExchange", routingKey, new Message(message.getBytes("UTF-8"),messageProperties));

1-4-5、使用exchange的header发送消息

MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
//设置header信息
messageProperties.setHeader("name", "admin");
messageProperties.setHeader("pass", "123");
//发送消息
rabbitTemplate.send("headerExchange", "uselessRoutingKey", new Message(message.getBytes("UTF-8"),messageProperties));

1-4-6、使用quorum队列发送消息

//设置部分请求参数
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
     
//发消息
rabbitTemplate.send("QUEUE_QUORUM",new Message(message.getBytes("UTF-8"),messageProperties));

1-5、接收消息

消费者都是通过@RabbitListener注解来声明。注解中包含了声明消费者队列时所需要的重点参数。对照原生API,这些参数就不难理解了。

但是当要消费Stream队列时,还是要重点注意他的三个必要的步骤:

  • channel必须设置basicQos属性。 channel对象可以在@RabbitListener声明的消费者方法中直接引用,Spring框架会进行注入。
  • 正确声明Stream队列。 通过往Spring容器中注入Queue对象的方式声明队列。在Queue对象中传入声明Stream队列所需要的参数。
  • 消费时需要指定offset。 可以通过注入Channel对象,使用原生API传入offset属性。

​ 使用SpringBoot框架集成RabbitMQ后,开发过程可以得到很大的简化,所以使用过程并不难,对照一下示例就能很快上手。但是,需要理解一下的是,SpringBoot集成后的RabbitMQ中的很多概念,虽然都能跟原生API对应上,但是这些模型中间都是做了转换的,比如Message,就不是原生RabbitMQ中的消息了。使用SpringBoot框架,尤其需要加深对RabbitMQ原生API的理解,这样才能以不变应万变,深入理解各种看起来简单,但是其实坑很多的各种对象声明方式。

主要通过给方法加上如下注解,并设置队列即可消费对应的消息

@RabbitListener(queues=MyConstants.QUEUE_Name)

1-5-1、接收消息

接收消息的方式,不论是直接接收发送到队列的,还是发送到exchange交换机,方式都是一样,主要区别就是绑定交换机的队列,根据绑定的队列不同而接收对应的消息

@RabbitListener(queues=MyConstants.QUEUE_Name)
public void directReceive2(String message) {
   System.out.println("consumer2 received message : " +message);
}

1-5-2、普通队列消息

发送消息,第一个参数为队列名称,如果有多个消费此队列的消费端,只有一个消费端可以进行消费

rabbitTemplate.send("directqueue",new Message(message.getBytes("UTF-8"),messageProperties));

接收消息注解

@RabbitListener(queues="directqueue")

1-5-3、交换机fanout类型接收消息

发送消息,第一个参数为交换机名称,和交换机绑定的队列都可以消费消息

rabbitTemplate.send("EXCHANGE_FANOUT", routingkey, new Message(message.getBytes("UTF-8"),messageProperties));

接收消息,传入和上面EXCHANGE_FANOUT交换机绑定的队列,即可消费消息

@RabbitListener(queues="fanout_queue1")

1-5-4、交换机topic类型接收消息

发送消息,指定一个topic类型的交换机,并且设置routingkey

rabbitTemplate.send("topicExchange", routingKey, new Message(message.getBytes("UTF-8"),messageProperties));

接收消息:

注意这个模式会有优先匹配原则。例如发送routingKey=beijing.haidian,那匹配到beijing.* (beijing.haidian,beijing.chaoyang),之后就不会再去匹配*.haidian(XXX.haidian)

@RabbitListener(queues="beijing.haidian")

1-5-5、交换机header类型接收消息

如下图三个队列绑定了headerExchange交换机

busType1:当header中有busTyp=1则接收消息,即使设置其他key-val也可以

txType1:当header中有txTyp=1则接收消息,即使设置其他key-val也可以

txbusType1:当header中有busTyp=1或者txTyp:1则可以接收消息(因为设置的x-match:any,如果是all则两个都必须满足)

image.png