Spring Boot, Spring Cloud Stream integrate RabbitMQ source code analysis

Spring Boot has very good support for RabbitMQ. Today, we simply analyze how to find our monitoring method after RabbitMQ pushes messages from the source code level, and how to find them in Spring Cloud Stream.

First introduce the pom file, the version is based on spring boot 2.2.7.

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

Configuration class RabbitmqConfig

@Configuration
public class RabbitmqConfig {
    public static final String QUEUE_INFORM_EMAIL = "queue_email_test";
    public static final String QUEUE_INFORM_SMS = "queue_sms_test";
    public static final String QUEUE_DEAD="queue_dead";
    public static final String EXCHANGE_TOPICS_INFORM="exchange_topics_test";
    public static final String EXCHANGE_DIRECT_INFORM="exchange_direct_test";
    private static final String DEAD_LETTER_QUEUE_KEY = "x-dead-letter-exchange";
    private static final String DEAD_LETTER_ROUTING_KEY = "x-dead-letter-routing-key";

    /**
     * 交换机配置
     * ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
     * @return the exchange
     */
    @Bean(EXCHANGE_TOPICS_INFORM)
    public Exchange EXCHANGE_TOPICS_INFORM() {
        //durable(true)持久化,消息队列重启后交换机仍然存在
        return ExchangeBuilder.topicExchange(EXCHANGE_TOPICS_INFORM).durable(true).build();
    }
    /**
     * 交换机配置
     * ExchangeBuilder提供了fanout、direct、topic、header交换机类型的配置
     * @return the exchange
     */
    @Bean(EXCHANGE_DIRECT_INFORM)
    public Exchange EXCHANGE_DIRECT_INFORM() {
        //durable(true)持久化,消息队列重启后交换机仍然存在
        return ExchangeBuilder.directExchange(EXCHANGE_DIRECT_INFORM).durable(true).build();
    }

    //声明队列
    @Bean(QUEUE_INFORM_SMS)
    public Queue QUEUE_INFORM_SMS() {
        Map<String, Object> args = new HashMap<>(2);
        // x-dead-letter-exchange 声明 死信交换机
        args.put(DEAD_LETTER_QUEUE_KEY, EXCHANGE_DIRECT_INFORM);
        // x-dead-letter-routing-key 声明 死信路由键
        args.put(DEAD_LETTER_ROUTING_KEY, "queue_dead");
//        Queue queue = new Queue(QUEUE_INFORM_SMS,true,false,false,args);
        Queue queue = QueueBuilder.durable(QUEUE_INFORM_SMS).withArguments(args).build();
        return queue;
    }
    //声明队列
    @Bean(QUEUE_INFORM_EMAIL)
    public Queue QUEUE_INFORM_EMAIL() {
        Queue queue = new Queue(QUEUE_INFORM_EMAIL);
        return queue;
    }
    //声明队列
    @Bean(QUEUE_DEAD)
    public Queue QUEUE_DEAD() {
    	Queue queue = new Queue(QUEUE_DEAD);
    	return queue;
    }
    /** channel.queueBind(INFORM_QUEUE_SMS,"inform_exchange_topic","inform.#.sms.#");
     * 绑定队列到交换机 .
     *
     * @param queue    the queue
     * @param exchange the exchange
     * @return the binding
     */
    @Bean
    public Binding BINDING_QUEUE_INFORM_SMS(@Qualifier(QUEUE_INFORM_SMS) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("inform.#.sms.#").noargs();
    }
    @Bean
    public Binding BINDING_QUEUE_INFORM_EMAIL(@Qualifier(QUEUE_INFORM_EMAIL) Queue queue, @Qualifier(EXCHANGE_TOPICS_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("inform.#.email.#").noargs();
    }
    @Bean
    public Binding BINDING_QUEUE_INFORM_SMS2(@Qualifier(QUEUE_INFORM_SMS) Queue queue, @Qualifier(EXCHANGE_DIRECT_INFORM) Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("inform.sms.test").noargs();
    }
    @Bean
    public Binding BINDING_QUEUE_DEAD(@Qualifier(QUEUE_DEAD) Queue queue, @Qualifier(EXCHANGE_DIRECT_INFORM) Exchange exchange) {
    	return BindingBuilder.bind(queue).to(exchange).with(QUEUE_DEAD).noargs();
    }

    @Bean
    public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
        return new RepublishMessageRecoverer(rabbitTemplate, EXCHANGE_DIRECT_INFORM,QUEUE_DEAD);
    }
}

