Spring Boot、Spring Cloud Stream は RabbitMQ ソースコード分析を統合します

Spring Boot は RabbitMQ を非常に適切にサポートしています。今日は、RabbitMQ がソース コード レベルからメッセージをプッシュした後のモニタリング メソッドを見つける方法と、Spring Cloud Stream でメッセージを見つける方法を簡単に分析します。

まず pom ファイルを導入します。バージョンは Spring Boot 2.2.7 に基づいています。

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

構成クラス 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);
    }
}

モニタークラス:

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

送信クラス:

@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 + "'");
        }
    }
}

スタートアップクラス:

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

アプリケーション.yml:

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

これらは起動直後に実行でき、ソースコードを直接解析できるようになりました。

私たちが分析したいのは、RabbitMQ が @RabbitListener(queues = {RabbitmqConfig.QUEUE_INFORM_EMAIL}) アノテーションでマークされたメソッドをどのように直接見つけるかです。

spring-boot-autoconfigure.jar パッケージの spring.factories ファイルでは、RabbitAutoConfiguration クラスが自動的に挿入されていることがわかります。

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

RabbitAnnotationDrivenConfiguration クラスを直接見てみましょう。

@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 と DirectRabbitListenerContainerFactory の方が重要です。どちらのクラスも AbstractRabbitListenerContainerFactory クラスを継承しており、これら 2 つのクラスによって作成された SimpleMessageListenerContainer と DirectMessageListenerContainer は主に、RabbitMQ からメッセージを受信し、独自のメソッドに転送する役割を果たします。皆さんはまずこの 2 つのクラスを覚えてください。

EnableRabbit は RabbitAnnotationDrivenConfiguration クラスにあります。

@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 は RabbitBootstrapConfiguration をインポートします。RabbitBootstrapConfiguration は、RabbitListenerAnnotationBeanPostProcessor と RabbitListenerEndpointRegistry の 2 つのクラスに登録されます。

まず、RabbitListenerAnnotationBeanPostProcessor クラスの postProcessAfterInitialization メソッドを見ていきます。

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

各Beanオブジェクトの作成後、Bean内に@RabbitListenerアノテーションを含むメソッドが存在するかどうかを検出し、存在する場合はprocessAmqpListenerメソッドが実行されます。

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

このメソッドは、MethodRabbitListenerEndpoint (ウサギのリスニング メソッド ターミナル) オブジェクトを作成します。

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

エンドポイントの一連の設定が完了したら、this.registrar.registerEndpoint(endpoint, Factory); メソッドを実行します。registrar=RabbitListenerEndpointRegistrar()、クラスの作成時に定義されます。

RabbitListenerEndpointRegistrar.registerEndpoint メソッド

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

startImmediately=false なので、ここのリストにエンドポイントを追加するだけです。

引き続き、RabbitListenerAnnotationBeanPostProcessor クラスの afterSingletonsInstantiated メソッドを見ていきます。

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

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

ここでの endpointRegistry は RabbitListenerEndpointRegistry クラスです。registerListenerContainerメソッドを実行します。

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

createListenerContainer メソッドに進みます。

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

ここでの createContainerInstance メソッドは、以前に作成された SimpleMessageListenerContainer および DirectMessageListenerContainer のいずれかに基づいています。

その後、 endpoint.setupListenerContainer(instance); この行まで実行されます。

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

createMessageListener メソッドは MessagingMessageListenerAdapter オブジェクトを作成し、その後、configureListenerAdapter(messageListener) メソッドは、対応する Bean とメソッドを関連付けるための HandlerAdapter オブジェクトを作成します。

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

ここでの手順は次のとおりです

1. SimpleMessageListenerContainer または DirectMessageListenerContainer を通じて MessagingMessageListenerAdapter を検索します

2. MessagingMessageListenerAdapter を通じて、対応する HandlerAdapter を見つけます。

3. HandlerAdapter を通じて、対応する Bean とメソッドを見つけます。

さて、焦点は、RabbitMq を通じて SimpleMessageListenerContainer または DirectMessageListenerContainer を見つける方法にあります。

RabbitBootstrapConfiguration クラスに戻ると、このクラスが RabbitListenerEndpointRegistry も登録していることがわかります。

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

このクラスは SmartLifecycle メソッドを実装します。次に、その起動メソッドを見てみましょう。

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

上記はすべて似ていますが、主に doStart メソッドについて説明します。DirectMessageListenerContainer を例に挙げますが、SimpleMessageListenerContainer も同様です。

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

名前を見るだけで開始コンシューマが分かります。startConsumers メソッドを見るだけです。

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

doConsumeFromQueue メソッドを入力します

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

