本篇会和SpringBoot做整合,采用自动配置的方式进行开发,我们只需要声明RabbitMQ地址就可以了,关于各种创建连接关闭连接的事都由Spring帮我们了~

交给Spring帮我们管理连接可以让我们专注于业务逻辑,就像声明式事务一样易用,方便又高效。


祝有好收获,先赞后看,快乐无限。

本文代码:

  • https://gitee.com/he-erduo/spring-boot-learning-demo

  • https://github.com/he-erduo/spring-boot-learning-demo

1. 环境配置

第一节我们先来搞一下环境的配置,上一篇中我们已经引入了自动配置的包,我们既然使用了自动配置的方式,那RabbitMQ的连接信息我们直接放在配置文件中就行了,就像我们需要用到JDBC连接的时候去配置一下DataSource一样。

如图所示,我们只需要指明一下连接的IP+端口号和用户名密码就行了,这里我用的是默认的用户名与密码,不写的话默认也都是guest,端口号也是默认5672。

主要我们需要看一下手动确认消息的配置,需要配置成manual才是手动确认,日后还会有其他的配置项,眼下我们配置这一个就可以了。


接下来我们要配置一个Queue,上一篇中我们往一个名叫erduo的队列中发送消息,当时是我们手动定义的此队列,这里我们也需要手动配置,声明一个Bean就可以了。

  1.  
    @Configuration
  2.  
    public class RabbitmqConfig {
  3.  
        @Bean
  4.  
        public Queue erduo() {
  5.  
            // 其三个参数:durable exclusive autoDelete
  6.  
            // 一般只设置一下持久化即可
  7.  
            return new Queue("erduo",true);
  8.  
        }
  9.  
     
  10.  
    }

就这么简单声明一下就可以了,当然了RabbitMQ毕竟是一个独立的组件,如果你在RabbitMQ中通过其他方式已经创建过一个名叫erduo的队列了,你这里也可以不声明,这里起到的一个效果就是如果你没有这个队列,会按照你声明的方式帮你创建这个队列。

配置完环境之后,我们就可以以SpringBoot的方式来编写生产者和消费者了。

2. 生产者与RabbitTemplate

和上一篇的节奏一样,我们先来编写生产者,不过这次我要引入一个新的工具:RabbitTemplate

听它的这个名字就知道,又是一个拿来即用的工具类,Spring家族这点就很舒服,什么东西都给你封装一遍,让你用起来更方便更顺手。

RabbitTemplate实现了标准AmqpTemplate接口,功能大致可以分为发送消息和接受消息。

我们这里是在生产者中来用,主要就是使用它的发送消息功能:sendconvertAndSend方法。

  1.  
    // 发送消息到默认的Exchange,使用默认的routing key
  2.  
    void send(Message message) throws AmqpException;
  3.  
     
  4.  
    // 使用指定的routing key发送消息到默认的exchange
  5.  
    void send(String routingKey, Message message) throws AmqpException;
  6.  
     
  7.  
    // 使用指定的routing key发送消息到指定的exchange
  8.  
    void send(String exchange, String routingKey, Message message) throws AmqpException;

send方法是发送byte数组的数据的模式,这里代表消息内容的对象是Message对象,它的构造方法就是传入byte数组数据,所以我们需要把我们的数据转成byte数组然后构造成一个Message对象再进行发送。

  1.  
    // Object类型,可以传入POJO
  2.  
    void convertAndSend(Object message) throws AmqpException;
  3.  
     
  4.  
    void convertAndSend(String routingKey, Object message) throws AmqpException;
  5.  
     
  6.  
    void convertAndSend(String exchange, String routingKey, Object message) throws AmqpException;

convertAndSend方法是可以传入POJO对象作为参数,底层是有一个MessageConverter帮我们自动将数据转换成byte类型或String或序列化类型。

所以这里支持的传入对象也只有三种:byte类型,String类型和实现了Serializable接口的POJO。