Monitor class:

@Component
public class ReceiveHandler {
	//监听email队列
	@RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL})
	public void receive_email(String msg, Message message, Channel channel) throws Exception {
		System.out.println("email队列接收消息:"+msg);
	}
}

Send class:

@Component
public class RabbitmqSend {
    @Autowired
    RabbitTemplate rabbitTemplate;

    public void testSendByTopics(){
        DlxMsg messagePostProcessor = new DlxMsg(5000l);
        for (int i=0;i<1;i++){
            String message = "sms email inform to user"+i;
            rabbitTemplate.convertAndSend(RabbitmqConfig.QUEUE_INFORM_EMAIL,"inform.sms.email",message);
            System.out.println("Send Message is:'" + message + "'");
        }
    }
}

Startup class:

@SpringBootApplication
public class RabbitmqDemoApplication {
	@Autowired
	RabbitmqSend rabbitmqSend;

	public static void main(String[] args) throws Exception{
		ConfigurableApplicationContext run = SpringApplication.run(RabbitmqDemoApplication.class, args);
		RabbitmqSend rabbitmqSend = (RabbitmqSend)run.getBean("rabbitmqSend");
		rabbitmqSend.testSendByTopics();
        }
}

application.yml:

server:
  port: 44001
spring:
  application:
    name: test-rabbitmq-producer
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    passowrd: guest
    virtualHost: /

These can be run directly after startup, and now directly analyze the source code.

What we want to analyze is how RabbitMQ directly finds the method marked with @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL}) annotation.

In the spring.factories file in the spring-boot-autoconfigure.jar package, we can see that the RabbitAutoConfiguration class is automatically injected.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
    ....................
}

Let's look directly at the RabbitAnnotationDrivenConfiguration class.

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
        ...............
    	@Bean(name = "rabbitListenerContainerFactory")
	@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
	@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple",
			matchIfMissing = true)
	SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
			SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
		SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
		configurer.configure(factory, connectionFactory);
		return factory;
	}

	@Bean(name = "rabbitListenerContainerFactory")
	@ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
	@ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "direct")
	DirectRabbitListenerContainerFactory directRabbitListenerContainerFactory(
			DirectRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
		DirectRabbitListenerContainerFactory factory = new DirectRabbitListenerContainerFactory();
		configurer.configure(factory, connectionFactory);
		return factory;
	}
}

SimpleRabbitListenerContainerFactory and DirectRabbitListenerContainerFactory are more important in this class. Both classes inherit from the AbstractRabbitListenerContainerFactory class. The SimpleMessageListenerContainer and DirectMessageListenerContainer created by these two classes are mainly responsible for receiving messages from RabbitMQ and forwarding them to our own methods. Everyone remember these two classes first.

EnableRabbit is on the RabbitAnnotationDrivenConfiguration class.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RabbitListenerConfigurationSelector.class)
public @interface EnableRabbit {
}

public class RabbitListenerConfigurationSelector implements DeferredImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		return new String[] { RabbitBootstrapConfiguration.class.getName() };
	}
}

public class RabbitBootstrapConfiguration implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		if (!registry.containsBeanDefinition(
				RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)) {

			registry.registerBeanDefinition(RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME,
					new RootBeanDefinition(RabbitListenerAnnotationBeanPostProcessor.class));
		}

		if (!registry.containsBeanDefinition(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)) {
			registry.registerBeanDefinition(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
					new RootBeanDefinition(RabbitListenerEndpointRegistry.class));
		}
	}

}

EnableRabbit imports RabbitBootstrapConfiguration, and RabbitBootstrapConfiguration will be registered into two classes: RabbitListenerAnnotationBeanPostProcessor and RabbitListenerEndpointRegistry.

