RabbitMq交换机/队列的创建源码分析

188 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

概述

SpringBoot整合RabbitMq思考一个问题,为什么只用在配置类里配置一下交换机/队列/绑定关系,在项目启动后就会自动创建对应的交换机/队列/绑定关系?

@Configuration
public class RabbitConfig {
    @Bean
    public FanoutExchange exchange(){
        return new FanoutExchange("TEST");
    }
}

创建交换机/队列/绑定关系的时机到底是什么时候?如何创建的?

测试

  1. 配置如上bean后,启动项目,观察Rabbitmq控制台是否创建该交换机
    1. 并未创建
  1. 发送一条消息到该交换机,观察是否能成功
package com.zy.rabbitmq.controller;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: Zy
 * @Date: 2022/3/14 15:04
 */
@RestController
@RequestMapping("test")
public class TestController {
    @Autowired
    RabbitTemplate rabbitTemplate;

    @GetMapping
    public void test(){
        rabbitTemplate.convertAndSend("TEST","","HHHH");
    }
}

再次观察管理控制台,惊奇的发现: 有了!

猜想

那么其实并不是在项目启动后就进行的交换机的创建吗?而是在发送消息时才创建的交换机吗?

源码在哪里?

参考文章: cloud.tencent.com/developer/a…

源码解析

上篇文章中提到了一个很重要的类: RabbitAdmin

这个类封装了对队列/交换机/绑定关系等一系列的增删改查操作

而在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;
		}

        // 获取spring注册的bean
		this.logger.debug("Initializing declarations");
		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.manualDeclarables.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;
		});
		if (this.manualDeclarables.size() > 0) {
			synchronized (this.manualDeclarables) {
				this.logger.debug("Redeclaring manually declared Declarables");
				for (Declarable dec : this.manualDeclarables.values()) {
					if (dec instanceof Queue) {
						declareQueue((Queue) dec);
					}
					else if (dec instanceof Exchange) {
						declareExchange((Exchange) dec);
					}
					else {
						declareBinding((Binding) dec);
					}
				}
			}
		}
		this.logger.debug("Declarations finished");

	}

可以看到从Spring中获取所有注册的交换机/队列/绑定关系的bean,然后进行创建.

而还有另外一个方法afterPropertiesSet(),这个方法就很常见了,在Spring中这个方法在Bean初始化完会进行调用:

而就在这个方法里调用上面的initialize() 方法,进行了交换机的初始化绑定操作

/**
	 * If {@link #setAutoStartup(boolean) autoStartup} is set to true, registers a callback on the
	 * {@link ConnectionFactory} to declare all exchanges and queues in the enclosing application context. If the
	 * callback fails then it may cause other clients of the connection factory to fail, but since only exchanges,
	 * queues and bindings are declared failure is not expected.
	 *
	 * @see InitializingBean#afterPropertiesSet()
	 * @see #initialize()
	 */
	@Override
	public void afterPropertiesSet() {

		synchronized (this.lifecycleMonitor) {

			if (this.running || !this.autoStartup) {
				return;
			}

			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);

			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;

		}
	}

那么这个调用链就很清楚了

RabbitAdmin创建后调用afterPropertiesSet方法,afterPropertiesSet方法调用initialize()方法,在initialize方法内部进行了交换机的创建操作

那么RabbitAdmin是什么时候创建的呢?

对于SpringBoot来说,当然是自动配置类里创建的 RabbitAutoConfiguration

@Bean
@ConditionalOnSingleCandidate(ConnectionFactory.class)
@ConditionalOnProperty(prefix = "spring.rabbitmq", name = "dynamic", matchIfMissing = true)
@ConditionalOnMissingBean
public AmqpAdmin amqpAdmin(ConnectionFactory connectionFactory) {
    return new RabbitAdmin(connectionFactory);
}

创建之后,就会进入afterPropertiesSet方法

对于afterPropertiesSet方法,还有一点要注意,并不是进入afterPropertiesSet就会调用initialize,而是添加了一个连接监听器,当有一个新的连接创建时,才会进入initialize方法.

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);
    }

});

总结

  1. 对于SpringBoot的源码分析,一般都可以从自动配置类中找到源码的入口,结合整个框架的结构debug,才能分析源码的走向.
  2. RabbitMq项目启动后创建对应的交换机/队列/绑定关系看似简单,其实也经过了复杂的过程,特别是看源码的过程中还有很多Spring加载bean的方法,比较吃力,需要加强Spring源码的学习和阅读能力.