-RabbitMQ之Spring客户端源码

Posted Mr-昊哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了-RabbitMQ之Spring客户端源码相关的知识,希望对你有一定的参考价值。

目录

第十章-RabbitMQ之Spring客户端源码

        1. 前言

2. 客户端消费代码

2.1 消费的实现方式

2.2 消费中注解解释

2.3 推测Spring实现过程

3.MQ消费源码分析

3.1 集成SpringBoot 启动过程

3.2 Broker投递消息给客户端过程

3.3 客户端消费过程

4. 总结


第十章-RabbitMQ之Spring客户端源码

1. 前言

经过前面前面的学习,我们已经掌握了rabbitmq的基本用法,高级用法延迟队列、死信队列等,已经研究过了amqp-client的java客户端源码,由于我们在使用的时候,一般还是以SpringBoot为主,那经过Spring封装后的客户端源码是是如何实现的呢?

同学们最好需要有研读过 Spring源码及SpringBoot 源码的经验,会更好衔接一下,不过关系也不大。

由于Spring 体系的庞大,封装的rabbit客户端实现的功能也很多,例 创建连接、生产者推送消息,事务,消费者消费等等内容,那我们这次只抽取rabbitmq消费的部分,进行研读。

集成starter

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

2. 客户端消费代码

2.1 消费的实现方式

如之前我们提到的集成SpringBoot后的使用方式:

@RabbitHandler
@RabbitListener(queues = "SolarWaterHeater")
 @RabbitHandler
 @RabbitListener(queuesToDeclare = @Queue("SolarWaterHeater"))
    @RabbitHandler
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("SolarWaterHeater-RedWine"),
            key = "REDWINE",
            exchange = @Exchange(value = "routing-exchange", type = ExchangeTypes.DIRECT, durable = "false")))

2.2 消费中注解解释

这里面出现了两个注解

第一个:RabbitHandler 看下它的解释:

* Annotation that marks a method to be the target of a Rabbit message
* listener within a class that is annotated with @link RabbitListener.

如果一天类上面的注解是RabbitListener,那RabbitHandler标注的方法,即是Rabbit的消息监听者。

@Target( ElementType.METHOD, ElementType.ANNOTATION_TYPE )
这个注解只能标注到Method

第二个 RabbitListener

1. Annotation that marks a method to be the target of a Rabbit message listener 

标注的方法是一个消息监听者

2. When defined at the class level, a single message listener container is used to
* service all methods annotated with @code @RabbitHandler

如果标注到类上,那标注RabbitHandler的方法即是消息监听

链一个:@RabbitListener和@RabbitHandler的使用_sliver1836的博客-CSDN博客

2.3 推测Spring实现过程

所以,我们后续的源码分析即基于此两个注解开展。

在开始看代码之前,我们先想一想,我们之前的使用java amqp客户端开发消费逻辑的过程,

1、创建连接

2、创建Channel

3、声明队列、Exchange、绑定关系

4、监听方法实现 继承DefaultConumer

5、basic.Consume 注册到Broker

6、Broker消息推送,监听方法实现消费

那现在Spring就靠两个注解就帮我们实现了消息的消费,有没有很神奇。顿时感叹程序猿越来越幸福,写代码如此简单了呢?但有利就有弊,Spring帮我们封装的太多,而我们知道的底层却太少了。

闲话少说,到这,大家想一下,如果让你写个注解,就去实现上面6个步骤的内容,你该如何去做呢?

开发自定义注解大家都应该做过,大致的逻辑应该是不是可以,在系统启动的时候,我们就会抓取到标注注解的方法,有此类的方法时,我们认为需要使用mq,我们在后端服务中依次的去执行上面中的6个步骤。这样把注解的方法实现了监听,后续监听消息进行消费。

这里只是一个大概的推测,大家自己自行发挥想像。

3.MQ消费源码分析

从哪入手呢?首先点开 RabbitListener 的源码,然后Download源码。

到这个界面:

我们不再研读RabbitListener这个注解的功能了,大家自己看。

然后紧接着看到 RabbitListenerAnnotationBeanPostProcessor

这个类有什么特点呢?首先是处理RabbitListener 的处理类,然后呢是一个BeanPostProcessor继承了BeanPostProcessor 接口-读过Spring源码的同学,肯定就能得到最有效的信息了,这个类会在系统初始化的时候,执行postProcessAfterInitialization()这个方法。如果没读过Spring源码的话就先跟着节奏走吧。