We first look at the postProcessAfterInitialization method of the RabbitListenerAnnotationBeanPostProcessor class.

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

After each Bean object is created, it will detect whether there is a method containing the @RabbitListener annotation in the Bean, and if so, the processAmqpListener method will be executed.

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

The method will create a MethodRabbitListenerEndpoint (Rabbit listening method terminal) object.

	protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,
			Object target, 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);
			}
		}

		resolveExecutor(endpoint, rabbitListener, target, beanName);
		resolveAdmin(endpoint, rabbitListener, target);
		resolveAckMode(endpoint, rabbitListener);
		resolvePostProcessor(endpoint, rabbitListener, target, beanName);
		RabbitListenerContainerFactory<?> factory = resolveContainerFactory(rabbitListener, target, beanName);

		this.registrar.registerEndpoint(endpoint, factory);
	}

After the endpoint has undergone a series of settings, execute the this.registrar.registerEndpoint(endpoint, factory); method. registrar=RabbitListenerEndpointRegistrar(), defined when the class is created.

RabbitListenerEndpointRegistrar.registerEndpoint method

	public void registerEndpoint(RabbitListenerEndpoint endpoint,
			@Nullable RabbitListenerContainerFactory<?> factory) {
		Assert.notNull(endpoint, "Endpoint must be set");
		Assert.hasText(endpoint.getId(), "Endpoint id must be set");
		Assert.state(!this.startImmediately || this.endpointRegistry != null, "No registry available");
		// Factory may be null, we defer the resolution right before actually creating the container
		AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
		synchronized (this.endpointDescriptors) {
			if (this.startImmediately) { // Register and start immediately
				this.endpointRegistry.registerListenerContainer(descriptor.endpoint, // NOSONAR never null
						resolveContainerFactory(descriptor), true);
			}
			else {
				this.endpointDescriptors.add(descriptor);
			}
		}
	}

Because startImmediately=false, just add the endpoint to the list here.

Continue to look at the afterSingletonsInstantiated method of the RabbitListenerAnnotationBeanPostProcessor class.

	public void afterSingletonsInstantiated() {
		this.registrar.setBeanFactory(this.beanFactory);

		if (this.beanFactory instanceof ListableBeanFactory) {
			Map<String, RabbitListenerConfigurer> instances =
					((ListableBeanFactory) this.beanFactory).getBeansOfType(RabbitListenerConfigurer.class);
			for (RabbitListenerConfigurer configurer : instances.values()) {
				configurer.configureRabbitListeners(this.registrar);
			}
		}

		if (this.registrar.getEndpointRegistry() == null) {
			if (this.endpointRegistry == null) {
				Assert.state(this.beanFactory != null,
						"BeanFactory must be set to find endpoint registry by bean name");
				this.endpointRegistry = this.beanFactory.getBean(
						RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME,
						RabbitListenerEndpointRegistry.class);
			}
			this.registrar.setEndpointRegistry(this.endpointRegistry);
		}

		if (this.defaultContainerFactoryBeanName != null) {
			this.registrar.setContainerFactoryBeanName(this.defaultContainerFactoryBeanName);
		}

		// Set the custom handler method factory once resolved by the configurer
		MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory();
		if (handlerMethodFactory != null) {
			this.messageHandlerMethodFactory.setMessageHandlerMethodFactory(handlerMethodFactory);
		}

		// Actually register all listeners
		this.registrar.afterPropertiesSet();

		// clear the cache - prototype beans will be re-cached.
		this.typeCache.clear();
	}

Go directly to registrar.afterPropertiesSet().

	public void afterPropertiesSet() {
		registerAllEndpoints();
	}

	protected void registerAllEndpoints() {
		Assert.state(this.endpointRegistry != null, "No registry available");
		synchronized (this.endpointDescriptors) {
			for (AmqpListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
				this.endpointRegistry.registerListenerContainer(// NOSONAR never null
						descriptor.endpoint, resolveContainerFactory(descriptor));
			}
			this.startImmediately = true;  // trigger immediate startup
		}
	}

