【SpringBoot-消息 02】

一、概念

1、消息服务中间件主要是提升异步通信能力

2、消息服务的两个重要概念:消息代理(message broker)和目的地(destination)

消息发送后,消息代理进行管理然后在发送到目的地

3、消息队列主要的两种目的地

  队列((queue)):点对点的通信(消息发到一个队列中,消息接收者从队列中获取消息,然后被移除此队列。只能有一个队列但不是只有一个消息接收者)

  主题(topic):发布(publish)/订阅(subscribe)的消息通信(消息发布到主题,多个接收者进行订阅接收)

4、JMS(Java Message Service)JAVA消息服务:基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现

5、AMQP:高级消息队列协议,也是一个消息代理的规范,兼容JMS,RabbitMQ是AMQP的实现

6、Spring支持

  • spring-jms提供了对JMS的支持
  • spring-rabbit提供了对AMQP的支持
  • 需要ConnectionFactory的实现来连接消息代理
  • 提供JmsTemplate、RabbitTemplate来发送消息
  • @JmsListener(JMS)、@RabbitListener(AMQP)注解在方法上监听消息代理发布的消息
  • @EnableJms、@EnableRabbit开启支持

7、Spring Boot自动配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

* 自动配置:
* 1、RabbitAutoConfiguration
* 2、自动配置了连接工厂:ConnectionFactory  即:自动获取port,host等配置
* 3、RabbitProperties:封装了RabbitMQ的配置
* 4、RabbitTemplate:给rabbitMQ发送和接受消息
* 5、amqpAdmin:rabbitMQ系统管理功能组件(不发送和接受消息,可以帮我们声明一个队列,创建一个交换器)
      创建和删除exchage,Bingding,Queue
* 6、@EnableRabbit+@RabbitListener监听消息队列内容

二、RabbitMQ

1、从消息发送到接收的整个流程示意图

Publisher:消息生产者

Broker:表示消息队列服务器实体

Virtual Host:虚拟主机,表示一批交换器、消息队列和相关对象

Exchange:交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
Binding:绑定交换器和队列

Queue:消息队列,用来保存消息直到被消费否则消息会一直待在队列里面。一个消息可投入一个或多个队列。

Connection:网络连接,比如TCP网络连接

Channel:信道,即消息通过信道发送给消费者

Consumer:消费者

三、docker安装RabbitMQ

1、拉取镜像

docker pull rabbitmq:3.9.3-management

2、启动RabbitMQ

docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq a0a2d74e6e6a

-d 后台运行,-p指定映射端口

5672客户端和docker容器的端口

15672是浏览器访问管理界面的端口

a0a2d74e6e6a 镜像ID

3、浏览器访问mq的管理页面

http://172.16.203.134:15672/

初始密码:guest/guest

4、测试消息发送的流程

4.1、先创建三个交换器  --》分别对应不同的类型

4.2、在创建四个消息队列

 4.3、绑定交换机和消息队列-->按照上图进行绑定

 四、SpringBoot整合RabbitMQ (发送消息给消息队列和从消息队列中接收消息)

1、引入spring-boot-starter-amqp

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

2、rabbitMQ自动配置

* 1、RabbitAutoConfiguration
* 2、自动配置了连接工厂:ConnectionFactory  即:自动获取port,host等配置
* 3、RabbitProperties:封装了RabbitMQ的配置
* 4、RabbitTemplate:给rabbitMQ发送和接受消息
* 5、amqpAdmin:rabbitMQ系统管理功能组件(不发送和接受消息,可以帮我们声明一个队列,创建一个交换器)

理解springboot引用的功能,应该从RabbitAutoConfiguration来查看,并且Properties一般是配置属性的,Template是操作功能的对象,ConnectionFactory试试配置连接工厂

3、测试springboot给rabbitMQ发送消息