从这开始了我们的切入。

3.1 集成SpringBoot 启动过程

接着上面的步骤呢,我们往上简单倒一下,

首先 这是一个SpringBoot 项目,通过SpringBoot 的启动类的Main 方法进行启动,然后开始扫描各个组件,初始化各种信息,这个不再细聊。【需要读SpringBoot源码】

其次呢,SpringBoot 只是对Spring 的封装,还是需要回到Spring 的类初始化的过程中去。【需要读Spring源码】

如下呢,即Spring 的核心初始化方法:无论Spring 再怎么升级,这几个核心方法基本不会怎么变化了,这里面我们找到 【registerBeanPostProcessors】,从这里面就会触发到我们上面所说的-

RabbitListenerAnnotationBeanPostProcessor

@Override
	public void refresh() throws BeansException, IllegalStateException 
		synchronized (this.startupShutdownMonitor) 
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try 
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			

			catch (BeansException ex) 
				if (logger.isWarnEnabled()) 
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			

			finally 
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			
		
	

随着Spring 的启动,开始触发到了RabbitListenerAnnotationBeanPostProcessor 中的 

postProcessAfterInitialization 方法。

代码:

这就很好解释了,bean 就是我们的消费类,

解析到了 标有注解的方法 @RabbitListener,然后进行处理。processAmqpListener

@Override
	public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException 
		Class<?> targetClass = AopUtils.getTargetClass(bean);
		final TypeMetadata metadata = this.typeCache.computeIfAbsent(targetClass, this::buildMetadata);
		for (ListenerMethod lm : metadata.listenerMethods) 
			for (RabbitListener rabbitListener : lm.annotations) 
				processAmqpListener(rabbitListener, lm.method, bean, beanName);
			
		
		if (metadata.handlerMethods.length > 0) 
			processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
		
		return bean;
	
protected void processAmqpListener(RabbitListener rabbitListener, Method method, Object bean, String beanName) 
        // 对应的消费方法
		Method methodToUse = checkProxy(method, bean);
        //封装对象
		MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
		endpoint.setMethod(methodToUse);
        // 继续处理
		processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
	

继续:

protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,
			Object adminTarget, String beanName) 
		endpoint.setBean(bean);
		endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
		endpoint.setId(getEndpointId(rabbitListener));
		endpoint.setQueueNames(resolveQueues(rabbitListener));
		endpoint.setConcurrency(resolveExpressionAsStringOrInteger(rabbitListener.concurrency(), "concurrency"));
		endpoint.setBeanFactory(this.beanFactory);
		endpoint.setReturnExceptions(resolveExpressionAsBoolean(rabbitListener.returnExceptions()));
		Object errorHandler = resolveExpression(rabbitListener.errorHandler());
		if (errorHandler instanceof RabbitListenerErrorHandler) 
			endpoint.setErrorHandler((RabbitListenerErrorHandler) errorHandler);
		
		else if (errorHandler instanceof String) 
			String errorHandlerBeanName = (String) errorHandler;
			if (StringUtils.hasText(errorHandlerBeanName)) 
				endpoint.setErrorHandler(this.beanFactory.getBean(errorHandlerBeanName, RabbitListenerErrorHandler.class));
			
		
		else 
			throw new IllegalStateException("error handler mut be a bean name or RabbitListenerErrorHandler, not a "
					+ errorHandler.getClass().toString());
		
		String group = rabbitListener.group();
		if (StringUtils.hasText(group)) 
			Object resolvedGroup = resolveExpression(group);
			if (resolvedGroup instanceof String) 
				endpoint.setGroup((String) resolvedGroup);
			
		
		String autoStartup = rabbitListener.autoStartup();
		if (StringUtils.hasText(autoStartup)) 
			endpoint.setAutoStartup(resolveExpressionAsBoolean(autoStartup));
		

		endpoint.setExclusive(rabbitListener.exclusive());
		String priority = resolve(rabbitListener.priority());
		if (StringUtils.hasText(priority)) 
			try 
				endpoint.setPriority(Integer.valueOf(priority));
			
			catch (NumberFormatException ex) 
				throw new BeanInitializationException("Invalid priority value for " +
						rabbitListener + " (must be an integer)", ex);
			
		
        // 以上 前面都完成了对 MethodRabbitListenerEndpoint 对象的封装,封装的也都是注解中的属性
    //此方法内部实际没执行 跳过
		resolveAdmin(endpoint, rabbitListener, adminTarget);
    //跳过
		RabbitListenerContainerFactory<?> factory = resolveContainerFactory(rabbitListener, adminTarget, beanName);
    // 属性填充 放入List ,不重要

		this.registrar.registerEndpoint(endpoint, factory);
	