ようやくいくつかの手がかりが見えてきました。

1. まず、rabbitmq と対話するためのチャネルを作成します。

2. ユーザーが使用する SimpleConsumer を作成します。SimpleConsumer は DirectMessageListenerContainer の内部クラスであり、DefaultConsumer を継承します。SimpleMessageListenerContainer で作成した場合は InternalConsumer になります。

3. チャネルがどのキューと対話するかを 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. チャネルと SimpleConsumer の間のマッピング関係を確立します。channel.basicConsume メソッドでは、コンシューマは最後の入力パラメータです。

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

消費タグに従って、対応する消費者が見つかったことがわかります。

ここでは、接続を作成する手順を簡単に紹介します。興味のある学生は、自分でソース コードを確認してください。

1. com.rabbitmq.client.ConnectionFactory クラスの newConnection メソッドを通じて作成されます。

2. 主に RabbitMq の Socket に接続するために使用する FrameHandler オブジェクトを作成します。

3. AMQConnection オブジェクトを作成し、FrameHandler をカプセル化します。

4. AMQConnection.start メソッドを開始します。SocketFrameHandler.initialize メソッドが呼び出され、最終的に AMQConnection.run メソッドが呼び出されます。

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

このうち、readFrame はニュースが来た後に実行されます。

メッセージを読む:

1. AMQChannel の handleCompleteInboundCommand メソッドで、processAsync を実行し、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. 入力したばかりの消費者タグに従って、消費者を見つけます。dispatcher.handleDeliverメソッドを実行する

    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. SimpleConsumer.handledelivery メソッドを再度実行します。SimpleConsumer は DirectMessageListenerContainer の内部クラスであり、実際には DirectMessageListenerContainer クラスの handledelivery メソッドを実行すると述べました。

PS: ここでそれについて話しましょう

①:DirectMessageListenerContainerの場合、SimpleConsumer経由でcallExecuteListenerを実行します。

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

 

②:SimpleMessageListenerContainerの場合、InternalConsumerはhandleDeliverを実行し、メッセージをキューに入れます。

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

その後、SimpleMessageListenerContainerのrunメソッド内で以下のコードが実行されます。

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

4. callExecuteListenerメソッドを実行します。ここまで説明した後、AbstractMessageListenerContainer のactualInvokeListener メソッドに入ります。そして最後に doInvokeListener メソッドを入力します。

	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. 以前に作成した実装クラス 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. invokeHandlerメソッドは先ほど作成したhandlerAdapterであり、対応するBeanメソッドを実行できます。

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

全体的な概要:

受信リンクを作成します。

SimpleMessageListenerContainer と DirectMessageListenerContainer は AbstractMessageListenerContainer の子クラスです

1. SimpleMessageListenerContainer または DirectMessageListenerContainer を通じて MessagingMessageListenerAdapter を検索します

2. MessagingMessageListenerAdapter を通じて、対応する HandlerAdapter を見つけます。

3. HandlerAdapter を通じて、対応する Bean とメソッドを検索します。

読み取り受信:

1. com.rabbitmq.client.ConnectionFactory クラスの newConnection メソッドを通じて作成されます。

2. 主に RabbitMq の Socket に接続するために使用する FrameHandler オブジェクトを作成します。

3. AMQConnection オブジェクトを作成し、FrameHandler をカプセル化します。

4. AMQConnection.start メソッドを開始します。SocketFrameHandler.initialize メソッドが呼び出され、最後に AMQConnection.run が呼び出されて受信の準備が行われます。

5. AMQChannel の handleCompleteInboundCommand メソッドで、processAsync を実行し、processdelivery を実行します。

6. 入力したコンシューマ タグに従ってコンシューマを検索します。dispatcher.handleDeliverメソッドを実行します。

7. SimpleConsumer.handledelivery メソッドを再度実行します。SimpleConsumer は DirectMessageListenerContainer の内部クラスであり、実際には DirectMessageListenerContainer クラスの handledelivery メソッドを実行すると述べました。

8. callExecuteListenerメソッドを実行します。ここまで説明した後、AbstractMessageListenerContainer のactualInvokeListener メソッドに入ります。そして最後に doInvokeListener メソッドに入ります

9. 以前に作成した実装クラス MessagingMessageListenerAdapter を見つけます。

実際、読者は上で青くマークされた 3 つの重要な部分を覚えておくだけでよく、その後の作成と受信は無視できます。対応関係を確立する方法を知ってください。

Spring Stream は RabbitMQ メッセージを受信し、対応するクラスのソース コード分析をここで見つけます。

 

おすすめ

転載: blog.csdn.net/yytree123/article/details/108922367