消息中间件——RabbitMQ(九)RabbitMQ整合Spring AMQP实战!

  • 例如:添加一个交换机、删除一个绑定、清空一个队列里的消息等等

2.1 代码演示

2.1.1 引入Pom文件
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

4.0.0

com.cp

rabbitmq-spring

0.0.1-SNAPSHOT

jar

rabbitmq-spring

rabbitmq-spring

org.springframework.boot

spring-boot-starter-parent

1.5.14.RELEASE

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

com.rabbitmq

amqp-client

3.6.5

org.springframework.boot

spring-boot-starter-amqp

org.springframework.boot

spring-boot-maven-plugin

2.1.2 配置Bean

@Configuration

@ComponentScan({“com.cp.spring.*”})

public class RabbitMQConfig {

//相当于

@Bean

public ConnectionFactory connectionFactory(){

CachingConnectionFactory connectionFactory = new CachingConnectionFactory();

connectionFactory.setAddresses(“127.0.0.1:5672”);

connectionFactory.setUsername(“user_cp”);

connectionFactory.setPassword(“123456”);

connectionFactory.setVirtualHost(“/vhost_cp”);

return connectionFactory;

}

//形参名称要与bean的方法名保持一致

@Bean

public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {

RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);

rabbitAdmin.setAutoStartup(true);

return rabbitAdmin;

}

}

2.1.3 测试类

@RunWith(SpringRunner.class)

@SpringBootTest

public class ApplicationTests {

@Test

public void contextLoads() {

}

@Autowired

private RabbitAdmin rabbitAdmin;

@Test

public void testAdmin() throws Exception {

//直连监听

rabbitAdmin.declareExchange(new DirectExchange(“test.direct”, false, false));

rabbitAdmin.declareExchange(new TopicExchange(“test.topic”, false, false));

rabbitAdmin.declareExchange(new FanoutExchange(“test.fanout”, false, false));

rabbitAdmin.declareQueue(new Queue(“test.direct.queue”, false));

rabbitAdmin.declareQueue(new Queue(“test.topic.queue”, false));

rabbitAdmin.declareQueue(new Queue(“test.fanout.queue”, false));

//第一个参数:具体的队列 第二个参数:绑定的类型 第三个参数:交换机 第四个参数:路由key 第五个参数:arguments 参数

rabbitAdmin.declareBinding(new Binding(“test.direct.queue”,

Binding.DestinationType.QUEUE,

“test.direct”, “direct”, new HashMap<>()));

//BindingBuilder 链式编程

rabbitAdmin.declareBinding(

BindingBuilder

.bind(new Queue(“test.topic.queue”, false)) //直接创建队列

.to(new TopicExchange(“test.topic”, false, false)) //直接创建交换机 建立关联关系

.with(“user.#”)); //指定路由Key

rabbitAdmin.declareBinding(

BindingBuilder

.bind(new Queue(“test.fanout.queue”, false))

.to(new FanoutExchange(“test.fanout”, false, false)));

//清空队列数据

rabbitAdmin.purgeQueue(“test.topic.queue”, false);

}

}

通过以上代码,可以自行测试一下结果。

RabbitAdmin源码

RabbitAdmin源码UML图

实现了InitializingBean接口,表明在Bean配置加载完后再加载RabbitAdmin配置。找到afterPropertiesSet()方法中最要的initialize()初始化方法。

afterPropertiesSet方法

initialize方法

initialize

this.applicationContext.getBeansOfType(Collection.class, false, false).values()

可以看到Exchange、Queue、Binding都是从Spring容器中获取三种类型,加载到上方定义的contextExchanges、contextQueues、contextBindings三种容器中。

后续的源码中,也可以看出通过筛选Spring容器中RabbitMQ的信息之后,再去建立RabbitMQ服务器的连接。主要通过Spring以@Bean的方式,将配置加载到Spring容器之后,再从容器中获取相关信息,再去建立连接。

3. SpringAMQP声明


  • 在Rabbit基础API里面声明一个Exchange、声明一个绑定、一个队列

channel

-使用SpringAMQP去声明,就需要使用SpringAMQP的如下模式,即声明@Bean方式

声明AMQP

3.1 代码演示

@Configuration

@ComponentScan({“com.cp.spring.*”})

public class RabbitMQConfig {

//相当于

@Bean

public ConnectionFactory connectionFactory(){

CachingConnectionFactory connectionFactory = new CachingConnectionFactory();

connectionFactory.setAddresses(“127.0.0.1:5672”);

connectionFactory.setUsername(“user_cp”);

connectionFactory.setPassword(“123456”);

connectionFactory.setVirtualHost(“/vhost_cp”);

return connectionFactory;

}

//形参名称要与bean的方法名保持一致

@Bean

public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {

RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);

rabbitAdmin.setAutoStartup(true);

return rabbitAdmin;

}

/**

  • 针对消费者配置

    1. 设置交换机类型
    1. 将队列绑定到交换机

FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念

HeadersExchange :通过添加属性key-value匹配

DirectExchange:按照routingkey分发到指定队列

TopicExchange:多关键字匹配

*/

@Bean

public TopicExchange exchange001() {

return new TopicExchange(“topic001”, true, false);

}

@Bean

public Queue queue001() {

return new Queue(“queue001”, true); //队列持久

}

@Bean

public Binding binding001() {

return BindingBuilder.bind(queue001()).to(exchange001()).with(“spring.*”);

}

@Bean

public TopicExchange exchange002() {

return new TopicExchange(“topic002”, true, false);

}

@Bean

public Queue queue002() {

return new Queue(“queue002”, true); //队列持久

}

@Bean

public Binding binding002() {

return BindingBuilder.bind(queue002()).to(exchange002()).with(“rabbit.*”);

}

@Bean

public Queue queue003() {

return new Queue(“queue003”, true); //队列持久

}

@Bean

public Binding binding003() {

//同一个Exchange绑定了2个队列

return BindingBuilder.bind(queue003()).to(exchange001()).with(“mq.*”);

}

}

再次运行ApplicationTests类中testAdmin()方法,可以在控制台中,查看到一个Exchange绑定两个Queue。

运行结果

4. RabbitTemplate


RabbitTemplate,即消息模板

