文章目录
一、问题描述
- 项目代码中需要连接多个
RabbitMQ
,然后分别在不同的RabbitMQ
中创建交换机、队列以及进行关系绑定;实现代码使用了注解@QueueBinding
,但是在每个RabbitMQ
中都创建了其他的RabbitMQ
的交换机和队列
二、项目代码
- RabbitMqConfig部分代码
@Bean(name = "virConnectionFactory")
@Primary
public ConnectionFactory virConnectionFactory() {
return connectionFactory(host, port, password, username, virtualHost);
}
@Bean(name = "sjVirConnectionFactory")
public ConnectionFactory sjVirConnectionFactory() {
return connectionFactory(sjHost, sjPort, sjPassword, sjUsername, sjVirtualHost);
}
@Bean(name = "virFactory")
@Primary
public SimpleRabbitListenerContainerFactory virFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
@Qualifier("virConnectionFactory") ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
@Bean(name = "sjVirFactory")
public SimpleRabbitListenerContainerFactory sjVirFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer,
@Qualifier("sjVirConnectionFactory") ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
configurer.configure(factory, connectionFactory);
return factory;
}
@Bean(name = "virRabbitTemplate")
public RabbitTemplate virRabbitTemplate(@Qualifier("virConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
return rabbitTemplate;
}
@Bean(name = "sjVirRabbitTemplate")
public RabbitTemplate sjVirRabbitTemplate(@Qualifier("sjVirConnectionFactory") ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
return rabbitTemplate;
}
@Bean(value = "virRabbitAdmin")
public RabbitAdmin virRabbitAdmin(@Qualifier("virRabbitTemplate") RabbitTemplate rabbitTemplate) {
return new RabbitAdmin(rabbitTemplate);
}
@Bean(value = "sjVirRabbitAdmin")
public RabbitAdmin sjVirRabbitAdmin(@Qualifier("sjVirRabbitTemplate") RabbitTemplate rabbitTemplate) {
return new RabbitAdmin(rabbitTemplate);
}
Listener
监听类部分代码(这部分代码有问题,下文中第五点解释说明)
@RabbitListener(bindings = @QueueBinding(value =
@Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET)
, exchange = @Exchange(value = olesExchangeName, type = "topic")
, key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY)
, containerFactory = "sjVirFactory")
public void afterEventWarn2Sheet(String msg, Channel channel, Message message) throws IOException {
//...
}
三、以上代码的思路
- 根据
配置
创建ConnectionFactory
- 根据
ConnectionFactory
创建SimpleRabbitListenerContainerFactory
- 根据
ConnectionFactory
创建RabbitTemplate
- 根据
RabbitTemplate
创建RabbitAdmin
注:也可以使用
ConnectionFactory
创建RabbitAdmin
,写法不同,不必要纠结
四、ConnectionFactory、SimpleRabbitListenerContainerFactory、RabbitTemplate、RabbitAdmin的作用
ConnectionFactory
:连接工厂,负责创建消费端与rabbitMQ
之间的链接SimpleRabbitListenerContainerFactory
:负责监听的任务RabbitTemplate
:负责对rabbitMQ
各种操作,如:用于创建、绑定、删除队列与交换机,发送消息等RabbitAdmin
:用于创建、绑定、删除队列与交换机,发送消息等;他的作用跟RabbitTemplate
差不多,只不过他对RabbitTemplate
进行了封装,内部的一些具体的操作还是依赖RabbitTemplate
来完成的
五、正确的写法
- 目前的错误写法
@RabbitListener(bindings = @QueueBinding(value =
@Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET)
, exchange = @Exchange(value = olesExchangeName, type = "topic")
, key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY)
, containerFactory = "sjVirFactory")
public void afterEventWarn2Sheet(String msg, Channel channel, Message message) throws IOException {
//...
}
注: 上述代码中没有指定
admins
,也就是RabbitAdmin
,导致每个RabbitAdmin
的初始化Bean
获取所有交换机和队列时,没有过滤掉不属于自己的交换机和队列(见下文第六点的解释说明)
- 正确的写法
@RabbitListener(bindings = @QueueBinding(value =
@Queue(value= RabbitMQReceiveBind.QUEUE_NAME_AFTER_EVENT_AUTO_DELAY_TLES_SHEET , admins = "sjVirRabbitAdmin")
, exchange = @Exchange(value = olesExchangeName, type = "topic", admins = "sjVirRabbitAdmin")
, key = RabbitMQSendConfig.AFTER_EVENT_AUTO_DELAY_TLES_SHEET_ROUTEKEY, admins = "sjVirRabbitAdmin")
, containerFactory = "sjVirFactory")
注:
@QueueBinding
、@Queue
、@Exchange
都要进行绑定admins
;
误区:一开始陷入了一个误区,认为声明了
containerFactory = "sjVirFactory"
,就可以找到对应的数据源了,其实这个containerFactory
只是在监听环节起到了作用,创建时,与它无关,它是属于@RabbitListener
的参数值,而不是@QueueBinding
的参数值
六、分析源码
org.springframework.amqp.rabbit.core.RabbitAdmin#initialize
的关键部分
public void initialize() {
// 反射获取当前所有Exchange、Queue、Binding对象
Collection<Exchange> contextExchanges = new LinkedList<Exchange>(
this.applicationContext.getBeansOfType(Exchange.class).values());
Collection<Queue> contextQueues = new LinkedList<Queue>(
this.applicationContext.getBeansOfType(Queue.class).values());
Collection<Binding> contextBindings = new LinkedList<Binding>(
this.applicationContext.getBeansOfType(Binding.class).values());
Collection<DeclarableCustomizer> customizers =
this.applicationContext.getBeansOfType(DeclarableCustomizer.class).values();
processDeclarables(contextExchanges, contextQueues, contextBindings);
// 进行过滤
final Collection<Exchange> exchanges = filterDeclarables(contextExchanges, customizers);
final Collection<Queue> queues = filterDeclarables(contextQueues, customizers);
final Collection<Binding> bindings = filterDeclarables(contextBindings, customizers);
}
注:可以看到通过反射获取了所有的
Exchange
、Queue
、Binding
,以及通过方法filterDeclarables
进行了过滤
- 方法:
filterDeclarable
、declarableByMe
private <T extends Declarable> Collection<T> filterDeclarables(Collection<T> declarables,
Collection<DeclarableCustomizer> customizers) {
return declarables.stream()
// 通过declarableByMe进行过滤
.filter(dec -> dec.shouldDeclare() && declarableByMe(dec))
.map(dec -> {
if (customizers.isEmpty()) {
return dec;
}
AtomicReference<T> ref = new AtomicReference<>(dec);
customizers.forEach(cust -> ref.set((T) cust.apply(ref.get())));
return ref.get();
})
.collect(Collectors.toList());
}
private <T extends Declarable> boolean declarableByMe(T dec) {
return (dec.getDeclaringAdmins().isEmpty() && !this.explicitDeclarationsOnly) // NOSONAR boolean complexity
|| dec.getDeclaringAdmins().contains(this)
// dec.getDeclaringAdmins().contains(this.beanName))这里就是判断admins是否包含当前Bean
|| (this.beanName != null && dec.getDeclaringAdmins().contains(this.beanName));
}
七、最终结论
- 使用注解
@QueueBinding
进行多数据源
队列绑定时,需要指定admins
,即RabbitAdmin