介绍完了,我们可以看一下代码:

  1.  
    @Slf4j
  2.  
    @Component("rabbitProduce")
  3.  
    public class RabbitProduce {
  4.  
        @Autowired
  5.  
        private RabbitTemplate rabbitTemplate;
  6.  
     
  7.  
        public void send() {
  8.  
            String message = "Hello 我是作者和耳朵,欢迎关注我。" + LocalDateTime.now().toString();
  9.  
     
  10.  
            System.out.println("Message content : " + message);
  11.  
     
  12.  
            // 指定消息类型
  13.  
            MessageProperties props = MessagePropertiesBuilder.newInstance()
  14.  
                    .setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN).build();
  15.  
     
  16.  
            rabbitTemplate.send(Producer.QUEUE_NAME,new Message(message.getBytes(StandardCharsets.UTF_8),props));
  17.  
            System.out.println("消息发送完毕。");
  18.  
        }
  19.  
     
  20.  
        public void convertAndSend() {
  21.  
            User user = new User();
  22.  
     
  23.  
            System.out.println("Message content : " + user);
  24.  
     
  25.  
            rabbitTemplate.convertAndSend(Producer.QUEUE_NAME,user);
  26.  
            System.out.println("消息发送完毕。");
  27.  
        }
  28.  
     
  29.  
    }
  30.  
     

这里我特意写明了两个例子,一个用来测试send,另一个用来测试convertAndSend。

send方法里我们看下来和之前的代码是几乎一样的,定义一个消息,然后直接send,但是这个构造消息的构造方法可能比我们想的要多一个参数,我们原来说的只要把数据转成二进制数组放进去即可,现在看来还要多放一个参数了。

MessageProperties,是的我们需要多放一个MessageProperties对象,从他的名字我们也可以看出它的功能就是附带一些参数,但是某些参数是少不了的,不带不行。

比如我的代码这里就是设置了一下消息的类型,消息的类型有很多种可以是二进制类型,文本类型,或者序列化类型,JSON类型,我这里设置的就是文本类型,指定类型是必须的,也可以为我们拿到消息之后要将消息转换成什么样的对象提供一个参考。

convertAndSend方法就要简单太多,这里我放了一个User对象拿来测试用,直接指定队列然后放入这个对象即可。

Tips:User必须实现Serializable接口,不然的话调用此方法的时候会抛出IllegalArgumentException异常。


代码完成之后我们就可以调用了,这里我写一个测试类进行调用:

  1.  
    @SpringBootTest
  2.  
    public class RabbitProduceTest {
  3.  
        @Autowired
  4.  
        private RabbitProduce rabbitProduce;
  5.  
     
  6.  
        @Test
  7.  
        public void sendSimpleMessage() {
  8.  
            rabbitProduce.send();
  9.  
            rabbitProduce.convertAndSend();
  10.  
        }
  11.  
    }

效果如下图~

同时在控制台使用命令rabbitmqctl.bat list_queues查看队列-erduo现在的情况:

如此一来,我们的生产者测试就算完成了,现在消息队列里两条消息了,而且消息类型肯定不一样,一个是我们设置的文本类型,一个是自动设置的序列化类型。

3. 消费者与RabbitListener

既然队列里面已经有消息了,接下来我们就要看我们该如何通过新的方式拿到消息并消费与确认了。

消费者这里我们要用到@RabbitListener来帮我们拿到指定队列消息,它的用法很简单也很复杂,我们可以先来说简单的方式,直接放到方法上,指定监听的队列就行了。

  1.  
    @Slf4j
  2.  
    @Component("rabbitConsumer")
  3.  
    public class RabbitConsumer {
  4.  
     
  5.  
        @RabbitListener(queues = Producer.QUEUE_NAME)
  6.  
        public void onMessage(Message message, Channel channel) throws Exception {
  7.  
            System.out.println("Message content : " + message);
  8.  
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
  9.  
            System.out.println("消息已确认");
  10.  
        }
  11.  
     
  12.  
    }

这段代码就代表onMessage方法会处理erduo(Producer.QUEUE_NAME是常量字符串"erduo")队列中的消息。

我们可以看到这个方法里面有两个参数,MessageChannel,如果用不到Channel可以不写此参数,但是Message消息一定是要的,它代表了消息本身。

我们可以想想,我们的程序从RabbitMQ之中拉回一条条消息之后,要以怎么样的方式展示给我们呢?

没错,就是封装为一个个Message对象,这里面放入了一条消息的所有信息,数据结构是什么样一会我一run你就能看到了。

同时这里我们使用Channel做一个消息确认的操作,这里的DeliveryTag代表的是这个消息在队列中的序号,这个信息存放在MessageProperties中。

4. SpringBoot 启动!

编写完生产者和消费者,同时已经运行过生产者往消息队列里面放了两条信息,接下来我们可以直接启动消息,查看消费情况:

在我红色框线标记的地方可以看到,因为我们有了消费者所以项目启动后先和RabbitMQ建立了一个连接进行监听队列。

随后就开始消费我们队列中的两条消息:

第一条信息是contentType=text/plain类型,所以直接就在控制台上打印出了具体内容。