  • 我们在与SpringAMQP整合的时候进行发送消息的关键词

  • 该类提供了丰富的发送消息方法,包括可靠性投递消息方法、回调监听消息接口ConfirmCallback、返回值确认接口ReturnCallback等等。同样我们需要进行注入到Spring容器中,然后直接使用

  • 在与SPring整合时需要实例化,但是在与SpringBoot整合时,在配置文件里添加配置即可

4.1 代码演示

4.1.1 RabbitMQConfig类

在RabbitMQConfig类中写RabbitTemplate配置

@Bean

public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {

RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);

return rabbitTemplate;

}

4.1.2 ApplicationTests类

在ApplicationTests测试类中添加测试方法,进行测试。

@Autowired

private RabbitTemplate rabbitTemplate;

@Test

public void testSendMessage() throws Exception {

//1 创建消息

MessageProperties messageProperties = new MessageProperties();

messageProperties.getHeaders().put(“desc”, “信息描述…”);

messageProperties.getHeaders().put(“type”, “自定义消息类型…”);

//消息体,与参数

Message message = new Message(“Hello RabbitMQ”.getBytes(), messageProperties);

//转换并发送

//MessagePostProcessor 在消息发送完毕后再做一次转换进行再加工,匿名接口,需要重写方法

rabbitTemplate.convertAndSend(“topic001”, “spring.amqp”, message, new MessagePostProcessor() {

@Override

public Message postProcessMessage(Message message) throws AmqpException {

System.err.println(“------添加额外的设置---------”);

message.getMessageProperties().getHeaders().put(“desc”, “额外修改的信息描述”);

message.getMessageProperties().getHeaders().put(“attr”, “额外新加的属性”);

return message;

}

});

}

4.1.3 查看管控台

运行前,可以看到queue001中是没有消息的。

执行前Queue001

运行testSendMessage()方法。并获取消息。

执行后Queue001

获取消息

4.1.4 简单写法

@Test

public void testSendMessage2() throws Exception {

//1 创建消息

MessageProperties messageProperties = new MessageProperties();

messageProperties.setContentType(“text/plain”);

Message message = new Message(“mq 消息1234”.getBytes(), messageProperties);

rabbitTemplate.send(“topic001”, “spring.abc”, message);

rabbitTemplate.convertAndSend(“topic001”, “spring.amqp”, “hello object message send!”);

rabbitTemplate.convertAndSend(“topic002”, “rabbit.abc”, “hello object message send!”);

}

我们往topic001中发送了两条消息,topic002中发送了一条消息。运行testSendMessage2() 接下来再查看下管控台

查看管控台

获取消息2

获取消息3

可以看到topic001中已经有了三条消息,刚才发送的消息也还在。GetMessage并不是消费消息,而只是获取消息。

5. SimpleMessageListenerContainer


