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 클래스에서 상속되며, 이 두 클래스에서 생성된 SimpleMessageListenerContainer 및 DirectMessageListenerContainer는 주로 RabbitMQ에서 메시지를 수신하고 이를 자체 메서드로 전달하는 역할을 합니다. 모두가 먼저 이 두 클래스를 기억합니다.

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라는 두 클래스에 등록됩니다.

먼저 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.handleDelivery 메소드 실행

    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 메서드를 실행한다고 말했습니다.

추신: 여기에 대해 이야기하겠습니다.

① : 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는 handleDelivery를 실행하고 메시지를 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);
			}
		}

이후 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.handleDelivery 메서드를 실행합니다.

7. SimpleConsumer.handleDelivery 메서드를 다시 실행합니다. 방금 SimpleConsumer는 DirectMessageListenerContainer의 내부 클래스이며 실제로 DirectMessageListenerContainer 클래스의 handleDelivery 메서드를 실행한다고 말했습니다.

8. callExecuteListener 메서드를 실행합니다. 끝까지 추적한 후 AbstractMessageListenerContainer의 actualInvokeListener 메서드를 입력합니다. 마지막으로 doInvokeListener 메서드를 입력합니다.

9. 이전에 생성한 구현 클래스 MessagingMessageListenerAdapter를 찾습니다.

사실 독자들은 위의 파란색으로 표시된 세 가지 중요한 부분만 기억하면 되고 이후 생성 및 수신은 무시할 수 있습니다. 해당 관계를 설정하는 방법을 알고 있습니다.

Spring Stream은 RabbitMQ 메시지를 받아 해당 클래스의 소스코드 분석을 여기서 찾습니다.

 

Supongo que te gusta

Origin blog.csdn.net/yytree123/article/details/108922367
Recomendado
Clasificación