The endpointRegistry here is the RabbitListenerEndpointRegistry class. Execute the registerListenerContainer method.

	public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
				boolean startImmediately) {
		Assert.notNull(endpoint, "Endpoint must not be null");
		Assert.notNull(factory, "Factory must not be null");

		String id = endpoint.getId();
		Assert.hasText(id, "Endpoint id must not be empty");
		synchronized (this.listenerContainers) {
			Assert.state(!this.listenerContainers.containsKey(id),
					"Another endpoint is already registered with id '" + id + "'");
			MessageListenerContainer container = createListenerContainer(endpoint, factory);
			this.listenerContainers.put(id, container);
		.........................................
		}
	}

Continue to the createListenerContainer method

	protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,
			RabbitListenerContainerFactory<?> factory) {

		MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);
                ....................
        }
	public C createListenerContainer(RabbitListenerEndpoint endpoint) {
		C instance = createContainerInstance();

		JavaUtils javaUtils =
				JavaUtils.INSTANCE
						.acceptIfNotNull(this.connectionFactory, instance::setConnectionFactory)
						.acceptIfNotNull(this.errorHandler, instance::setErrorHandler);
		if (this.messageConverter != null && endpoint != null) {
			endpoint.setMessageConverter(this.messageConverter);
		}
		javaUtils
			.acceptIfNotNull(this.acknowledgeMode, instance::setAcknowledgeMode)
			.acceptIfNotNull(this.channelTransacted, instance::setChannelTransacted)
			.acceptIfNotNull(this.applicationContext, instance::setApplicationContext)
			.acceptIfNotNull(this.taskExecutor, instance::setTaskExecutor)
			.acceptIfNotNull(this.transactionManager, instance::setTransactionManager)
			.acceptIfNotNull(this.prefetchCount, instance::setPrefetchCount)
			.acceptIfNotNull(this.defaultRequeueRejected, instance::setDefaultRequeueRejected)
			.acceptIfNotNull(this.adviceChain, instance::setAdviceChain)
			.acceptIfNotNull(this.recoveryBackOff, instance::setRecoveryBackOff)
			.acceptIfNotNull(this.mismatchedQueuesFatal, instance::setMismatchedQueuesFatal)
			.acceptIfNotNull(this.missingQueuesFatal, instance::setMissingQueuesFatal)
			.acceptIfNotNull(this.consumerTagStrategy, instance::setConsumerTagStrategy)
			.acceptIfNotNull(this.idleEventInterval, instance::setIdleEventInterval)
			.acceptIfNotNull(this.failedDeclarationRetryInterval, instance::setFailedDeclarationRetryInterval)
			.acceptIfNotNull(this.applicationEventPublisher, instance::setApplicationEventPublisher)
			.acceptIfNotNull(this.autoStartup, instance::setAutoStartup)
			.acceptIfNotNull(this.phase, instance::setPhase)
			.acceptIfNotNull(this.afterReceivePostProcessors, instance::setAfterReceivePostProcessors)
			.acceptIfNotNull(this.deBatchingEnabled, instance::setDeBatchingEnabled);
		if (this.batchListener && this.deBatchingEnabled == null) {
			// turn off container debatching by default for batch listeners
			instance.setDeBatchingEnabled(false);
		}
		if (endpoint != null) { // endpoint settings overriding default factory settings
			javaUtils
				.acceptIfNotNull(endpoint.getAutoStartup(), instance::setAutoStartup)
				.acceptIfNotNull(endpoint.getTaskExecutor(), instance::setTaskExecutor)
				.acceptIfNotNull(endpoint.getAckMode(), instance::setAcknowledgeMode);
			javaUtils
				.acceptIfNotNull(this.batchingStrategy, endpoint::setBatchingStrategy);
			instance.setListenerId(endpoint.getId());
			endpoint.setBatchListener(this.batchListener);
			endpoint.setupListenerContainer(instance);
		}
		if (instance.getMessageListener() instanceof AbstractAdaptableMessageListener) {
			AbstractAdaptableMessageListener messageListener = (AbstractAdaptableMessageListener) instance
					.getMessageListener();
			javaUtils
					.acceptIfNotNull(this.beforeSendReplyPostProcessors,
							messageListener::setBeforeSendReplyPostProcessors)
					.acceptIfNotNull(this.retryTemplate, messageListener::setRetryTemplate)
					.acceptIfCondition(this.retryTemplate != null && this.recoveryCallback != null,
							this.recoveryCallback, messageListener::setRecoveryCallback)
					.acceptIfNotNull(this.defaultRequeueRejected, messageListener::setDefaultRequeueRejected)
					.acceptIfNotNull(endpoint.getReplyPostProcessor(), messageListener::setReplyPostProcessor);
		}
		initializeContainer(instance, endpoint);

		if (this.containerCustomizer != null) {
			this.containerCustomizer.configure(instance);
		}

		return instance;
	}

