作用
用于将注入Spring容器的交换器, 队列和绑定, 自动注册到RabbitMQ Broker上
源码剖析
首先, 看一下RabbitAdmin
的类结构图, 我们发现了一个熟悉的面孔(如果不熟悉, 证明你的Spring用的还是少啊(┬_┬)), 没错就是InitializingBean
, 这是一个Spring Bean生命周期的常见扩展点, 也是我们此次分析源码的起点
afterPropertiesSet方法
查看一下RabbitAdmin
中对InitializingBean
方法afterPropertiesSet()
方法的实现, 如下
@Override
public void afterPropertiesSet() {
synchronized (this.lifecycleMonitor) {
if (this.running || !this.autoStartup) {
return;
}
// 设置RetryTemplate, 具有重试功能的请求工具类
if (this.retryTemplate == null && !this.retryDisabled) {
this.retryTemplate = new RetryTemplate();
this.retryTemplate.setRetryPolicy(new SimpleRetryPolicy(DECLARE_MAX_ATTEMPTS));
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(DECLARE_INITIAL_RETRY_INTERVAL);
backOffPolicy.setMultiplier(DECLARE_RETRY_MULTIPLIER);
backOffPolicy.setMaxInterval(DECLARE_MAX_RETRY_INTERVAL);
this.retryTemplate.setBackOffPolicy(backOffPolicy);
}
if (this.connectionFactory instanceof CachingConnectionFactory &&
((CachingConnectionFactory) this.connectionFactory).getCacheMode() == CacheMode.CONNECTION) {
this.logger.warn("RabbitAdmin auto declaration is not supported with CacheMode.CONNECTION");
return;
}
// Prevent stack overflow...
final AtomicBoolean initializing = new AtomicBoolean(false);
// 设置ConnectFactory回调方法
this.connectionFactory.addConnectionListener(connection -> {
if (!initializing.compareAndSet(false, true)) {
// If we are already initializing, we don't need to do it again...
return;
}
try {
/*
* ...but it is possible for this to happen twice in the same ConnectionFactory (if more than
* one concurrent Connection is allowed). It's idempotent, so no big deal (a bit of network
* chatter). In fact it might even be a good thing: exclusive queues only make sense if they are
* declared for every connection. If anyone has a problem with it: use auto-startup="false".
*/
if (this.retryTemplate != null) {
this.retryTemplate.execute(c -> {
// 这里是重点奥.....
initialize();
return null;
});
}
else {
// 这里是重点奥.....
initialize();
}
}
finally {
initializing.compareAndSet(true, false);
}
});
this.running = true;
}
}
这个方法里主要做了两件事:
- 设置RetryTemplate, 具有重试功能的请求工具类
- 设置ConnectionFactory回调方法, 该回调方法里调用了
initialize()
方法
initialize方法
接下来我们看一下initialize()
方法里都做了什么事情
/**
* Declares all the exchanges, queues and bindings in the enclosing application context, if any. It should be safe
* (but unnecessary) to call this method more than once.
*/
@Override // NOSONAR complexity
public void initialize() {
if (this.applicationContext == null) {
this.logger.debug("no ApplicationContext has been set, cannot auto-declare Exchanges, Queues, and Bindings");
return;
}
this.logger.debug("Initializing declarations");
// 这里构建了几个集合, 放置已经注入到Spring IOC容器中的队列, 交换器, 绑定等
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);
for (Exchange exchange : exchanges) {
if ((!exchange.isDurable() || exchange.isAutoDelete()) && this.logger.isInfoEnabled()) {
this.logger.info("Auto-declaring a non-durable or auto-delete Exchange ("
+ exchange.getName()
+ ") durable:" + exchange.isDurable() + ", auto-delete:" + exchange.isAutoDelete() + ". "
+ "It will be deleted by the broker if it shuts down, and can be redeclared by closing and "
+ "reopening the connection.");
}
}
for (Queue queue : queues) {
if ((!queue.isDurable() || queue.isAutoDelete() || queue.isExclusive()) && this.logger.isInfoEnabled()) {
this.logger.info("Auto-declaring a non-durable, auto-delete, or exclusive Queue ("
+ queue.getName()
+ ") durable:" + queue.isDurable() + ", auto-delete:" + queue.isAutoDelete() + ", exclusive:"
+ queue.isExclusive() + ". "
+ "It will be redeclared if the broker stops and is restarted while the connection factory is "
+ "alive, but all messages will be lost.");
}
}
if (exchanges.size() == 0 && queues.size() == 0 && bindings.size() == 0) {
this.logger.debug("Nothing to declare");
return;
}
// 执行声明队列, 交换器等的请求
this.rabbitTemplate.execute(channel -> {
declareExchanges(channel, exchanges.toArray(new Exchange[exchanges.size()]));
declareQueues(channel, queues.toArray(new Queue[queues.size()]));
declareBindings(channel, bindings.toArray(new Binding[bindings.size()]));
return null;
});
this.logger.debug("Declarations finished");
}
从这个方法的源码中我们可以看到, 这个方法主要做了两件事:
- 从Spring容器中获取已经注入的队列, 交换器, 绑定等
- 向rabbitmq的broker发送声明这些组件的请求
最后一个问题
经过前面的讲述, 我们大体明白了RabbitAdmin
的作用和原理, 但是还有一个问题我们需要研究一下:
ConnectionFactory回调方法的调用时机? 也就是说什么时候会向rabbitmq broker声明队列, 交换器这些组件呢?
思路
找不到在哪调用的啊, 没关系, 我们可以debug一下
-
在回调方法内打一个断点
-
启动一下Spring容器
demo是我提前写好的啊, 就是一个集成了
spring-rabbit
的maven项目, 小伙伴自己动手写一个哈☺
- …等了半天都没到断点, 神马情况
-
看看
addConnectionListener
都做了什么?ConnectionFactory
是个接口唉, 实现类是哪一个啊?你要是想快速知道的话, 可以选择debug一下, 当然你也可以从Bean注入的时候下手, 这里我直接说了啊!
-
CachingConnectionFactory
的addConnectionListener
方法
@Override
public void addConnectionListener(ConnectionListener listener) {
super.addConnectionListener(listener); // handles publishing sub-factory
// 如果连接已经激活,我们假设新的listener想要被通知 (直接翻译过来的啊)
if (this.connection.target != null) {
listener.onCreate(this.connection);
}
}
看到这里, 我也恍然大悟, spring容器启动但是没创建到rabbitmq的连接啊, 所以没调用, 小伙伴可以往rabbitmq发送消息试试, 我这里就不尝试了
好了, 先这样吧, 下次见