第二条信息是contentType=application/x-java-serialized-object,在打印的时候只打印了一个内存地址+字节大小。

不管怎么说,数据我们是拿到了,也就是代表我们的消费是没有问题的,同时也都进行了消息确认操作,从数据上看,整个消息可以分为两部分:bodyMessageProperties

我们可以单独使用一个注解拿到这个body的内容 - @Payload

  1.  
    @RabbitListener(queues = Producer.QUEUE_NAME)
  2.  
    public void onMessage(@Payload String body, Channel channel) throws Exception {
  3.  
        System.out.println("Message content : " + body);
  4.  
    }

也可以单独使用一个注解拿到MessageProperties的headers属性,headers属性在截图里也可以看到,只不过是个空的 - @Headers。

  1.  
    @RabbitListener(queues = Producer.QUEUE_NAME)
  2.  
    public void onMessage(@Payload String body, @Headers Map<String,Object> headers) throws Exception {
  3.  
        System.out.println("Message content : " + body);
  4.  
        System.out.println("Message headers : " + headers);
  5.  
    }

这两个注解都算是扩展知识,我还是更喜欢直接拿到全部,全都要!!!

上面我们已经完成了消息的发送与消费,整个过程我们可以再次回想一下,一切都和我画的这张图上一样的轨迹:

只不过我们一直没有指定Exchage一直使用的默认路由,希望大家好好记住这张图。

5. @RabbitListener与@RabbitHandler

下面再来补一些知识点,有关@RabbitListener@RabbitHandler

@RabbitListener上面我们已经简单的进行了使用,稍微扩展一下它其实是可以监听多个队列的,就像这样:

  1.  
    @RabbitListener(queues = { "queue1", "queue2" })
  2.  
    public void onMessage(Message message, Channel channel) throws Exception {
  3.  
        System.out.println("Message content : " + message);
  4.  
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
  5.  
        System.out.println("消息已确认");
  6.  
    }

还有一些其他的特性如绑定之类的,这里不再赘述因为太硬编码了一般用不上。

下面来说说这节要主要讲的一个特性:@RabbitListener和@RabbitHandler的搭配使用。

前面我们没有提到,@RabbitListener注解其实是可以注解在类上的,这个注解在类上标志着这个类监听某个队列或某些队列。

这两个注解的搭配使用就要让@RabbitListener注解在类上,然后用@RabbitHandler注解在方法上,根据方法参数的不同自动识别并去消费,写个例子给大家看一看更直观一些。

  1.  
    @Slf4j
  2.  
    @Component("rabbitConsumer")
  3.  
    @RabbitListener(queues = Producer.QUEUE_NAME)
  4.  
    public class RabbitConsumer {
  5.  
     
  6.  
        @RabbitHandler
  7.  
        public void onMessage(@Payload String message){
  8.  
            System.out.println("Message content : " + message);
  9.  
        }
  10.  
     
  11.  
        @RabbitHandler
  12.  
        public void onMessage(@Payload User user) {
  13.  
            System.out.println("Message content : " + user);
  14.  
        }
  15.  
    }

大家可以看看这个例子,我们先用@RabbitListener监听erduo队列中的消息,然后使用@RabbitHandler注解了两个方法。

  • 第一个方法的body类型是String类型,这就代表着这个方法只能处理文本类型的消息。

  • 第二个方法的body类型是User类型,这就代表着这个方法只能处理序列化类型且为User类型的消息。

这两个方法正好对应着我们第二节中测试类会发送的两种消息,所以我们往RabbitMQ中发送两条测试消息,用来测试这段代码,看看效果:

都在控制台上如常打印了,如果@RabbitHandler注解的方法中没有一个的类型可以和你消息的类型对的上,比如消息都是byte数组类型,这里没有对应的方法去接收,系统就会在控制台不断的报错,如果你出现这个情况就证明你类型写的不正确。

假设你的erduo队列中会出现三种类型的消息:byte,文本和序列化,那你就必须要有对应的处理这三种消息的方法,不然消息发过来的时候就会因为无法正确转换而报错。

而且使用了@RabbitHandler注解之后就不能再和之前一样使用Message做接收类型。

  1.  
    @RabbitHandler
  2.  
    public void onMessage(Message message, Channel channel) throws Exception {
  3.  
        System.out.println("Message content : " + message);
  4.  
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
  5.  
        System.out.println("消息已确认");
  6.  
    }

这样写的话会报类型转换异常的,所以二者选其一。

同时上文我的@RabbitHandler没有进行消息确认,大家可以自己试一下进行消息确认。