The createContainerInstance method here is based on one of the previously created SimpleMessageListenerContainer and DirectMessageListenerContainer.

After that, it will be executed to endpoint.setupListenerContainer(instance); this line.

	public void setupListenerContainer(MessageListenerContainer listenerContainer) {
		AbstractMessageListenerContainer container = (AbstractMessageListenerContainer) listenerContainer;

		......................
		setupMessageListener(listenerContainer);
	}

	private void setupMessageListener(MessageListenerContainer container) {
		MessageListener messageListener = createMessageListener(container);
		Assert.state(messageListener != null, () -> "Endpoint [" + this + "] must provide a non null message listener");
		container.setupMessageListener(messageListener);
	}

	protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
		Assert.state(this.messageHandlerMethodFactory != null,
				"Could not create message listener - MessageHandlerMethodFactory not set");
		MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
		messageListener.setHandlerAdapter(configureListenerAdapter(messageListener));
		String replyToAddress = getDefaultReplyToAddress();
		if (replyToAddress != null) {
			messageListener.setResponseAddress(replyToAddress);
		}
		MessageConverter messageConverter = getMessageConverter();
		if (messageConverter != null) {
			messageListener.setMessageConverter(messageConverter);
		}
		if (getBeanResolver() != null) {
			messageListener.setBeanResolver(getBeanResolver());
		}
		return messageListener;
	}

The createMessageListener method will create a MessagingMessageListenerAdapter object, and then the configureListenerAdapter(messageListener) method will create a HandlerAdapter object for associating the corresponding Bean and method.

	protected HandlerAdapter configureListenerAdapter(MessagingMessageListenerAdapter messageListener) {
		InvocableHandlerMethod invocableHandlerMethod =
				this.messageHandlerMethodFactory.createInvocableHandlerMethod(getBean(), getMethod());
		return new HandlerAdapter(invocableHandlerMethod);
	}

The steps here are

1. Find MessagingMessageListenerAdapter through SimpleMessageListenerContainer or DirectMessageListenerContainer

2. Find the corresponding HandlerAdapter through MessagingMessageListenerAdapter

3. Find the corresponding Bean and method through HandlerAdapter.

Well, the focus is on how to find SimpleMessageListenerContainer or DirectMessageListenerContainer through RabbitMq.

Going back to the RabbitBootstrapConfiguration class, we know that this class also registered a RabbitListenerEndpointRegistry.

public class RabbitListenerEndpointRegistry implements DisposableBean, SmartLifecycle, ApplicationContextAware,
		ApplicationListener<ContextRefreshedEvent> {
        ...............
}

This class implements the SmartLifecycle method. Then let's take a look at its start method.

	public void start() {
		for (MessageListenerContainer listenerContainer : getListenerContainers()) {
			startIfNecessary(listenerContainer);
		}
	}
	private void startIfNecessary(MessageListenerContainer listenerContainer) {
		if (this.contextRefreshed || listenerContainer.isAutoStartup()) {
			listenerContainer.start();
		}
	}
	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;
		}
	}