程序回转:

这里面来到一个

public void afterSingletonsInstantiated() 方法,这是由于实现了接口SmartInitializingSingleton, 后续得到了处理。

这里面会涉及到两个类:

1. RabbitListenerEndpointRegistrar

2. RabbitListenerEndpointRegistry

有没有长得很像,这里面是把 RabbitListenerEndpointRegistry 手工注册到了RabbitListenerEndpointRegistrar 里面,然后进行了一系列初始化,

这里面不再详细展开了,但这个RabbitListenerEndpointRegistry 很重要,后面还会涉及到它

 RabbitListenerEndpointRegistry 实现了一个Lifecycle接口,后续会调用到它的实现start()

将对应的消费Class 做好了封装 ,返回,继续Spring的初始化过程。

来到Spring核心流程 

finishRefresh();
	/**
	 * Finish the refresh of this context, invoking the LifecycleProcessor's
	 * onRefresh() method and publishing the
	 * @link org.springframework.context.event.ContextRefreshedEvent.
	 */
	protected void finishRefresh() 
		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));

		// Participate in LiveBeansView MBean, if active.
		LiveBeansView.registerApplicationContext(this);
	

其中第三个方法

getLifecycleProcessor().onRefresh();

这个方法是获取 lifecycle的处理器,进行lifecycle接口实现类的处理,这就呼应到了上面的 RabbitListenerEndpointRegistry ,他实现了lifecycle的接口。

最终一番流转终于到了 这个Registry处理逻辑中:

	@Override
	public void start() 
		for (MessageListenerContainer listenerContainer : getListenerContainers()) 
			startIfNecessary(listenerContainer);
		
	
	/**
	 * Start the specified @link MessageListenerContainer if it should be started
	 * on startup or when start is called explicitly after startup.
	 * @param listenerContainer the container.
	 * @see MessageListenerContainer#isAutoStartup()
	 */
	private void startIfNecessary(MessageListenerContainer listenerContainer) 
		if (this.contextRefreshed || listenerContainer.isAutoStartup()) 
			listenerContainer.start();
		
	
MessageListenerContainer 也是在上面afterSingletonsInstantiated 处理好的,现在要启动这个监听者容器。

来到了 AbstractMessageListenerContainer 中的启动方法:

/**
	 * Start this container.
	 * @see #doStart
	 */
	@Override
	public void start() 
		if (isRunning()) 
			return;
		
		if (!this.initialized) 
			synchronized (this.lifecycleMonitor) 
				if (!this.initialized) 
					afterPropertiesSet();
				
			
		
		try 
			logger.debug("Starting Rabbit listener container.");
			configureAdminIfNeeded();
			checkMismatchedQueues();
			doStart();
		
		catch (Exception ex) 
			throw convertRabbitAccessException(ex);
		
		finally 
			this.lazyLoad = false;
		
	
configureAdminIfNeeded() 获取RabbitAdmin 
checkMismatchedQueues() 这个方法就很关键了,运行到此时打开我们的抓包工具,这里面开始创建Connection了。
protected void checkMismatchedQueues() 
		if (this.mismatchedQueuesFatal && this.amqpAdmin != null) 
			try 
				this.amqpAdmin.initialize();
			
			catch (AmqpConnectException e) 
				logger.info("Broker not available; cannot check queue declarations");
			
			catch (AmqpIOException e) 
				if (RabbitUtils.isMismatchedQueueArgs(e)) 
					throw new FatalListenerStartupException("Mismatched queues", e);
				
				else 
					logger.info("Failed to get connection during start(): " + e);
				
			
		
		else 
			try 
                // 创建连接方法
				Connection connection = getConnectionFactory().createConnection(); // NOSONAR
				if (connection != null) 
					connection.close();
				
			
			catch (Exception e) 
				logger.info("Broker not available; cannot force queue declarations during start: " + e.getMessage());
			
		
	

有没有很熟悉

