RabbitMQ和SpringBoot整合使用的组件列表:
RabbitAdmin:
RabbitAdmin 可以用来声明exchange,queue,binding。发送消息等操作
SpringAMQP 声明
通过@Bean注解方式声明交换机和队列。
RabbitTemplate 消息模板
我们在与springAMQP整合的时候进行发送消息的关键类
SimpleMessageListenerContainer
消息监听容器
MessageListenerAdapter
消息监听适配器
MessageConverter
转换器,序列化,反序列化
<!-- junit测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- mq依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
package com.example.demo;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author wx
* @date 2021-01-16
*/
@Configuration
@ComponentScan("com.example.demo.*")
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory(){
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses("127.0.0.1:5672");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
return connectionFactory;
}
}
rabbitAdmin类可以很好的操作RabbitMQ, 在Spring中直接注入即可。
// 在RabbitMQConfig类中加入如下代码
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
//AutoStartup 必须设置为true, 否则spring容器不会加载
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
RabbitAdmin 底层实现就是从Sping容器中获取Exchange、Bingding、RoutingKey、以及Queue的@Bean声明。
然后使用RabbitTemplate的execute方法执行对应的声明,修改,删除等一系列RabbitMQ基础功能操作。
添加测试类:RabbitAdminTest
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitAdminTest {
@Test
public void contextLoads() {
}
@Autowired
private RabbitAdmin rabbitAdmin;
@Test
public void testAdmin() throws Exception{
//声明交换机
rabbitAdmin.declareExchange(new DirectExchange("wx.direct", false, false));
rabbitAdmin.declareExchange(new TopicExchange("wx.topic", false, false));
rabbitAdmin.declareExchange(new FanoutExchange("wx.fanout", false, false));
//声明队列
rabbitAdmin.declareQueue(new Queue("wx.direct.queue", false));
rabbitAdmin.declareQueue(new Queue("wx.topic.queue", false));
rabbitAdmin.declareQueue(new Queue("wx.fanout.queue", false));
//bingding 交换机和队列, new HashMap<>()为其他arguments
rabbitAdmin.declareBinding(new Binding("wx.direct.queue",
Binding.DestinationType.QUEUE,
"wx.direct", //exchange
"direct", //routingKey
new HashMap<>()));
//bingding 交换机和队列的另一种方式
rabbitAdmin.declareBinding(
BindingBuilder
.bind(new Queue("wx.topic.queue", false)) //直接创建队列
.to(new TopicExchange("wx.topic", false, false)) //建立关联关系(没有该交换机会报错)
.with("wx.#")); //routingKey
//bingding fanout类型交换机不需要 routingKey
rabbitAdmin.declareBinding(
BindingBuilder
.bind(new Queue("wx.fanout.queue", false))
.to(new FanoutExchange("wx.fanout", false, false)));
//清空队列
rabbitAdmin.purgeQueue("wx.topic.queue", false);
}
}
在RabbitMQ控制台 http://localhost:15672/ 可以看到对应的exchange和queue,以及绑定关系。
在RabbitMQ基础API里面声明一个Exchange, 声明一个绑定,一个队列。
通过注解去声明,跟用RabbitMQ写法类似。
新建一个 RabbitMQBinding 配置类。 类上加注解 @Configuration。
package com.example.demo;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author wx
* @date 2021-01-16
*/
@Configuration
public class RabbitMQBinding {
/**
* 针对消费者配置
* 1. 设置交换机类型
* 2. 将队列绑定到交换机
FanoutExchange: 将消息分发到所有绑定队列,无routingkey的概念
HeaderExchange: 通过添加属性key-value匹配
DirectExchange: 按照routingkey分发到指定队列
TopicExchange: 多关键字匹配
*/
@Bean
public TopicExchange topic001(){
return new TopicExchange("wx.topic001", false, false);
}
/**
* 声明队列
*/
@Bean
public Queue queue001(){
return new Queue("wx.topic.queue001", true);
}
@Bean
public Queue queue002(){
return new Queue("wx.topic.queue002", true);
}
@Bean
public Queue queue003(){
return new Queue("wx.topic.queue003", true);
}
/**
* 建立绑定关系
*/
@Bean
public Binding binding001(){
return BindingBuilder.bind(queue001()).to(topic001()).with("rabbit.*");
}
@Bean
public Binding binding002(){
return BindingBuilder.bind(queue002()).to(topic001()).with("mq.*");
}
}
在RabbitMQ控制台 http://localhost:15672/ 可以看到对应的exchange和queue,以及绑定关系。
我们在与springAMQP整合的时候进行发送消息的关键类
该类丰富了发送消息方法,包括可靠性投递消息方法,回调监听消息接口 ConfirmCallback, 返回值确认接口ReturnCallback等等。同样我们需要进行注入到Spring容器中,然后直接使用。
在 RabbitMQBinding 类中增加如下代码
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
增加 RabbitTemplateTest 类:
package com.example.demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
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.nio.charset.StandardCharsets;
import java.util.HashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitTemplateTest {
@Test
public void contextLoads() {
}
/**
* rabbitTemplate.convertAndSend 发送消息
* 发送object格式的消息时, 接收端使用string接收, 发送message格式时,用byte[]接收
*/
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessage1() throws Exception{
//直接发送一个object
// rabbitTemplate.convertAndSend("wx.topic001", "mq.info", "hello mq");
rabbitTemplate.convertAndSend("wx.topic001", "rabbit.info", "hello rabbit");
rabbitTemplate.convertAndSend("wx.topic001", "mq.info", "hello mq");
}
@Test
public void testSendMessage2() throws Exception{
//通过message发送消息
//消息属性, 可以使用已经存在的属性比如setContentType, 也可以自定义属性
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType("text/plain");
//消息内容
Message message = new Message("hello rabbitMQ - text/plain".getBytes(StandardCharsets.UTF_8), messageProperties);
rabbitTemplate.convertAndSend("wx.topic001", "rabbit.info", message);
}
@Test
public void testSendMessage3() throws Exception{
//发送message, 并在发送时修改和增加内容
//消息属性, 可以使用已经存在的属性比如setContentType, 也可以自定义属性
MessageProperties messageProperties = new MessageProperties();
messageProperties.getHeaders().put("desc", "自定义信息描述");
messageProperties.getHeaders().put("type", "自定义消息类型");
//消息内容
Message message = new Message("hello rabbitMQ".getBytes(StandardCharsets.UTF_8), messageProperties);
//通过 MessagePostProcessor 增加额外的属性
rabbitTemplate.convertAndSend("wx.topic001", "rabbit.info", message, new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
System.out.println("添加额外的设置");
message.getMessageProperties().getHeaders().put("desc", "额外修改的信息描述");
message.getMessageProperties().getHeaders().put("attr", "额外增加的属性");
return message;
}
});
}
}
在RabbitMQ控制台 http://localhost:15672/ 可以看到对应的queue下的消息。
这个类非常强大,我们可以对他进行很多设置,对于消费者的配置项,这个类都可以满足
simpleMessageListenerContainer可以进行动态设置,比如在运行过程中的应用可以动态的修改其消费者数量的大小,接收消息的模式等。
很多基于RabbitMQ的自定制的一些后端管控台在进行动态设置的时候,也是根据这一特性去实现的。所以可以看出SpringAMQP非常的强大。
在 RabbitMQBinding 类中增加如下代码:
/**
* 声明一个 SimpleMessageListenerContainer 用于监听queue, 并处理消息
*/
@Bean
public SimpleMessageListenerContainer messageListenerContainer1(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//设置监听队列
container.setQueues(queue001(), queue002(), queue003());
//设置消费者数量
container.setConcurrentConsumers(1);
//设置最大消费者数量
container.setMaxConcurrentConsumers(2);
//是否重回队列
container.setDefaultRequeueRejected(false);
//签收策略
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//消费端的标签策略, 用于区分不同的消费者
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
//处理消息
String msg = new String(message.getBody());
System.out.println("----- consumer : " + msg);
}
});
return container;
}
也可以通过适配器的方式处理消息,上边代码改成如下:
/**
* 声明一个 SimpleMessageListenerContainer 用于监听queue, 并处理消息
* 通过适配器的方式处理消息。 处理消息的模式方法为是 handleMessage, 可以进行修改
*/
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//设置监听队列
container.setQueues(queue001(), queue002(), queue003());
//设置消费者数量
container.setConcurrentConsumers(1);
//设置最大消费者数量
container.setMaxConcurrentConsumers(1);
//是否重回队列
container.setDefaultRequeueRejected(false);
//签收策略
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
//适配器方式
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MessageDelegate());
//修改执行的方法名, 默认方法名为 handleMessage
messageListenerAdapter.setDefaultListenerMethod("consumeMessage");
container.setMessageListener(messageListenerAdapter);
return container;
}
增加一个 MessageDelegate 类:
package com.example.adapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author wx
* @date 2021/2/15
*/
@Component
public class MessageDelegate {
@Autowired
private MessageDelegate messageDelegate;
public void handleMessage(byte[] messageBody){
System.err.println("handleMessage : " + new String(messageBody));
}
public void handleMessage(String messageBody){
System.err.println("handleMessage : " + messageBody);
}
public void consumeMessage(String messageBody){
System.err.println("consumeMessage string : " + messageBody);
}
public void consumeMessage(byte[] messageBody){
System.err.println("consumeMessage byte[] : ");
consumeMessage(new String(messageBody));
}
public void method1(String messageBody){
System.err.println("method1 : " + messageBody);
}
public void method2(String messageBody){
System.err.println("method2 : " + messageBody);
}
}
rabbitTemplate.convertAndSend 发送消息时,有一个问题
/**
* 声明一个 SimpleMessageListenerContainer 用于监听queue, 并处理消息
* 通过适配器的方式处理消息
*/
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//设置监听队列
container.setQueues(queue001(), queue002(), queue003());
//设置消费者数量
container.setConcurrentConsumers(1);
//设置最大消费者数量
container.setMaxConcurrentConsumers(1);
//是否重回队列
container.setDefaultRequeueRejected(false);
//签收策略
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
// //1,适配器方式1
// MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MessageDelegate());
// //修改执行的方法名, 默认方法名为 handleMessage
// messageListenerAdapter.setDefaultListenerMethod("consumeMessage");
// //指定一个转换器, 将message的字节数组转换为字符串,转换器可以不加(用方法重载解决)
// messageListenerAdapter.setMessageConverter(new TextMessageConverter());
// container.setMessageListener(messageListenerAdapter);
//2, 适配器方式2, 不同的队列可以使用不同的方法处理数据
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MessageDelegate());
Map<String, String> queueOrTagToMethodName = new HashMap<>();
queueOrTagToMethodName.put("wx.topic.queue001", "method1");
queueOrTagToMethodName.put("wx.topic.queue002", "method2");
//指定一个转换器, 将message的字节数组转换为字符串,转换器可以不加(用方法重载解决)
messageListenerAdapter.setMessageConverter(new TextMessageConverter());
messageListenerAdapter.setQueueOrTagToMethodName(queueOrTagToMethodName);
container.setMessageListener(messageListenerAdapter);
return container;
}
增加一个 TextMessageConverter 转换器类:
自定义转换器:实现MessageConverter 接口, 并且需要重写下面两个方法。
package com.example.convert;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
/**
* @author wx
* @date 2021/2/15
*/
public class TextMessageConverter implements MessageConverter {
@Override
public Message toMessage(Object o, MessageProperties messageProperties) throws MessageConversionException {
return new Message(o.toString().getBytes(), messageProperties);
}
@Override
public Object fromMessage(Message message) throws MessageConversionException {
// String contentType = message.getMessageProperties().getContentType();
// if(null != contentType && contentType.contains("text")){
// return new String(message.getBody());
// }
Object body = message.getBody();
if(body == null) return body;
/**
* rabbitTemplate.convertAndSend 发送消息时
* 发送object格式的消息时, 接收端使用string接收
* 发送message格式时,用byte[]接收
* 所以在接收到消息时转换一下格式, (判断是否是byte[], 也可以使用其他的属性字段判断, 如上通过contentType判断)
**/
return body instanceof byte[] ? (new String((byte[]) body)) : body;
}
}
修改 messageListenerContainer 代码如下:
/**
* 声明一个 SimpleMessageListenerContainer 用于监听queue, 并处理消息
* 通过适配器的方式处理消息
*/
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//设置监听队列
container.setQueues(queue001(), queue002(), queue003(), queueImage(), queuePdf());
//设置消费者数量
container.setConcurrentConsumers(1);
//设置最大消费者数量
container.setMaxConcurrentConsumers(1);
//是否重回队列
container.setDefaultRequeueRejected(false);
//签收策略
container.setAcknowledgeMode(AcknowledgeMode.AUTO);
//消费端的标签策略
container.setConsumerTagStrategy(new ConsumerTagStrategy() {
@Override
public String createConsumerTag(String queue) {
return queue + "_" + UUID.randomUUID().toString();
}
});
//1.1 适配器方式, json格式转换器 Jackson2JsonMessageConverter
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(new MessageDelegate());
//修改执行的方法名, 默认方法名为 handleMessage
messageListenerAdapter.setDefaultListenerMethod("consumeMapMessage");
//JSON格式转换器
Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
messageListenerAdapter.setMessageConverter(jackson2JsonMessageConverter);
container.setMessageListener(messageListenerAdapter);
return container;
}
在 MessageDelegate 类中增加方法:
public void consumeMapMessage(Map messageBody){
System.err.println("consumeMapMessage map : " + messageBody);
}
增加junit测试方法:
@Test
public void testSendJsonMessage() throws Exception{
//实体类
Order order = new Order("001", "消息订单", "描述信息");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(order);
System.err.println("order json : " + json);
MessageProperties messageProperties = new MessageProperties();
//必须修改ContentType 为application/json
messageProperties.setContentType("application/json");
Message message = new Message(json.getBytes(), messageProperties);
rabbitTemplate.send("wx.topic001", "rabbit.info", message);
}
Order 实体类代码:
public class Order {
private String id;
private String name;
private String content;
public Order() {
}
public Order(String id, String name, String content) {
this.id = id;
this.name = name;
this.content = content;
}
public String toString(){
return "id = " + id + ", name = " + name + ", content = " + content;
}
// getter和setter方法...
}
Json转换器:jackson2JsonMessageConvert:可以进行java对象的转换功能
defaultJack2JavaTypeMapper映射器: 可以进行java对象的映射关系
自定义二进制转换器:比如图片类型,pdf,ppt,流媒体。