【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)); } }