Connection connection = getConnectionFactory().createConnection(); 
@Override
	public final Connection createConnection() throws AmqpException 
		if (this.stopped) 
			throw new AmqpApplicationContextClosedException(
					"The ApplicationContext is closed and the ConnectionFactory can no longer create connections.");
		
		synchronized (this.connectionMonitor) 
			if (this.cacheMode == CacheMode.CHANNEL) 
				if (this.connection.target == null) 
					this.connection.target = super.createBareConnection();
					// invoke the listener *after* this.connection is assigned
					if (!this.checkoutPermits.containsKey(this.connection)) 
						this.checkoutPermits.put(this.connection, new Semaphore(this.channelCacheSize));
					
					this.connection.closeNotified.set(false);
					getConnectionListener().onCreate(this.connection);
				
				return this.connection;
			
			else if (this.cacheMode == CacheMode.CONNECTION) 
				return connectionFromCache();
			
		
		return null; // NOSONAR - never reach here - exceptions
	

运行完此步,如上的代码中,两个重要的点:

1. 此步直接就创建了Connection、

this.connection.target = super.createBareConnection();

看下抓包:

2. 继续这一步也很关键,创建完连接后,会把接下来的 Exchange、Queue、绑定关系根据注解配置中的内容,该创建的都创建一遍。

getConnectionListener().onCreate(this.connection);

直接运行到了

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;
		

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

		processLegacyCollections(contextExchanges, contextQueues, contextBindings);
		processDeclarables(contextExchanges, contextQueues, contextBindings);

		final Collection<Exchange> exchanges = filterDeclarables(contextExchanges);
		final Collection<Queue> queues = filterDeclarables(contextQueues);
		final Collection<Binding> bindings = filterDeclarables(contextBindings);

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

	

由于我们只创建了Queue,使用默认的Exchange,代码不贴太多了,只贴声明Queue的内容:

DeclareOk declareOk = channel.queueDeclare(queue.getName(), queue.isDurable(),
								queue.isExclusive(), queue.isAutoDelete(), queue.getArguments());

我们看下抓包情况:

 到此呢,Queue也声明好了。下面呢,下面就该basic.Consume 了吧,把消费者注册到Broker中去。

好,我们继续:

继续代码又倒回去,倒到:

/**
	 * Start this container.
	 * @see #doStart
	 */
	@Override
	public void start() 
		if (isRunning()) 
			return;
		
		if (!this.initialized) 
			synchronized (this.lifecycleMonitor) 
				if (!this.initialized) 
					afterPropertiesSet();
				
			
		
		try 
			logger.debug("Starting Rabbit listener container.");
			configureAdminIfNeeded();
			checkMismatchedQueues();
			doStart();
		
		catch (Exception ex) 
			throw convertRabbitAccessException(ex);
		
		finally 
			this.lazyLoad = false;
		
	
doStart(); 

一看doxxx,那一定是要干实际的事情的,很重要对吧,

我们进入到 

SimpleMessageListenerContainer

中的实现方法中:

/**
	 * Re-initializes this container's Rabbit message consumers, if not initialized already. Then submits each consumer
	 * to this container's task executor.
	 */
	@Override
	protected void doStart() 
		checkListenerContainerAware();
		super.doStart();
		synchronized (this.consumersMonitor) 
			if (this.consumers != null) 
				throw new IllegalStateException("A stopped container should not have consumers");
			
			int newConsumers = initializeConsumers();
			if (this.consumers == null) 
				logger.info("Consumers were initialized and then cleared " +
						"(presumably the container was stopped concurrently)");
				return;
			
			if (newConsumers <= 0) 
				if (logger.isInfoEnabled()) 
					logger.info("Consumers are already running");
				
				return;
			
			Set<AsyncMessageProcessingConsumer> processors = new HashSet<AsyncMessageProcessingConsumer>();
			for (BlockingQueueConsumer consumer : this.consumers) 
				AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
				processors.add(processor);
				getTaskExecutor().execute(processor);
				if (getApplicationEventPublisher() != null) 
					getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
				
			
			waitForConsumersToStart(processors);
		
	

前面几步意义不大,走到

int newConsumers = initializeConsumers();
protected int initializeConsumers() 
		int count = 0;
		synchronized (this.consumersMonitor) 
			if (this.consumers == null) 
				this.cancellationLock.reset();
				this.consumers = new HashSet<BlockingQueueConsumer>(this.concurrentConsumers);
				for (int i = 0; i < this.concurrentConsumers; i++) 
					BlockingQueueConsumer consumer = createBlockingQueueConsumer();
					this.consumers.add(consumer);
					count++;
				
			
		
		return count;
	