The above are all similar, we mainly look at the doStart method. Take DirectMessageListenerContainer as an example, SimpleMessageListenerContainer is similar.

	protected void doStart() {
		if (!this.started) {
			actualStart();
		}
	}
	protected void actualStart() {
		..............
		if (queueNames.length > 0) {
			doRedeclareElementsIfNecessary();
			getTaskExecutor().execute(() -> { // NOSONAR never null here

				startConsumers(queueNames);

			});
		}
		else {
			this.started = true;
			this.startedLatch.countDown();
		}
		.................
	}

Just look at the name to know the start consumer, just look at the startConsumers method.

	private void startConsumers(final String[] queueNames) {
				while (!DirectMessageListenerContainer.this.started && isRunning()) {
					this.cancellationLock.reset();
					try {
						for (String queue : queueNames) {
							consumeFromQueue(queue);
						}
					}
					................
					DirectMessageListenerContainer.this.started = true;
					DirectMessageListenerContainer.this.startedLatch.countDown();
				}
			}
		}
	}
	private void consumeFromQueue(String queue) {
		List<SimpleConsumer> list = this.consumersByQueue.get(queue);
		// Possible race with setConsumersPerQueue and the task launched by start()
		if (CollectionUtils.isEmpty(list)) {
			for (int i = 0; i < this.consumersPerQueue; i++) {
				doConsumeFromQueue(queue);
			}
		}
	}

Enter the doConsumeFromQueue method

	private void doConsumeFromQueue(String queue) {
		..............
		Connection connection = null; // NOSONAR (close)
		try {
			connection = getConnectionFactory().createConnection();
		}
		
		SimpleConsumer consumer = consume(queue, connection);
		...........
	}

	private SimpleConsumer consume(String queue, Connection connection) {
		Channel channel = null;
		SimpleConsumer consumer = null;
		try {
			channel = connection.createChannel(isChannelTransacted());
			channel.basicQos(getPrefetchCount());
			consumer = new SimpleConsumer(connection, channel, queue);
			channel.queueDeclarePassive(queue);
			consumer.consumerTag = channel.basicConsume(queue, getAcknowledgeMode().isAutoAck(),
					(getConsumerTagStrategy() != null
							? getConsumerTagStrategy().createConsumerTag(queue) : ""), // NOSONAR never null
					isNoLocal(), isExclusive(), getConsumerArguments(), consumer);
		}
		.......................
		return consumer;
	}

Finally saw some clues.

1. First create a channel for interacting with rabbitmq.

2. Create a SimpleConsumer for user consumption. SimpleConsumer is the inner class of DirectMessageListenerContainer and inherits from DefaultConsumer. If it is created by SimpleMessageListenerContainer, it is InternalConsumer.

3. Declare which queue the channel interacts with channel.queueDeclarePassive(queue).

    public Queue.DeclareOk queueDeclarePassive(String queue)
        throws IOException
    {
        validateQueueNameLength(queue);
        return (Queue.DeclareOk)
               exnWrappingRpc(new Queue.Declare.Builder()
                               .queue(queue)
                               .passive()
                               .exclusive()
                               .autoDelete()
                              .build())
               .getMethod();
    }

4. Establish the mapping relationship between channel and SimpleConsumer. In the channel.basicConsume method, the consumer is the last input parameter.

    public String basicConsume(String queue, final boolean autoAck, String consumerTag,
                               boolean noLocal, boolean exclusive, Map<String, Object> arguments,
                               final Consumer callback)
        throws IOException
    {
        final Method m = new Basic.Consume.Builder()
            .queue(queue)
            .consumerTag(consumerTag)
            .noLocal(noLocal)
            .noAck(autoAck)
            .exclusive(exclusive)
            .arguments(arguments)
            .build();
        BlockingRpcContinuation<String> k = new BlockingRpcContinuation<String>(m) {
            @Override
            public String transformReply(AMQCommand replyCommand) {
                String actualConsumerTag = ((Basic.ConsumeOk) replyCommand.getMethod()).getConsumerTag();
                _consumers.put(actualConsumerTag, callback);
                ..................
            }
        }
    }

You can see that according to the consumption tag, the corresponding consumer is found.

Here is a brief introduction to the steps of creating a connection. Interested students can look at the source code by themselves.