@Autowired
    RabbitTemplate rabbitTemplate;

    //发送数据
    @Test
    public void contextLoads() {
       /*
        可以通过send发送,但是message需要自己构造一个,定义消息体内容和消息头
        exchage:代表交换器,routerkey代表路由,message是消息体
        rabbitTemplate.send(exchage,routerkey,message);
        */

        /*
         object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq
         rabbitTemplate.convertAndSend(exchage,routerkey,object)
        */

        Map<String,Object> map = new HashMap<>();
        map.put("msg","这是第一个消息");
        map.put("data", Arrays.asList("hello",123,true));
        //对象被默认序列化以后发送出去
        rabbitTemplate.convertAndSend("amq.direct","csiixueyuan.news",map);
    }

但是存的数据是序列化后的数据,为什么发送的消息存在rabbitmq里面是那种序列化主要是RabbitTemplate里面的SimpleMessageConverter这个对象的原因,所以可以换一个MessageConverter来反序列化
private MessageConverter messageConverter = new SimpleMessageConverter();

预期存的应该是json格式的数据,如何解决?

因为序列化是由于MessageConverter,所以应该在MessageConverter里面去看是否有json的序列的类

 

从上面的图可以看到MessageConverter里面有json的反序列化类,那么我们定义一个配置类,返回Jackson2JsonMessageConverter类对象,就可以解决序列化问题

MessageConverter 配置类  (生产者发送消息配置)

@Configuration
public class MyAmqpconfig {

    @Bean
    public MessageConverter messageConverter(){

        return new Jackson2JsonMessageConverter();
    }
}

设置了序列化配置类以后,在发送数据,rabbitMQ内存储的就是json格式的数据

4、测试springboot接收rabbitMQ的消息

//接收数据,如何将数据自动的转为json发送出去
    @Test
    public void recived(){
        //rabbitTemplate.receive(); 如果用receive方法接受的话,收到的消息会转换成message,但是只有消息头没有消息体

        Object obj = rabbitTemplate.receiveAndConvert("csiixueyuan.news");

        System.out.println(obj.getClass()); //class java.util.HashMap 类型
        System.out.println(obj); //{msg=这是第一个消息, data=[hello, 123, true]}

    }

设置了配置序列化类以后,取的数据同样也是json的数据

 5、以上都是单播,下面是广播的发送方式

    @Test
    public void sendMsg(){
        rabbitTemplate.convertAndSend("amq.fanout","",new Book("水浒传","施耐庵"));
    }
}

五、监听订阅消息

大体流程:消息系统发送消息以后,库存系统会监听然后订阅消息给消费者

1、测试监听 (构造两个方法:收到消息、收到消息体和消息属性)

package com.wufq.rabbitmq.service;

import com.wufq.rabbitmq.bean.Book;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
/*
*
 * @Description
 * @Author wufq
 * @Version
 * @Date 2021/9/13 11:00*/


@Service
public class BookService {


   /* * 这个方法主要是监听消息队列的
    * 想要@RabbitListener 监听起作用,我们必须开启注解的rabbit注解模式@EnableRabbit
    * @Return:
    */


    @RabbitListener(queues = "wufq.news")
    public void receive(Book book){
        System.out.println("收到消息:"+book);
    }

    @RabbitListener(queues = "wufq")
    public void receive02(Message message){
        System.out.println("获取消息体s"+message.getBody());
        System.out.println("获取消息属性"+message.getMessageProperties());

    }

}

注意点:

1、@RabbitListener起的作用是监听那个消息队列,这个的前提是要开启监听RabbitListener

2、要想成功监听,必须要设置消费者消息配置的反序列类

package com.wufq.rabbitmq.config;

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 消费者消费消息配置
 */
@Configuration
public class RabbitMQConfig {

    @Bean
    public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }

}

这里注意:设置这个被序列化对象(这里是Book对象)应提供一个无参的构造函数,否则会抛出异常

Book类

package com.wufq.rabbitmq.bean;

/**
 * @Description
 * @Author wufq
 * @Version
 * @Date 2021/9/13 10:20
 */
public class Book {

    private String author;
    private String name;

    public String getAuthor() {
        return author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "author='" + author + '\'' +
                ", name='" + name + '\'' +
                '}';
    }