简单消息监听容器

  • 这个类非常的强大,我们可以对它进行很多设置,对于消费者的配置项,这个类都可以满足

  • 监听队列(多个队列)、自动启动、自动声明功能

  • 设置事务特性、事务管理器、事务属性、事务容器(并发)、是否开启事务、回滚消息等

  • 设置消费者数量、最小最大数量、批量消费

  • 设置消息确认和自动确认模式、是否重回队列、异常捕捉handler函数

  • 设置消费者标签生成策略、是否独占模式、消费者属性等

  • 设置具体的监听器、消息转换器等等。

注意:

  • SimpleMessageListenerContainer可以进行动态设置,比如在运行中的应用可以动态的修改其消费者数量的大小、接收消息的模式等

  • 很多机遇RabbitMQ的自制定话后端管控台在进行动态设置的时候,也是根据这一特性去实现的。所以可以看出SpringAMQP非常的强大

思考

SimpleMessageListenerContainer为什么可以动态感知配置变更?

5.1 代码演示

5.1.1 RabbitMQConfig类

配置中添加如下代码:

@Bean

public SimpleMessageListenerContainer messageContainer(ConnectionFactory connectionFactory) {

SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);

//添加多个队列进行监听

container.setQueues(queue001(), queue002(), queue003(), queue_image(), queue_pdf());

//当前消费者数量

container.setConcurrentConsumers(1);

//最大消费者数量

container.setMaxConcurrentConsumers(5);

//设置重回队列,一般设置false

container.setDefaultRequeueRejected(false);

//设置自动签收机制

container.setAcknowledgeMode(AcknowledgeMode.AUTO);

//设置listener外露

container.setExposeListenerChannel(true);

//消费端标签生成策略

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.err.println("----------消费者: " + msg);

}

});

return container;

}

运行之前写的testSendMessage2()方法,查看管控台中的相关信息以及控制台打印信息

查看Channel1

查看Channel2

打印信息

6. MessageListenerAdapter


MessageListenerAdapter 即消息监听适配器

6.1 代码演示

6.1.1 适配器使用方式1

我们把之前的消息监听代码注释,可以不用直接加消息监听,而是采用MessageListenerAdapter的方式,通过适配器方式1,我们来学习下如何使用默认的handleMessage,自定义方法名,自定义转换器。

使用默认handleMessage

//消息监听

/*container.setMessageListener(new ChannelAwareMessageListener() {

@Override

public void onMessage(Message message, Channel channel) throws Exception {

String msg = new String(message.getBody());

System.err.println("----------消费者: " + msg);

}

});*/

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

container.setMessageListener(adapter);

MessageListenerAdapter 适配器类,熟悉适配器模式的朋友肯定了解适配器模式的话,可以通过适配器,适配自己的实现,这里我们适配自定义的MessageDelegate类。我们就可以不采用监听的方式,采用适配的方式。

自定义MessageDelegate

public class MessageDelegate {

public void handleMessage(byte[] messageBody) {

System.err.println(“默认方法, 消息内容:” + new String(messageBody));

}

}

MessageDelegate类中,方法名与参数handleMessage(byte[] messageBody)是固定的。为什么呢?

MessageListenerAdapter源码分析

我们来看下MessageListenerAdapter底层代码

MessageListenerAdapter

MessageListenerAdapter类中

public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = “handleMessage”;

默认方法名就是叫handleMessage。当然也可以自己去指定设置。通过messageListenerAdapter的代码我们可以看出如下核心属性

  • defaultListenerMethod默认监听方法名称:用于设置监听方法名称

  • Delegate 委托对象:实际真实的委托对象,用于处理消息

  • queueOrTagToMethodName 队列标识与方法名称组成集合

  • 可以一一进行队列与方法名称的匹配

  • 队列和方法名称绑定,即指定队列里的消息会被绑定的方法所接受处理

测试一下默认使用的handleMessage方法。启动ApplicationTests类,运行testSendMessage()测试方法。

打印消息

自定义方法名

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

adapter.setDefaultListenerMethod(“consumeMessage”);

container.setMessageListener(adapter);

修改MessageDelegate()类

public class MessageDelegate {

public void consumeMessage(byte[] messageBody) {

System.err.println(“字节数组方法, 消息内容:” + new String(messageBody));

}

}

自定义TextMessageConverter转换器

public class TextMessageConverter implements MessageConverter {

@Override

public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {

return new Message(object.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());

}

return message.getBody();

}

}

修改RabbitMQConfig类

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

adapter.setDefaultListenerMethod(“consumeMessage”);

adapter.setMessageConverter(new TextMessageConverter());

container.setMessageListener(adapter);

修改MessageDelegate类