重点来咯,

BlockingQueueConsumer consumer = createBlockingQueueConsumer();

这里把BlockingQueueConsumer做了一个初始化,相关的不再展开。

BlockingQueueConsumer -这将是后续我们非常重要的一个类

继续重点内容,回到我们上面代码块中的内容:

for (BlockingQueueConsumer consumer : this.consumers) 
				AsyncMessageProcessingConsumer processor = new AsyncMessageProcessingConsumer(consumer);
				processors.add(processor);
				getTaskExecutor().execute(processor);
				if (getApplicationEventPublisher() != null) 
					getApplicationEventPublisher().publishEvent(new AsyncConsumerStartedEvent(this, consumer));
				
			

这个for循环很重要了,由于我们是一个消费者,循环一次。

初始化一个

AsyncMessageProcessingConsumer

对象。这个对象点进去,大家看下这是个实现了Runnable接口的线程对象。哦哦,真正的核心哦。使用 SimpleAsyncTaskExecutor   来new的线程,这个执行器可不是线程池哦,来一个线程就会New一个,大家自行研究。

这里面我们可以得到一个结论,就是一个消费者,就会开启一个线程进行监听。

从此开启了新线程,【打断点记得Thread模式】

看线程的实现:

@Override // NOSONAR - complexity - many catch blocks
		public void run()  // NOSONAR - line count
			if (!isActive()) 
				return;
			

			boolean aborted = false;

			this.consumer.setLocallyTransacted(isChannelLocallyTransacted());

			String routingLookupKey = getRoutingLookupKey();
			if (routingLookupKey != null) 
				SimpleResourceHolder.bind(getRoutingConnectionFactory(), routingLookupKey); // NOSONAR both never null
			

			if (this.consumer.getQueueCount() < 1) 
				if (logger.isDebugEnabled()) 
					logger.debug("Consumer stopping; no queues for " + this.consumer);
				
				SimpleMessageListenerContainer.this.cancellationLock.release(this.consumer);
				if (getApplicationEventPublisher() != null) 
					getApplicationEventPublisher().publishEvent(
							new AsyncConsumerStoppedEvent(SimpleMessageListenerContainer.this, this.consumer));
				
				this.start.countDown();
				return;
			

			try 
				initialize();
				while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) 
					mainLoop();
				
			

摘出核心点:

1、initialize();

	private void initialize() throws Throwable  // NOSONAR
			try 
				redeclareElementsIfNecessary();
				this.consumer.start();
				this.start.countDown();
			

初始化内容,

1.  redeclareElementsIfNecessary - 这个是再进行检查进行Exchange 、Queue、Binding的声明与前面声明的方法实现的共用。

2.this.consumer.start();  

public void start() throws AmqpException 
		if (logger.isDebugEnabled()) 
			logger.debug("Starting consumer " + this);
		

		this.thread = Thread.currentThread();

		try 
			this.resourceHolder = ConnectionFactoryUtils.getTransactionalResourceHolder(this.connectionFactory,
					this.transactional);
			this.channel = this.resourceHolder.getChannel();
			ClosingRecoveryListener.addRecoveryListenerIfNecessary(this.channel); // NOSONAR never null here
		
		catch (AmqpAuthenticationException e) 
			throw new FatalListenerStartupException("Authentication failure", e);
		
		this.deliveryTags.clear();
		this.activeObjectCounter.add(this);

		passiveDeclarations();
		setQosAndreateConsumers();
	
这里面我们看这个方法就行
setQosAndreateConsumers();

Qos是设定消费时每次抓取的数量

并CreadConsumers

private void setQosAndreateConsumers() 
		if (!this.acknowledgeMode.isAutoAck() && !cancelled()) 
			// Set basicQos before calling basicConsume (otherwise if we are not acking the broker
			// will send blocks of 100 messages)
			try 
				this.channel.basicQos(this.prefetchCount);
			
			catch (IOException e) 
				this.activeObjectCounter.release(this);
				throw new AmqpIOException(e);
			
		

		try 
			if (!cancelled()) 
				for (String queueName : this.queues) 
					if (!this.missingQueues.contains(queueName)) 
						consumeFromQueue(queueName);
					
				
			
		
		catch (IOException e) 
			throw RabbitExceptionTranslator.convertRabbitAccessException(e);
		
	