6. 消息的序列化转换

通过上文我们已经知道,能被自动转换的对象只有byte[]Stringjava序列化对象(实现了Serializable接口的对象),但是并不是所有的Java对象都会去实现Serializable接口,而且序列化的过程中使用的是JDK自带的序列化方法,效率低下。

所以我们更普遍的做法是:使用Jackson先将数据转换成JSON格式发送给RabbitMQ,再接收消息的时候再用Jackson将数据反序列化出来。

这样做可以完美解决上面的痛点:消息对象既不必再去实现Serializable接口,也有比较高的效率(Jackson序列化效率业界应该是最好的了)。

默认的消息转换方案是消息转换顶层接口-MessageConverter的一个子类:SimpleMessageConverter,我们如果要换到另一个消息转换器只需要替换掉这个转换器就行了。

上图是MessageConverter结构树的结构树,可以看到除了SimpleMessageConverter之外还有一个Jackson2JsonMessageConverter,我们只需要将它定义为Bean,就可以直接使用这个转换器了。

  1.  
    @Bean
  2.  
        public MessageConverter jackson2JsonMessageConverter() {
  3.  
            return new Jackson2JsonMessageConverter(jacksonObjectMapper);
  4.  
        }

这样就可以了,这里的jacksonObjectMapper可以不传入,但是默认的ObjectMapper方案对JDK8的时间日期序列化会不太友好,具体可以参考我的上一篇文章:从LocalDateTime序列化探讨全局一致性序列化,总的来说就是定义了自己的ObjectMapper

同时为了接下来测试方便,我又定义了一个专门测试JSON序列化的队列:

  1.  
    @Bean
  2.  
    public Queue erduoJson() {
  3.  
        // 其三个参数:durable exclusive autoDelete
  4.  
        // 一般只设置一下持久化即可
  5.  
        return new Queue("erduo_json",true);
  6.  
    }

如此之后就可以进行测试了,先是生产者代码:

  1.  
    public void sendObject() {
  2.  
            Client client = new Client();
  3.  
     
  4.  
            System.out.println("Message content : " + client);
  5.  
     
  6.  
            rabbitTemplate.convertAndSend(RabbitJsonConsumer.JSON_QUEUE,client);
  7.  
            System.out.println("消息发送完毕。");
  8.  
        }

我又重新定义了一个Client对象,它和之前测试使用的User对象成员变量都是一样的,不一样的是它没有实现Serializable接口。

同时为了保留之前的测试代码,我又新建了一个RabbitJsonConsumer,用于测试JSON序列化的相关消费代码,里面定义了一个静态变量:JSON_QUEUE = "erduo_json";

所以这段代码是将Client对象作为消息发送到"erduo_json"队列中去,随后我们在测试类中run一下进行一次发送。

紧着是消费者代码:

  1.  
    @Slf4j
  2.  
    @Component("rabbitJsonConsumer")
  3.  
    @RabbitListener(queues = RabbitJsonConsumer.JSON_QUEUE)
  4.  
    public class RabbitJsonConsumer {
  5.  
        public static final String JSON_QUEUE = "erduo_json";
  6.  
     
  7.  
        @RabbitHandler
  8.  
        public void onMessage(Client client, @Headers Map<String,Object> headers, Channel channel) throws Exception {
  9.  
            System.out.println("Message content : " + client);
  10.  
            System.out.println("Message headers : " + headers);
  11.  
            channel.basicAck((Long) headers.get(AmqpHeaders.DELIVERY_TAG),false);
  12.  
            System.out.println("消息已确认");
  13.  
        }
  14.  
     
  15.  
    }

有了上文的经验之后,这段代码理解起来也是很简单了吧,同时给出了上一节没写的如何在@RabbitHandler模式下进行消息签收。

我们直接来看看效果:

在打印的Headers里面,往后翻可以看到contentType=application/json,这个contentType是表明了消息的类型,这里正是说明我们新的消息转换器生效了,将所有消息都转换成了JSON类型。