public class MessageDelegate {

public void consumeMessage(String messageBody) {

System.err.println(“字符串方法, 消息内容:” + messageBody);

}

}

运行testSendMessage4Text()测试方法

@Test

public void testSendMessage2() throws Exception {

//1 创建消息

MessageProperties messageProperties = new MessageProperties();

messageProperties.setContentType(“text/plain”);

Message message = new Message(“mq 消息1234”.getBytes(), messageProperties);

rabbitTemplate.send(“topic001”, “spring.abc”, message);

rabbitTemplate.convertAndSend(“topic001”, “spring.amqp”, “hello object message send!”);

rabbitTemplate.convertAndSend(“topic002”, “rabbit.abc”, “hello object message send!”);

}

注意:在发消息的时候,必须符合自己的转换器。

打印结果

打印结果

6.1.2 适配器使用方式2

自定义队列名称和方法名称。

/**

  • 2 适配器方式: 我们的队列名称 和 方法名称 也可以进行一一的匹配

  • /

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

adapter.setMessageConverter(new TextMessageConverter());

Map<String, String> queueOrTagToMethodName = new HashMap<>();

queueOrTagToMethodName.put(“queue001”, “method1”);

queueOrTagToMethodName.put(“queue002”, “method2”);

adapter.setQueueOrTagToMethodName(queueOrTagToMethodName);

container.setMessageListener(adapter);

public class MessageDelegate {

public void method1(String messageBody) {

System.err.println(“method1 收到消息内容:” + new String(messageBody));

}

public void method2(String messageBody) {

System.err.println(“method2 收到消息内容:” + new String(messageBody));

}

}

运行 测试方法

@Test

public void testSendMessage4Text() throws Exception {

//1 创建消息

MessageProperties messageProperties = new MessageProperties();

messageProperties.setContentType(“text/plain”);

Message message = new Message(“mq 消息1234”.getBytes(), messageProperties);

rabbitTemplate.send(“topic001”, “spring.abc”, message);

rabbitTemplate.send(“topic002”, “rabbit.abc”, message);

}

运行结果:

打印结果

7. MessageConverter消息转换器


我们在进行发送消息的时候,正常情况下消息体为二进制的数据方式进行传输,如果希望内部帮我们进行转换,或者指定自定义的转换器,就需要用到MessageConverter

  • 自定义常用转换器:MessageConverter,一般来讲都需要实现这个接口

  • 重写下面两个方法:

toMessage:java对象转换为Message

fromMessage:Message对象转换为java对象

  • Json转换器:Jackson2JsonMessageConverter:可以进行Java对象的转换功能

  • DefaultJackson2JavaTypeMapper映射器:可以进行java对象的映射关系

  • 自定义二进制转换器:比如图片类型、PDF、PPT、流媒体

7.1 代码演示

其实我们在介绍MessageListenerAdapter的时候,中间就介绍到了TextMessageConverter转换器,将二进制数据转换成字符串数据。

7.1.1 添加json格式的转换器

修改RabbitMQConfig类

// 1.1 支持json格式的转换器

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

adapter.setDefaultListenerMethod(“consumeMessage”);

//重点,加入json格式的转换器 json对应Map对象

Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();

最后的话

无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!

部分截图:
在这里插入图片描述

late.send(“topic002”, “rabbit.abc”, message);

}

运行结果:

打印结果

7. MessageConverter消息转换器


我们在进行发送消息的时候,正常情况下消息体为二进制的数据方式进行传输,如果希望内部帮我们进行转换,或者指定自定义的转换器,就需要用到MessageConverter

  • 自定义常用转换器:MessageConverter,一般来讲都需要实现这个接口

  • 重写下面两个方法:

toMessage:java对象转换为Message

fromMessage:Message对象转换为java对象

  • Json转换器:Jackson2JsonMessageConverter:可以进行Java对象的转换功能

  • DefaultJackson2JavaTypeMapper映射器:可以进行java对象的映射关系

  • 自定义二进制转换器:比如图片类型、PDF、PPT、流媒体

7.1 代码演示

其实我们在介绍MessageListenerAdapter的时候,中间就介绍到了TextMessageConverter转换器,将二进制数据转换成字符串数据。

7.1.1 添加json格式的转换器

修改RabbitMQConfig类

// 1.1 支持json格式的转换器

MessageListenerAdapter adapter = new MessageListenerAdapter(new MessageDelegate());

adapter.setDefaultListenerMethod(“consumeMessage”);

//重点,加入json格式的转换器 json对应Map对象

Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();

最后的话

无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!

部分截图:
[外链图片转存中…(img-aiXIzVp2-1714736983615)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 25
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值