1. It will be created through the newConnection method of the com.rabbitmq.client.ConnectionFactory class.

2. Create a FrameHandler object, which is mainly used to connect with the Socket of RabbitMq.

3. Create an AMQConnection object and encapsulate FrameHandler.

4. Start the AMQConnection.start method. The SocketFrameHandler.initialize method will be called, and the AMQConnection.run method will eventually be called

        public void run() {
            boolean shouldDoFinalShutdown = true;
            try {
                while (_running) {
                    Frame frame = _frameHandler.readFrame();
                    readFrame(frame);
                }
            } catch (Throwable ex) {
               ............
            } finally {
                if (shouldDoFinalShutdown) {
                    doFinalShutdown();
                }
            }
        }

Among them, readFrame will be executed after the news comes.

read message:

1. The handleCompleteInboundCommand method of AMQChannel, execute processAsync, and then execute processDelivery.

   protected void processDelivery(Command command, Basic.Deliver method) {
        Basic.Deliver m = method;

        Consumer callback = _consumers.get(m.getConsumerTag());
        ......................
        try {
            ..................
            this.dispatcher.handleDelivery(callback,
                                           m.getConsumerTag(),
                                           envelope,
                                           (BasicProperties) command.getContentHeader(),
                                           command.getContentBody());
        } catch (WorkPoolFullException e) {
            // couldn't enqueue in work pool, propagating
            throw e;
        } 
    }

2. According to the consumer tag just put in, find the consumer. Execute the dispatcher.handleDelivery method

    public void handleDelivery(final Consumer delegate,
                               final String consumerTag,
                               final Envelope envelope,
                               final AMQP.BasicProperties properties,
                               final byte[] body) throws IOException {
        executeUnlessShuttingDown(
        new Runnable() {
            @Override
            public void run() {
                try {
                    delegate.handleDelivery(consumerTag,
                            envelope,
                            properties,
                            body);
                } catch (Throwable ex) {
                    connection.getExceptionHandler().handleConsumerException(
                            channel,
                            ex,
                            delegate,
                            consumerTag,
                            "handleDelivery");
                }
            }
        });
    }

3. Execute the SimpleConsumer.handleDelivery method again. I just said that SimpleConsumer is the internal class of DirectMessageListenerContainer, and it actually executes the handleDelivery method of the DirectMessageListenerContainer class.

PS: Let me talk about it here

①: If it is DirectMessageListenerContainer, execute callExecuteListener through SimpleConsumer.

		public void handleDelivery(String consumerTag, Envelope envelope,
				BasicProperties properties, byte[] body) {

			MessageProperties messageProperties =
					getMessagePropertiesConverter().toMessageProperties(properties, envelope, "UTF-8");
			messageProperties.setConsumerTag(consumerTag);
			messageProperties.setConsumerQueue(this.queue);
			Message message = new Message(body, messageProperties);
			long deliveryTag = envelope.getDeliveryTag();
			if (this.logger.isDebugEnabled()) {
				this.logger.debug(this + " received " + message);
			}
			updateLastReceive();
                        if(){
			.............
			}else {
				try {
					callExecuteListener(message, deliveryTag);
				}
				catch (Exception e) {
					// NOSONAR
				}
			}
		}

 