有没有很熟悉:

this.channel.basicQos(this.prefetchCount);

抓包:

继续:

consumeFromQueue(queueName);
private void consumeFromQueue(String queue) throws IOException 
		InternalConsumer consumer = new InternalConsumer(this.channel, queue);
		String consumerTag = this.channel.basicConsume(queue, this.acknowledgeMode.isAutoAck(),
				(this.tagStrategy != null ? this.tagStrategy.createConsumerTag(queue) : ""), this.noLocal,
				this.exclusive, this.consumerArgs,
				consumer);

		if (consumerTag != null) 
			this.consumers.put(queue, consumer);
			if (logger.isDebugEnabled()) 
				logger.debug("Started on queue '" + queue + "' with tag " + consumerTag + ": " + this);
			
		
		else 
			logger.error("Null consumer tag received for queue " + queue);
		
	

 有没有很熟悉:

String consumerTag = this.channel.basicConsume(queue, this.acknowledgeMode.isAutoAck(),
      (this.tagStrategy != null ? this.tagStrategy.createConsumerTag(queue) : ""), this.noLocal,
      this.exclusive, this.consumerArgs,
      consumer);

那这里有有一个核心的类出现了。InternalConsumer

这里转向 3.2 Broker投递消息给客户端  解释

到这里呢,我们把消费者注册到了Broker中去了,看下抓包情况:

 到这呢,所以Broker也就能给我们投递消息了。

2、mainLoop();

				initialize();
				while (isActive(this.consumer) || this.consumer.hasDelivery() || !this.consumer.cancelled()) 
					mainLoop();
				

这里也有个mainLoop ,于是想到了,java 的amqp客户端也存在呢mainLoop ,这里的逻辑难道也和他的逻辑契合的?我们转向 3.3 客户端消费过程继续。

3.2 Broker投递消息给客户端过程

上面说到了,已经将消费者注册到了Broker中去了,但一定注意哦,注册到Broker 中的,可不是我们使用注解 RabbitListener 标注的实际消费方法哦,而是新创建了一个内部的消费者:InternalConsumer

我们看下他的一个实现

private final class InternalConsumer extends DefaultConsumer 

		private final String queueName;

		boolean canceled;

		InternalConsumer(Channel channel, String queue) 
			super(channel);
			this.queueName = queue;
		

		@Override
		public void handleConsumeOk(String consumerTag) 
			super.handleConsumeOk(consumerTag);
			if (logger.isDebugEnabled()) 
				logger.debug("ConsumeOK: " + BlockingQueueConsumer.this);
			
			if (BlockingQueueConsumer.this.applicationEventPublisher != null) 
				BlockingQueueConsumer.this.applicationEventPublisher
						.publishEvent(new ConsumeOkEvent(this, this.queueName, consumerTag));
			
		

		@Override
		public void handleShutdownSignal(String consumerTag, ShutdownSignalException sig) 
			if (logger.isDebugEnabled()) 
				if (RabbitUtils.isNormalShutdown(sig)) 
					logger.debug("Received shutdown signal for consumer tag=" + consumerTag + ": " + sig.getMessage());
				
				else 
					logger.debug("Received shutdown signal for consumer tag=" + consumerTag, sig);
				
			
			BlockingQueueConsumer.this.shutdown = sig;
			// The delivery tags will be invalid if the channel shuts down
			BlockingQueueConsumer.this.deliveryTags.clear();
			BlockingQueueConsumer.this.activeObjectCounter.release(BlockingQueueConsumer.this);
		

		@Override
		public void handleCancel(String consumerTag) throws IOException 
			if (logger.isWarnEnabled()) 
				logger.warn("Cancel received for " + consumerTag + " ("
						+ this.queueName
						+ "); " + BlockingQueueConsumer.this);
			
			BlockingQueueConsumer.this.consumers.remove(this.queueName);
			if (!BlockingQueueConsumer.this.consumers.isEmpty()) 
				basicCancel(false);
			
			else 
				BlockingQueueConsumer.this.cancelled.set(true);
			
		

		@Override
		public void handleCancelOk(String consumerTag) 
			if (logger.isDebugEnabled()) 
				logger.debug("Received cancelOk for tag " + consumerTag + " ("
						+ this.queueName
						+ "); " + BlockingQueueConsumer.this);
			
			this.canceled = true;
		

		@Override
		public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
				byte[] body) 
			if (logger.isDebugEnabled()) 
				logger.debug("Storing delivery for consumerTag: '"
						+ consumerTag + "' with deliveryTag: '" + envelope.getDeliveryTag() + "' in "
						+ BlockingQueueConsumer.this);
			
			try 
				if (BlockingQueueConsumer.this.abortStarted > 0) 
					if (!BlockingQueueConsumer.this.queue.offer(
							new Delivery(consumerTag, envelope, properties, body, this.queueName),
							BlockingQueueConsumer.this.shutdownTimeout, TimeUnit.MILLISECONDS)) 

						Channel channelToClose = super.getChannel();
						RabbitUtils.setPhysicalCloseRequired(channelToClose, true);
						// Defensive - should never happen
						BlockingQueueConsumer.this.queue.clear();
						if (!this.canceled) 
							getChannel().basicCancel(consumerTag);
						
						try 
							channelToClose.close();
						
						catch (@SuppressWarnings("unused") TimeoutException e) 
							// no-op
						
					
				
				else 
					BlockingQueueConsumer.this.queue
							.put(new Delivery(consumerTag, envelope, properties, body, this.queueName));
				
			
			catch (@SuppressWarnings("unused") InterruptedException e) 
				Thread.currentThread().interrupt();
			
			catch (Exception e) 
				BlockingQueueConsumer.logger.warn("Unexpected exception during delivery", e);
			
		

		@Override
		public String toString() 
			return "InternalConsumer" + "queue='" + this.queueName + '\\'' +
					", consumerTag='" + getConsumerTag() + '\\'' +
					'';
		

	

