SpringAMQP之RabbitAdmin类详解

作用

用于将注入Spring容器的交换器, 队列和绑定, 自动注册到RabbitMQ Broker上

源码剖析

首先, 看一下RabbitAdmin的类结构图, 我们发现了一个熟悉的面孔(如果不熟悉, 证明你的Spring用的还是少啊(┬_┬)), 没错就是InitializingBean, 这是一个Spring Bean生命周期的常见扩展点, 也是我们此次分析源码的起点
RabbitAdmin类结构图

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项目, 小伙伴自己动手写一个哈☺

在这里插入图片描述

  • …等了半天都没到断点, 神马情况

img

  • 看看addConnectionListener都做了什么? ConnectionFactory是个接口唉, 实现类是哪一个啊?

    你要是想快速知道的话, 可以选择debug一下, 当然你也可以从Bean注入的时候下手, 这里我直接说了啊!

  • CachingConnectionFactoryaddConnectionListener方法

@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发送消息试试, 我这里就不尝试了

好了, 先这样吧, 下次见

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值