②: If it is SimpleMessageListenerContainer, InternalConsumer executes handleDelivery and puts messages into the queue.

		public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
				byte[] body) {
			try {
				if (BlockingQueueConsumer.this.abortStarted > 0) {
					if (!BlockingQueueConsumer.this.queue.offer(
							new Delivery(consumerTag, envelope, properties, body, this.queueName),
							BlockingQueueConsumer.this.shutdownTimeout, TimeUnit.MILLISECONDS)) {

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

After that, the following code will be executed in the run method of SimpleMessageListenerContainer.

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

4. Execute the callExecuteListener method. After following up all the way, you will enter the actualInvokeListener method of AbstractMessageListenerContainer. And finally enter the doInvokeListener method.

	protected void doInvokeListener(ChannelAwareMessageListener listener, Channel channel, Object data) {

		    ...........................
			// Actually invoke the message listener...
			try {
				if (data instanceof List) {
					listener.onMessageBatch((List<Message>) data, channelToUse);
				}
				else {
					message = (Message) data;
					listener.onMessage(message, channelToUse);
				}
			}
			catch (Exception e) {
				throw wrapToListenerExecutionFailedExceptionIfNeeded(e, data);
			}
		}
		finally {
			cleanUpAfterInvoke(resourceHolder, channelToUse, boundHere);
		}
	}

5. Find the previously created implementation class MessagingMessageListenerAdapter.

	public void onMessage(org.springframework.amqp.core.Message amqpMessage, Channel channel) throws Exception { // NOSONAR
		Message<?> message = toMessagingMessage(amqpMessage);
		invokeHandlerAndProcessResult(amqpMessage, channel, message);
	}
	protected void invokeHandlerAndProcessResult(@Nullable org.springframework.amqp.core.Message amqpMessage,
			Channel channel, Message<?> message) throws Exception { // NOSONAR

		InvocationResult result = null;
		try {
			if (this.messagingMessageConverter.method == null) {
				amqpMessage.getMessageProperties()
						.setTargetMethod(this.handlerAdapter.getMethodFor(message.getPayload()));
			}
			result = invokeHandler(amqpMessage, channel, message);
			
		}
		catch (ListenerExecutionFailedException e) {
		}
	}

6. The invokeHandler method is the handlerAdapter created before, and the corresponding Bean method can be executed.

	public InvocationResult invoke(Message<?> message, Object... providedArgs) throws Exception { // NOSONAR
		if (this.invokerHandlerMethod != null) {
			return new InvocationResult(this.invokerHandlerMethod.invoke(message, providedArgs),
					null, this.invokerHandlerMethod.getMethod().getGenericReturnType(),
					this.invokerHandlerMethod.getBean(),
					this.invokerHandlerMethod.getMethod());
		}
		else if (this.delegatingHandler.hasDefaultHandler()) {
			// Needed to avoid returning raw Message which matches Object
			Object[] args = new Object[providedArgs.length + 1];
			args[0] = message.getPayload();
			System.arraycopy(providedArgs, 0, args, 1, providedArgs.length);
			return this.delegatingHandler.invoke(message, args);
		}
		else {
			return this.delegatingHandler.invoke(message, providedArgs);
		}
	}

Overall summary:

Create a receiving link:

SimpleMessageListenerContainer和DirectMessageListenerContainer是AbstractMessageListenerContainer子纸

1. Find MessagingMessageListenerAdapter through SimpleMessageListenerContainer or DirectMessageListenerContainer

2. Find the corresponding HandlerAdapter through MessagingMessageListenerAdapter

3. Find the corresponding Bean and method through HandlerAdapter

Read Receive:

1. It will be created through the newConnection method of the com.rabbitmq.client.ConnectionFactory class.

2. Create a FrameHandler object, which is mainly used to connect with the Socket of RabbitMq.

3. Create an AMQConnection object and encapsulate FrameHandler.

4. Start the AMQConnection.start method. The SocketFrameHandler.initialize method will be called, and finally AMQConnection.run will be called to prepare for receiving

5. The handleCompleteInboundCommand method of AMQChannel, execute processAsync, and then execute processDelivery.

6. Find the consumer according to the consumer tag you just put in. Execute the dispatcher.handleDelivery method.

7. Execute the SimpleConsumer.handleDelivery method again. I just said that SimpleConsumer is the internal class of DirectMessageListenerContainer, and it actually executes the handleDelivery method of the DirectMessageListenerContainer class.

8. Execute the callExecuteListener method. After following up all the way, you will enter the actualInvokeListener method of AbstractMessageListenerContainer. And finally enter the doInvokeListener method

9. Find the previously created implementation class MessagingMessageListenerAdapter.

In fact, readers only need to remember the three important parts marked in blue above, and the subsequent creation and reception can be ignored. Know how to establish a corresponding relationship.

Spring Stream receives RabbitMQ messages to find the source code analysis of the corresponding class here

 

Guess you like

Origin blog.csdn.net/yytree123/article/details/108922367