哇,内部类,而且继承了 DefaultConsumer ,这和我们前面学习Rabbitmq工作模式的过程中,自己手动开发的代码一样了吧,那我找到 投递方法:

public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,

好亲切有木有,所以到这里真相大白咯。Broker将消息投递到了这里,我们看看他接收到消息搞什么动作?

BlockingQueueConsumer.this.queue
							.put(new Delivery(consumerTag, envelope, properties, body, this.queueName));

很明显,和java amqp client 实现一样,他这也用到了Queue,去存储了,

this.queue = new LinkedBlockingQueue<Delivery>(prefetchCount);

也是个阻塞Queue哦,看来spring搞了一通,从客户端那边的queue里拿来,又放了一次queue。

那放进去了,就等着取呗,看谁来取咯。

3.3 客户端消费过程

接续上面的 mainLoop(),既然消息又存到了本地的queue中,那mainLoop 的目的岂不是很明确了,那就是死循环的去取消息消息,然后再转调到我们实际的 加入@RabbitListener 的方法中去呢。究竟是不是呢,验证下:

private void mainLoop() throws Exception  // NOSONAR Exception
			try 
				boolean receivedOk = receiveAndExecute(this.consumer); // At least one message received
				if (SimpleMessageListenerContainer.this.maxConcurrentConsumers != null) 
					checkAdjust(receivedOk);
				
				long idleEventInterval = getIdleEventInterval();
				if (idleEventInterval > 0) 
					if (receivedOk) 
						updateLastReceive();
					
					else 
						long now = System.currentTimeMillis();
						long lastAlertAt = SimpleMessageListenerContainer.this.lastNoMessageAlert.get();
						long lastReceive = getLastReceive();
						if (now > lastReceive + idleEventInterval
								&& now > lastAlertAt + idleEventInterval
								&& SimpleMessageListenerContainer.this.lastNoMessageAlert
								.compareAndSet(lastAlertAt, now)) 
							publishIdleContainerEvent(now - lastReceive);
						
					
				
			
			catch (ListenerExecutionFailedException ex) 
				// Continue to process, otherwise re-throw
				if (ex.getCause() instanceof NoSuchMethodException) 
					throw new FatalListenerExecutionException("Invalid listener", ex);
				
			
			catch (AmqpRejectAndDontRequeueException rejectEx) 
				/*
				 *  These will normally be wrapped by an LEFE if thrown by the
				 *  listener, but we will also honor it if thrown by an
				 *  error handler.
				 */
			
		

看下重点方法:

boolean receivedOk = receiveAndExecute(this.consumer); 
private boolean receiveAndExecute(final BlockingQueueConsumer consumer) throws Exception  // NOSONAR

		PlatformTransactionManager transactionManager = getTransactionManager();
		if (transactionManager != null) 
			try 
				if (this.transactionTemplate == null) 
					this.transactionTemplate =
							new TransactionTemplate(transactionManager, getTransactionAttribute());
				
				return this.transactionTemplate
						.execute(status ->  // NOSONAR null never returned
							RabbitResourceHolder resourceHolder = ConnectionFactoryUtils.bindResourceToTransaction(
									new RabbitResourceHolder(consumer.getChannel(), false),
									getConnectionFactory(), true);
							// unbound in ResourceHolderSynchronization.beforeCompletion()
							try 
								return doReceiveAndExecute(consumer);
							
							catch (RuntimeException e1) 
								prepareHolderForRollback(resourceHolder, e1);
								throw e1;
							
							catch (Exception e2) 
								throw new WrappedTransactionException(e2);
							
						);
			
			catch (WrappedTransactionException e)  // NOSONAR exception flow control
				throw (Exception) e.getCause();
			
		

		return doReceiveAndExecute(consumer);

	

抛开事务,我们不关注。

return doReceiveAndExecute(consumer);
private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Exception  //NOSONAR

		Channel channel = consumer.getChannel();

		for (int i = 0; i < this.txSize; i++) 

			logger.trace("Waiting for message from consumer.");
			Message message = consumer.nextMessage(this.receiveTimeout);
			if (message == null) 
				break;
			
			try 
				executeListener(channel, message);
			

重点哦:

			Message message = consumer.nextMessage(this.receiveTimeout);

从内部消费者取消息咯

public Message nextMessage(long timeout) throws InterruptedException, ShutdownSignalException 
		if (logger.isTraceEnabled()) 
			logger.trace("Retrieving delivery for " + this);
		
		checkShutdown();
		if (this.missingQueues.size() > 0) 
			checkMissingQueues();
		
		Message message = handle(this.queue.poll(timeout, TimeUnit.MILLISECONDS));
		if (message == null && this.cancelled.get()) 
			throw new ConsumerCancelledException();
		
		return message;
	

看到poll 我们就放心了,把消息取出来,包装成Message对象。

快调头回来,继续看:

try 
    executeListener(channel, message);

这就要真正处理这个消息了

protected void executeListener(Channel channel, Message messageIn) 
		if (!isRunning()) 
			if (logger.isWarnEnabled()) 
				logger.warn("Rejecting received message because the listener container has been stopped: " + messageIn);
			
			throw new MessageRejectedWhileStoppingException();
		
		try 
			doExecuteListener(channel, messageIn);
		
		catch (RuntimeException ex) 
			if (messageIn.getMessageProperties().isFinalRetryForMessageWithNoId()) 
				if (this.statefulRetryFatalWithNullMessageId) 
					throw new FatalListenerExecutionException(
							"Illegal null id in message. Failed to manage retry for message: " + messageIn, ex);
				
				else 
					throw new ListenerExecutionFailedException("Cannot retry message more than once without an ID",
							new AmqpRejectAndDontRequeueException("Not retryable; rejecting and not requeuing", ex),
							messageIn);
				
			
			handleListenerException(ex);
			throw ex;
		
	

代码不往下贴了,继续追就可以,最终还是找到了,打标@RabbitListener的那个方法上,得到了执行。真正让业务逻辑执行到了MQ推送过来的消息,

太不容易了,消息从发送-> Exchange->Queue -> java amqp client  ->spring client - >consume 最终得到了消费。

4. 总结

小结一下,我们从注解RabbitHandler RabbitListener 入手,一步步追踪到 与Broker链接的创建,Queue的声明,接着,启动新线程 注册一个内部的消费者到Broker中,Broker有消息的时候会推送到本地的BlockingQueue中去。

使用MainLoop 消费本地Blockinqueue的内容

贴个小图:

以上是关于-RabbitMQ之Spring客户端源码的主要内容,如果未能解决你的问题,请参考以下文章

Spring RabbitMQ 死信机制

RabbitMQ 消费端限流TTL死信队列

Spring Boot系列——RabbitMQ确认退回模式及死信队列

RabbitMQ项目使用之死信队列

springBoot集成rabbitmq 之延时(死信)队列

RabbitMQ项目使用之死信队列