    public Book() {
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Book(String author, String name) {
        this.author = author;
        this.name = name;
    }
}

总结:

1、主类开启监听:@EnableRabbit //开启注解的rabbitMQ模式

2、设置两个序列化类:生产者发送消息配置(MyAmqpconfig)和消费者消费配置(RabbitMQConfig),这里注意被序列化的对象提供一个无参的构造函数

3、生产者发送消息到队列:单播模式、广播模式

4、消费者监听消息队列:获取消息、获取消息的body和properties

六、amqpAdmin管理组件

当前我们已经在mq的管理网站上创建了对应的交换器和消息队列,如果没有事先创建,就可以用amqpAdmin来创建

 @Test
    public void createEexchange(){

        //创建交换器
        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));

        //创建队列
        amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));

        //创建绑定规则
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null));
    }

完整的测试类代码

package com.wufq.rabbitmq;


import com.wufq.rabbitmq.bean.Book;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/*
* 自动配置:
* 1、RabbitAutoConfiguration
* 2、自动配置了连接工厂:ConnectionFactory  即:自动获取port,host等配置
* 3、RabbitProperties:封装了RabbitMQ的配置
* 4、RabbitTemplate:给rabbitMQ发送和接受消息
* 5、amqpAdmin:rabbitMQ系统管理功能组件(不发送和接受消息,可以帮我们声明一个队列,创建一个交换器)
*       创建和删除exchage,Bingding,Queue
* 6、@EnableRabbit+@RabbitListener监听消息队列内容
*
*/

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

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    AmqpAdmin amqpAdmin;

    /*
    * 1、单播(点对点)
    */

    //发送数据
    @Test
    public void contextLoads() {
       /*
        可以通过send发送,但是message需要自己构造一个,定义消息体内容和消息头
        exchage:代表交换器,routerkey代表路由,message是消息体
        rabbitTemplate.send(exchage,routerkey,message);
        */

        /*
         object默认当成消息体,只需要传入要发送的对象,自动序列化发送给rabbitmq
         rabbitTemplate.convertAndSend(exchage,routerkey,object)
        */

        Map<String,Object> map = new HashMap<>();
        map.put("msg","这是第一个消息");
        map.put("data", Arrays.asList("hello",123,true));
        //对象被默认序列化以后发送出去
        rabbitTemplate.convertAndSend("amq.direct","csiixueyuan.news",map);
    }

    //接收数据,如何将数据自动的转为json发送出去
    @Test
    public void recived(){
        //rabbitTemplate.receive(); 如果用receive方法接受的话,收到的消息会转换成message,但是只有消息头没有消息体

        Object obj = rabbitTemplate.receiveAndConvert("csiixueyuan.news");

        System.out.println(obj.getClass()); //class java.util.HashMap 类型
        System.out.println(obj); //{msg=这是第一个消息, data=[hello, 123, true]}



        /*为什么发送的消息存在rabbitmq里面是那种序列化,主要是RabbitTemplate里面的SimpleMessageConverter这个对象的原因
        所以可以换一个MessageConverter来反序列化
        private MessageConverter messageConverter = new SimpleMessageConverter();*/

    }

    /*
    * 1、广播(给交换器绑定的所有路由发送消息,并且只需要制定交换器不需要绑定路由)
    *
    */

    @Test
    public void sendMsg(){
        rabbitTemplate.convertAndSend("amq.fanout","",new Book("水浒传","罗贯中"));
    }

    /*
    * amqpAdmin创建交换器、队列、Binding
    */

    @Test
    public void createEexchange(){

        //创建交换器
        amqpAdmin.declareExchange(new DirectExchange("amqpadmin.exchange"));

        //创建队列
        amqpAdmin.declareQueue(new Queue("amqpadmin.queue",true));

        //创建绑定规则
        amqpAdmin.declareBinding(new Binding("amqpadmin.queue",Binding.DestinationType.QUEUE,"amqpadmin.exchange","amqp.haha",null));
    }

}

 

posted @ 2021-08-17 13:08  尘封~~  阅读(103)  评论(0编辑  收藏  举报