SpringBoot+RabbitMQ 方式收发消息的更多相关文章

  1. 刚体验完RabbitMQ?一文带你SpringBoot+RabbitMQ方式收发消息

    人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长. 楔子 这篇是消息队列RabbitMQ的第二弹. 上一篇的结尾我也预告了本篇的内容:利用RabbitTemplate和注解进行收发消息,还有一个我 ...

  2. springboot + rabbitmq 用了消息确认机制,感觉掉坑里了

    本文收录在个人博客:www.chengxy-nds.top,技术资源共享,一起进步 最近部门号召大伙多组织一些技术分享会,说是要活跃公司的技术氛围,但早就看穿一切的我知道,这 T M 就是为了刷KPI ...

  3. springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    前言: RabbitMQ相关知识请参考: https://www.jianshu.com/p/cc3d2017e7b3 Linux安装RabbitMQ请参考: https://www.jianshu. ...

  4. (转载)springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    转载自https://www.jianshu.com/p/dca01aad6bc8 一.先扔一张图   image.png 说明: 本文涵盖了关于RabbitMQ很多方面的知识点, 如: 消息发送确认 ...

  5. 没用过消息队列?一文带你体验RabbitMQ收发消息

    人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长. 楔子 先给大家说声抱歉,最近一周都没有发文,有一些比较要紧重要的事需要处理. 今天正好得空,本来说准备写SpringIOC相关的东西,但是发现想 ...

  6. SpringBoot整合RabbitMQ,实现消息发送和消费以及多个消费者的情况

    下载安装Erlang和RabbitMQ Erlang和RabbitMQ:https://www.cnblogs.com/theRhyme/p/10069611.html AMQP协议 https:// ...

  7. springboot+rabbitmq整合示例程

    关于什么是rabbitmq,请看另一篇文: http://www.cnblogs.com/boshen-hzb/p/6840064.html 一.新建maven工程:springboot-rabbit ...

  8. AMQP协议与RabbitMQ、MQ消息队列的应用场景

    什么是AMQP? 在异步通讯中,消息不会立刻到达接收方,而是被存放到一个容器中,当满足一定的条件之后,消息会被容器发送给接收方,这个容器即消息队列,而完成这个功能需要双方和容器以及其中的各个组件遵守统 ...

  9. 2.RabbitMQ 的可靠性消息的发送

      本篇包含 1. RabbitMQ 的可靠性消息的发送 2. RabbitMQ 集群的原理与高可用架构的搭建 3. RabbitMQ 的实践经验   上篇包含 1.MQ 的本质,MQ 的作用 2.R ...

随机推荐

  1. Inherent Adversarial Robustness of Deep Spiking Neural Networks: Effects of Discrete Input Encoding and Non-Linear Activations

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! arXiv:2003.10399v2 [cs.CV] 23 Jul 2020 ECCV 2020 1 https://github.com ...

  2. 服务器基本配置(ubuntu)

    服务器基本配置(ubuntu) 学习目标: 修改初始服务器名字(ubuntu 16.04 ) 修改初始服务器名字(ubuntu 18.04 ) ubuntu换源 更改默认python版本 安装软件出现 ...

  3. 【原创】Kuberneters-ConfigMap的实践

    一.什么是ConfigMap        ConfigMap翻译过来即为“配置字典”,在实际的生产环境中,应用程序配置经常需要且又较为复杂,参数.config文件.变量等如果直接打包到镜像中,将会降 ...

  4. vue 在模板template中变量和字符串拼接

    例子:  :post-action="'/api/v1/reportPage/'+this.selectedPagerId+'/saveimg/'"

  5. 【HttpRunner v3.x】笔记—8.运行testcase的几种方式

    在之前的demo过程中,已经运行过testcase了,那这篇就也来汇总一下,运行case相关的知识点. 一.运行testcase的几种场景 1. 运行单个case 通常单个case的话我会在编辑器里用 ...

  6. 小程序开发-媒体组件image

    image 图片组件,支持 JPG.PNG.SVG.WEBP.GIF 等格式,2.3.0 起支持云文件ID. 所有属性如下: Tips image组件默认宽度320px.高度240px image组件 ...

  7. Ajax请求携带Cookie

    目录 xhr ajax cookie跨域处理 客户端 服务端 服务端设置跨域的几种方式 方式一 重写addCorsMappings方法 方式二 对单个接口处理 方式三 @CrossOrigin注解 方 ...

  8. RGB打水印在YUV图片上

    一. 概述 将RGB图片打在YUV上需要注意的是, 字体之外应该透明, 否则背景也会被覆盖不好看,  所以RGB必须有透明度,  本测试格式为BMP ARGB8888(也即B是最低字节, A是最高字节 ...

  9. 以jar包为容器的java程序访问一同打到jar包里的配置文件的方法

    Java程序有时会被打到jar包里执行,同时src/main/resources里一些配置文件也会被打进去. 比如,src/main/resources下有一个box目录,里面有几个json文件,用m ...

  10. docker基本操作及介绍

    Docker 简介 Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目.它基于 Google 公司推出的 Go 语言实现.项目后来加入了 Linux ...