Spring Cloud Stream source code analysis

Spring Cloud Stream is a framework for message-driven microservices.
  The application interacts with the binder  in Spring Cloud Stream through inputs  or outputs  , and binds through our configuration, and the binder of Spring Cloud Stream is responsible for interacting with the message middleware. Therefore, we only need to figure out how to interact with Spring Cloud Stream to use the message-driven approach conveniently.   By using Spring Integration to connect the message broker middleware to achieve message event driving. Spring Cloud Stream provides personalized automated configuration implementation for some vendors' message middleware products, referencing the three core concepts of publish-subscribe, consumer group, and partition. Currently only RabbitMQ and Kafka are supported .

Let's start the analysis, first introduce the pom file.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.3.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>eureka.stream</groupId>
	<artifactId>stream</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springstream</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Brixton.SR5</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Copy code

Among them, spring-cloud-starter-stream-rabbit is the introduced stream framework and can also support spring-cloud-starter-stream-kafka. Here we use rabbit for analysis.

First let's create a simple example.

First create the message input and output pipeline class StreamSendClient.

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;


public interface StreamSendClient {

    @Output("testMessage")
    MessageChannel output();
    
    @Input("testMessage")
    MessageChannel input();
}

Copy code

Create another message processing class SinkReceiver. Add the @EnableBinding annotation above. The class defined by the annotation is StreamSendClient.

@EnableBinding({StreamSendClient.class})
public class SinkReceiver {

    @StreamListener("testMessage")
    public void reveive(Object payload){
        System.out.println("Received:" + payload);
    }
}

Copy code

Create the startup class StreamApplication.

@SpringBootApplication
public class StreamApplication {

	public static void main(String[] args) {
		ConfigurableApplicationContext run = SpringApplication.run(StreamApplication.class, args);
		StreamSendClient streamClient = (StreamSendClient)run.getBean("com.springcloud.eurekaclient.StreamSendClient");
		streamClient.output().send(MessageBuilder.withPayload("from streamClient").build());
	}

}

Copy code

After execution, the printed information can be found on the console: Received: from streamClient. At the same time, you can also see that the queue in the rabbitmq console contains testMessage.

Let’s start the analysis.

First, no new annotations are added to the startup class. There is an @EnableBinding annotation on SinkReceiver.

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class,
		SpelExpressionConverterConfiguration.class})
@EnableIntegration
public @interface EnableBinding {

	/**
	 * A list of interfaces having methods annotated with {@link Input} and/or
	 * {@link Output} to indicate bindable components.
	 */
	Class<?>[] value() default {};

}

Copy code

Know:

1. This class is a @Component class

2. This class imports ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class, SpelExpressionConverterConfiguration.class.

3. EnableIntegration annotation is turned on. Spring Integration is positioned as an Enterprise Service Bus (ESB). In Spring Integration, channels are abstracted into two forms of expression: PollableChannel and SubscribableChannel, both of which inherit MessageChannel.

ChannelBindingServiceConfiguration class analysis:

@Configuration
@EnableConfigurationProperties(ChannelBindingServiceProperties.class)
public class ChannelBindingServiceConfiguration {

	private static final String ERROR_CHANNEL_NAME = "error";

	@Autowired
	private MessageBuilderFactory messageBuilderFactory;

	@Autowired(required = false)
	private ObjectMapper objectMapper;

	/**
	 * User defined custom message converters
	 */
	@Autowired(required = false)
	private List<AbstractFromMessageConverter> customMessageConverters;

	@Bean
	// This conditional is intentionally not in an autoconfig (usually a bad idea) because
	// it is used to detect a ChannelBindingService in the parent context (which we know
	// already exists).
	@ConditionalOnMissingBean(ChannelBindingService.class)
	public ChannelBindingService bindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
			BinderFactory<MessageChannel> binderFactory) {
		return new ChannelBindingService(channelBindingServiceProperties, binderFactory);
	}


	@Bean
	public BindableChannelFactory channelFactory(CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
		return new DefaultBindableChannelFactory(compositeMessageChannelConfigurer);
	}

	@Bean
	public CompositeMessageChannelConfigurer compositeMessageChannelConfigurer(
			MessageConverterConfigurer messageConverterConfigurer) {
		List<MessageChannelConfigurer> configurerList = new ArrayList<>();
		configurerList.add(messageConverterConfigurer);
		return new CompositeMessageChannelConfigurer(configurerList);
	}

	@Bean
	@DependsOn("bindingService")
	public OutputBindingLifecycle outputBindingLifecycle() {
		return new OutputBindingLifecycle();
	}

	@Bean
	@DependsOn("bindingService")
	public InputBindingLifecycle inputBindingLifecycle() {
		return new InputBindingLifecycle();
	}

	@Bean
	@DependsOn("bindingService")
	public ContextStartAfterRefreshListener contextStartAfterRefreshListener() {
		return new ContextStartAfterRefreshListener();
	}

	@Bean
	public static StreamListenerAnnotationBeanPostProcessor bindToAnnotationBeanPostProcessor(
			@Lazy BinderAwareChannelResolver binderAwareChannelResolver,
			@Lazy MessageHandlerMethodFactory messageHandlerMethodFactory) {
		return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver,
				messageHandlerMethodFactory);
	}

}

Copy code

ChannelBindingServiceConfiguration loads important beans:

1. ChannelBindingService: Responsible for creating the MessageChannel of producers and consumers, as well as the exchange (Exchange), Queue, etc. in RabbitMQ.

2. inputBindingLifecycle, outputBindingLifecycle: Mainly responsible for calling ChannelBindingService to create after startup.

3. StreamListenerAnnotationBeanPostProcessor: Responsible for creating an association between the method annotated with @StreamListener and the RabbitMQ consumption channel. That is, when a rabbitmq message is pushed over, the execution method has a method annotated with @StreamListener.

BindingBeansRegistrar class analysis:

BindingBeansRegistrar mainly analyzes the classes in the implementation of @EnableBinding(StreamSendClient.class).

public class BindingBeansRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		AnnotationAttributes attrs = AnnotatedElementUtils.getMergedAnnotationAttributes(
				ClassUtils.resolveClassName(metadata.getClassName(), null),
				EnableBinding.class);
		for (Class<?> type : collectClasses(attrs, metadata.getClassName())) {
			BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions(type,
					type.getName(), registry);
			BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions(
					ClassUtils.resolveClassName(metadata.getClassName(), null), type,
					registry);
		}
	}

	private Class<?>[] collectClasses(AnnotationAttributes attrs, String className) {
		EnableBinding enableBinding = AnnotationUtils.synthesizeAnnotation(attrs,
				EnableBinding.class, ClassUtils.resolveClassName(className, null));
		return enableBinding.value();
	}

}

Copy code

Through the collectClasses method, obtain enableBinding.value() in EnableBinding, which is the StreamSendClient class in the previous example. BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions method after

public static void registerChannelBeanDefinitions(Class<?> type,
			final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
		ReflectionUtils.doWithMethods(type, new MethodCallback() {
			@Override
			public void doWith(Method method) throws IllegalArgumentException,
					IllegalAccessException {
				Input input = AnnotationUtils.findAnnotation(method, Input.class);
				if (input != null) {
					String name = getChannelName(input, method);
					registerInputChannelBeanDefinition(input.value(), name,
							channelInterfaceBeanName, method.getName(), registry);
				}
				Output output = AnnotationUtils.findAnnotation(method, Output.class);
				if (output != null) {
					String name = getChannelName(output, method);
					registerOutputChannelBeanDefinition(output.value(), name,
							channelInterfaceBeanName, method.getName(), registry);
				}
			}

		});
	}

Copy code

public static void registerChannelBeanDefinitions(Class<?> type,
			final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
		ReflectionUtils.doWithMethods(type, new MethodCallback() {
			@Override
			public void doWith(Method method) throws IllegalArgumentException,
					IllegalAccessException {
				Input input = AnnotationUtils.findAnnotation(method, Input.class);
				if (input != null) {
					String name = getChannelName(input, method);
					registerInputChannelBeanDefinition(input.value(), name,
							channelInterfaceBeanName, method.getName(), registry);
				}
				Output output = AnnotationUtils.findAnnotation(method, Output.class);
				if (output != null) {
					String name = getChannelName(output, method);
					registerOutputChannelBeanDefinition(output.value(), name,
							channelInterfaceBeanName, method.getName(), registry);
				}
			}

		});
	}

Copy code

        public static void registerInputChannelBeanDefinition(String qualifierValue,
			String name, String channelInterfaceBeanName,
			String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
		registerChannelBeanDefinition(Input.class, qualifierValue, name,
				channelInterfaceBeanName, channelInterfaceMethodName, registry);
	}

	private static void registerChannelBeanDefinition(
			Class<? extends Annotation> qualifier, String qualifierValue, String name,
			String channelInterfaceBeanName, String channelInterfaceMethodName,
			BeanDefinitionRegistry registry) {

		RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
		rootBeanDefinition.setFactoryBeanName(channelInterfaceBeanName);
		rootBeanDefinition.setUniqueFactoryMethodName(channelInterfaceMethodName);
		rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier,
				qualifierValue));
		registry.registerBeanDefinition(name, rootBeanDefinition);
	}

Copy code

Find the @input and @output annotations in the StreamSendClient class and inject the two methods into beans. The name of the bean is the value of the @Input annotation in StreamSendClient, which is testMessage. And set the factory class that defines the BeanDefinition to generate the Bean to StreamSendClient, and set the method setUniqueFactoryMethodName to generate the Bean (testMessage) to input().

BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions方法:

public static void registerChannelsQualifiedBeanDefinitions(Class<?> parent,
			Class<?> type, final BeanDefinitionRegistry registry) {

		if (type.isInterface()) {
			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(
					BindableProxyFactory.class);
			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
					Bindings.class, parent));
			rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
					type);
			registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
		}
		else {
			RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(type);
			rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
					Bindings.class, parent));
			registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
		}
	}

Copy code

Inject a Bean object whose beanName is StreamSendClient, whose Qualifier is named Bindings, and whose BeanClass is BindableProxyFactory (Bindable). BindableProxyFactory implements the Bindable interface.

Let's take a look at the BindableProxyFactory source code here.

public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Object>, Bindable, InitializingBean {

	private Map<String, ChannelHolder> inputHolders = new HashMap<>();

	private Map<String, ChannelHolder> outputHolders = new HashMap<>();

	//实现了动态代理,所以当获取input和output方法获取channel时,会通过这里获得
	public synchronized Object invoke(MethodInvocation invocation) throws Throwable {
		MessageChannel messageChannel = null;
		Method method = invocation.getMethod();
		if (MessageChannel.class.isAssignableFrom(method.getReturnType())) {
			Input input = AnnotationUtils.findAnnotation(method, Input.class);
			if (input != null) {
				String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
				messageChannel = this.inputHolders.get(name).getMessageChannel();
			}
			Output output = AnnotationUtils.findAnnotation(method, Output.class);
			if (output != null) {
				String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
				messageChannel =  this.outputHolders.get(name).getMessageChannel();
			}
		}
		//ignore
		return messageChannel;
	}

	//实现了InitializingBean,在Bean生成后,会将input和output两个注解生成对应的channel
        //类,默认是DirectChannel类    
	public void afterPropertiesSet() throws Exception {
		ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(Method method) throws IllegalArgumentException {
				Assert.notNull(channelFactory, "Channel Factory cannot be null");
				Input input = AnnotationUtils.findAnnotation(method, Input.class);
				if (input != null) {
					String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
					validateChannelType(method.getReturnType());
					MessageChannel sharedChannel = locateSharedChannel(name);
					if (sharedChannel == null) {
						inputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
					}
					else {
						inputHolders.put(name, new ChannelHolder(sharedChannel, false));
					}
				}
			}
		});
		ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(Method method) throws IllegalArgumentException {
				Output output = AnnotationUtils.findAnnotation(method, Output.class);
				if (output != null) {
					String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
					validateChannelType(method.getReturnType());
					MessageChannel sharedChannel = locateSharedChannel(name);
					if (sharedChannel == null) {
						outputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
					}
					else {
						outputHolders.put(name, new ChannelHolder(sharedChannel, false));
					}
				}
			}

		});
	}

	private void validateChannelType(Class<?> channelType) {
		Assert.isTrue(SubscribableChannel.class.equals(channelType) || MessageChannel.class.equals(channelType),
				"A bound channel should be either a '" + MessageChannel.class.getName() + "', " +
						" or a '" + SubscribableChannel.class.getName() + "'");
	}

	private MessageChannel locateSharedChannel(String name) {
		return this.sharedChannelRegistry != null ?
				this.sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
	}

	private String getNamespacePrefixedChannelName(String name) {
		return this.channelNamespace + "." + name;
	}

	@Override
	public synchronized Object getObject() throws Exception {
		if (this.proxy == null) {
			ProxyFactory factory = new ProxyFactory(this.type, this);
			this.proxy = factory.getProxy();
		}
		return this.proxy;
	}

	@Override
	public Class<?> getObjectType() {
		return this.type;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	
	public void bindInputs(ChannelBindingService channelBindingService) {
		if (log.isDebugEnabled()) {
			log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
		}
		for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
			String inputChannelName = channelHolderEntry.getKey();
			ChannelHolder channelHolder = channelHolderEntry.getValue();
			if (channelHolder.isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
				}
				channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
			}
		}
	}

	@Override
	public void bindOutputs(ChannelBindingService channelBindingService) {
		if (log.isDebugEnabled()) {
			log.debug(String.format("Binding outputs for %s:%s", this.channelNamespace, this.type));
		}
		for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
			ChannelHolder channelHolder = channelHolderEntry.getValue();
			String outputChannelName = channelHolderEntry.getKey();
			if (channelHolderEntry.getValue().isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, outputChannelName));
				}
				channelBindingService.bindProducer(channelHolder.getMessageChannel(), outputChannelName);
			}
		}
	}

	@Override
	public void unbindInputs(ChannelBindingService channelBindingService) {
		if (log.isDebugEnabled()) {
			log.debug(String.format("Unbinding inputs for %s:%s", this.channelNamespace, this.type));
		}
		for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
			if (channelHolderEntry.getValue().isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Unbinding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
				}
				channelBindingService.unbindConsumers(channelHolderEntry.getKey());
			}
		}
	}

	@Override
	public void unbindOutputs(ChannelBindingService channelBindingService) {
		if (log.isDebugEnabled()) {
			log.debug(String.format("Unbinding outputs for %s:%s", this.channelNamespace, this.type));
		}
		for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
			if (channelHolderEntry.getValue().isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
				}
				channelBindingService.unbindProducers(channelHolderEntry.getKey());
			}
		}
	}

Copy code

BinderFactoryConfiguration class analysis

@Configuration
public class BinderFactoryConfiguration {

	@Bean
	@ConditionalOnMissingBean(BinderFactory.class)
	public BinderFactory<?> binderFactory(BinderTypeRegistry binderTypeRegistry,
			ChannelBindingServiceProperties channelBindingServiceProperties) {
		Map<String, BinderConfiguration> binderConfigurations = new HashMap<>();
		Map<String, BinderProperties> declaredBinders = channelBindingServiceProperties.getBinders();
		boolean defaultCandidatesExist = false;
		Iterator<Map.Entry<String, BinderProperties>> binderPropertiesIterator = declaredBinders.entrySet().iterator();
		while (!defaultCandidatesExist && binderPropertiesIterator.hasNext()) {
			defaultCandidatesExist = binderPropertiesIterator.next().getValue().isDefaultCandidate();
		}
		for (Map.Entry<String, BinderProperties> binderEntry : declaredBinders.entrySet()) {
			BinderProperties binderProperties = binderEntry.getValue();
			if (binderTypeRegistry.get(binderEntry.getKey()) != null) {
				binderConfigurations.put(binderEntry.getKey(),
						new BinderConfiguration(binderTypeRegistry.get(binderEntry.getKey()),
								binderProperties.getEnvironment(), binderProperties.isInheritEnvironment(),
								binderProperties.isDefaultCandidate()));
			}
			else {
				Assert.hasText(binderProperties.getType(),
						"No 'type' property present for custom binder " + binderEntry.getKey());
				BinderType binderType = binderTypeRegistry.get(binderProperties.getType());
				Assert.notNull(binderType, "Binder type " + binderProperties.getType() + " is not defined");
				binderConfigurations.put(binderEntry.getKey(),
						new BinderConfiguration(binderType, binderProperties.getEnvironment(),
								binderProperties.isInheritEnvironment(), binderProperties.isDefaultCandidate()));
			}
		}
		if (!defaultCandidatesExist) {
			for (Map.Entry<String, BinderType> entry : binderTypeRegistry.getAll().entrySet()) {
				binderConfigurations.put(entry.getKey(),
						new BinderConfiguration(entry.getValue(), new Properties(), true, true));
			}
		}
		DefaultBinderFactory<?> binderFactory = new DefaultBinderFactory<>(binderConfigurations);
		binderFactory.setDefaultBinder(channelBindingServiceProperties.getDefaultBinder());
		return binderFactory;
	}
}

Copy code

The main thing is to create a factory of DefaultBinderFactory.

The content of the three loading beans of ChannelBindingServiceConfiguration, BindingBeansRegistrar, and BinderFactoryConfiguration has been roughly introduced. Now let’s talk about the loading process:

1. The Bean objects outputBindingLifecycle and inputBindingLifecycle loaded by the ChannelBindingServiceConfiguration class. We use inputBindingLifecycle for analysis, and outputBindingLifecycle is similar.

	@Bean
	@DependsOn("bindingService")
	public OutputBindingLifecycle outputBindingLifecycle() {
		return new OutputBindingLifecycle();
	}

	@Bean
	@DependsOn("bindingService")
	public InputBindingLifecycle inputBindingLifecycle() {
		return new InputBindingLifecycle();
	}

Copy code

The inputBindingLifecycle class implements the SmartLifecycle interface, and the start method will be executed after spring is started.

public class InputBindingLifecycle implements SmartLifecycle, ApplicationContextAware {
	public void start() {
		if (!running) {
			// retrieve the ChannelBindingService lazily, avoiding early initialization
			try {
				ChannelBindingService channelBindingService = this.applicationContext
						.getBean(ChannelBindingService.class);
				Map<String, Bindable> bindables = this.applicationContext
						.getBeansOfType(Bindable.class);
				for (Bindable bindable : bindables.values()) {
                                        //bindables.values即为@@EnableBinding({StreamSendClient.class})类,BeanClass为BindableProxyFactory
					bindable.bindInputs(channelBindingService);
				}
			}
			catch (BeansException e) {
				throw new IllegalStateException(
						"Cannot perform binding, no proper implementation found", e);
			}
			this.running = true;
		}
	}
}

BindableProxyFactory.bindInputs方法如下:
	public void bindInputs(ChannelBindingService channelBindingService) {
		if (log.isDebugEnabled()) {
			log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
		}
		for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
			String inputChannelName = channelHolderEntry.getKey();
			ChannelHolder channelHolder = channelHolderEntry.getValue();
			if (channelHolder.isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
				}
                              //这里继续进入
                              channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
			}
		}

	public Collection<Binding<MessageChannel>> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
		....................
		validate(consumerProperties);
		for (String target : channelBindingTargets) {
                        //继续进入binder.bindConsumer方法
			Binding<MessageChannel> binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName), inputChannel, consumerProperties);
			bindings.add(binding);
		}
		this.consumerBindings.put(inputChannelName, bindings);
		return bindings;
	}
	}

会进入RabbitMessageChannelBinder.bindConsumer方法
	public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
			ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
		
		String prefix = properties.getExtension().getPrefix();
		String exchangeName = applyPrefix(prefix, name);
		TopicExchange exchange = new TopicExchange(exchangeName);
                //创建交换器
		declareExchange(exchangeName, exchange);

		String queueName = applyPrefix(prefix, baseQueueName);
		boolean partitioned = !anonymousConsumer && properties.isPartitioned();
		boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
		Queue queue;

		......................
                //创建队列
		declareQueue(queueName, queue);

                if (partitioned) {
			String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
			declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
		}
		else {
			declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
		}
                Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
		.................
		return binding;
	}

Copy code

You can see that the RabbitMq exchange (Exchange) and queue are created through inputBindingLifecycle.

In the same way, a producer will be created after starting through outputBindingLifecycle.

2. The Bean object StreamListenerAnnotationBeanPostProcessor loaded by the ChannelBindingServiceConfiguration class. StreamListenerAnnotationBeanPostProcessor implements the BeanPostProcessor interface. The postProcessAfterInitialization method will be executed.

public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, SmartInitializingSingleton {

    	public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
		Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean) : bean.getClass();
		ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
			@Override
			public void doWith(final Method method) throws IllegalArgumentException, IllegalAccessException {
                                // 步骤1
				StreamListener streamListener = AnnotationUtils.findAnnotation(method, StreamListener.class);
				if (streamListener != null) {
					Method targetMethod = checkProxy(method, bean);
					Assert.hasText(streamListener.value(), "The binding name cannot be null");
                                        //步骤2
					final InvocableHandlerMethod invocableHandlerMethod = messageHandlerMethodFactory.createInvocableHandlerMethod(bean, targetMethod);
					if (!StringUtils.hasText(streamListener.value())) {
						throw new BeanInitializationException("A bound component name must be specified");
					}
					if (mappedBindings.containsKey(streamListener.value())) {
						throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName() +
								" mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage() +
								" already existing for " + mappedBindings.get(streamListener.value()).getShortLogMessage());
					}
					mappedBindings.put(streamListener.value(), invocableHandlerMethod);
                                        //步骤3
					SubscribableChannel channel = applicationContext.getBean(streamListener.value(),
							SubscribableChannel.class);
					final String defaultOutputChannel = extractDefaultOutput(method);
					if (invocableHandlerMethod.isVoid()) {
						Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel), "An output channel cannot be specified for a method that " +
								"does not return a value");
					}
					else {
						Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel), "An output channel must be specified for a method that " +
								"can return a value");
					}
                                        //步骤4
					StreamListenerMessageHandler handler = new StreamListenerMessageHandler(invocableHandlerMethod);
					handler.setApplicationContext(applicationContext);
					handler.setChannelResolver(binderAwareChannelResolver);
					if (!StringUtils.isEmpty(defaultOutputChannel)) {
						handler.setOutputChannelName(defaultOutputChannel);
					}
					handler.afterPropertiesSet();
                                        //步骤5
					channel.subscribe(handler);
				}
			}
		});
		return bean;
	}
}

Copy code

postProcessAfterInitialization method process:

1. Find the method containing @StreamListener("testMessage")

2. Create an InvocableHandlerMethod proxy class, which executes the specific method we created.

3. The value of streamListener.value() is testMessage. What the Bean actually obtains is the Bean object testMessage obtained by the factory class StreamSendClient and the method input(). The specific way to generate this Bean has been mentioned above in the BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions method. Find the SubscribableChannel Bean corresponding to the @input and @output annotations previously generated by the BindableProxyFactory object through the afterPropertiesSet method.

PS: When obtaining the testMessage Bean object, first find the StreamSendClient class and then call the input method. Because rootBeanDefinition.setFactoryBeanName(StreamSendClient); was set previously.

rootBeanDefinition.setUniqueFactoryMethodName(input);

4. Create a StreamListenerMessageHandler object and use InvocableHandlerMethod as the input parameter in the constructor.

5. Add SubscribableChannel and subscribe to StreamListenerMessageHandler.

In this way, when the message comes, the InvocableHandlerMethod in StreamListenerMessageHandler will find a specific method to handle it.

========Separating line - Rabbit message finding corresponding method source code in-depth analysis======================

We know that in the RabbitMessageChannelBinder.bindConsumer method just now

RabbitMessageChannelBinder.bindConsumer方法
	public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
			ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
		
		String prefix = properties.getExtension().getPrefix();
		String exchangeName = applyPrefix(prefix, name);
		TopicExchange exchange = new TopicExchange(exchangeName);
                //创建交换器
		declareExchange(exchangeName, exchange);

		String queueName = applyPrefix(prefix, baseQueueName);
		boolean partitioned = !anonymousConsumer && properties.isPartitioned();
		boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
		Queue queue;

		......................
                //创建队列
		declareQueue(queueName, queue);

                if (partitioned) {
			String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
			declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
		}
		else {
			declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
		}
                Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
		.................
		return binding;
	}

Copy code

The last doRegisterConsumer method is where you find the corresponding processing method of the RabbitMq message.

	private Binding<MessageChannel> doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
			final ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
		DefaultBinding<MessageChannel> consumerBinding;
		SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
				this.connectionFactory);
		listenerContainer.setAcknowledgeMode(properties.getExtension().getAcknowledgeMode());
		listenerContainer.setChannelTransacted(properties.getExtension().isTransacted());
		listenerContainer.setDefaultRequeueRejected(properties.getExtension().isRequeueRejected());
		int concurrency = properties.getConcurrency();
		concurrency = concurrency > 0 ? concurrency : 1;
		listenerContainer.setConcurrentConsumers(concurrency);
		int maxConcurrency = properties.getExtension().getMaxConcurrency();
		if (maxConcurrency > concurrency) {
			listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
		}
		listenerContainer.setPrefetchCount(properties.getExtension().getPrefetch());
		listenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
		listenerContainer.setTxSize(properties.getExtension().getTxSize());
		listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
		listenerContainer.setQueues(queue);
		int maxAttempts = properties.getMaxAttempts();
		if (maxAttempts > 1 || properties.getExtension().isRepublishToDlq()) {
			RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
					.maxAttempts(maxAttempts)
					.backOffOptions(properties.getBackOffInitialInterval(),
							properties.getBackOffMultiplier(),
							properties.getBackOffMaxInterval())
					.recoverer(determineRecoverer(name, properties.getExtension().getPrefix(), properties.getExtension().isRepublishToDlq()))
					.build();
			listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
		}
		listenerContainer.setAfterReceivePostProcessors(this.decompressingPostProcessor);
		listenerContainer.setMessagePropertiesConverter(RabbitMessageChannelBinder.inboundMessagePropertiesConverter);
		listenerContainer.afterPropertiesSet();
		AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer);
		adapter.setBeanFactory(this.getBeanFactory());
		DirectChannel bridgeToModuleChannel = new DirectChannel();
		bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
		bridgeToModuleChannel.setBeanName(name + ".bridge");
		adapter.setOutputChannel(bridgeToModuleChannel);
		adapter.setBeanName("inbound." + name);
		DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
		mapper.setRequestHeaderNames(properties.getExtension().getRequestHeaderPatterns());
		mapper.setReplyHeaderNames(properties.getExtension().getReplyHeaderPatterns());
		adapter.setHeaderMapper(mapper);
		adapter.afterPropertiesSet();
		consumerBinding = new DefaultBinding<MessageChannel>(name, group, moduleInputChannel, adapter) {
			@Override
			protected void afterUnbind() {
				cleanAutoDeclareContext(properties.getExtension().getPrefix(), name);
			}
		};
		ReceivingHandler convertingBridge = new ReceivingHandler();
		convertingBridge.setOutputChannel(moduleInputChannel);
		convertingBridge.setBeanName(name + ".convert.bridge");
		convertingBridge.afterPropertiesSet();
		bridgeToModuleChannel.subscribe(convertingBridge);
		adapter.start();
		return consumerBinding;
	}

Copy code

1. You can see that Spring Stream has created SimpleMessageListenerContainer. The function of this class is that RabbitMQ receives messages and lets the corresponding method process them. In SpringRabbit, you can find the @RabbitListener annotation. For detailed analysis, you can see the previous article on SpringBoot integrating Rabbit .

Here we only need to know that SimpleMessageListenerContainer is a container responsible for interacting with RabbitMQ and local listening methods.

2. Then create an AmqpInboundChannelAdapter object, and the input parameter is SimpleMessageListenerContainer. And created a DirectChannel object named bridgeToModuleChannel, and set the Adapter's OutputChannel to DirectChannel. Then call the adapter's afterPropertiesSet method. The afterPropertiesSet method will call its own onInit method.

	protected void onInit() {
		this.messageListenerContainer.setMessageListener(new ChannelAwareMessageListener() {

			@Override
			public void onMessage(Message message, Channel channel) throws Exception {
				Object payload = AmqpInboundChannelAdapter.this.messageConverter.fromMessage(message);
				Map<String, Object> headers =
						AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties());
				if (AmqpInboundChannelAdapter.this.messageListenerContainer.getAcknowledgeMode()
						== AcknowledgeMode.MANUAL) {
					headers.put(AmqpHeaders.DELIVERY_TAG, message.getMessageProperties().getDeliveryTag());
					headers.put(AmqpHeaders.CHANNEL, channel);
				}
				sendMessage(getMessageBuilderFactory().withPayload(payload).copyHeaders(headers).build());
			}

		});
		this.messageListenerContainer.afterPropertiesSet();
		super.onInit();
	}

Copy code

As you can see from the onInit method, the message listening of SimpleMessageListenerContainer is set to a custom method that implements the ChannelAwareMessageListener interface. In SpringBoot's integration of RabbitMQ, the object of setMessageListener is MessagingMessageListenerAdapter.

3. Create a ReceivingHandler object. The ReceivingHandler object implements the MessageHandler interface. And set the OutputChannel of ReceivingHandler to the message channel created by BindableProxyFactory. It has been analyzed before that in the postProcessAfterInitialization of the StreamListenerAnnotationBeanPostProcessor object, the message channel has been added to the StreamListenerMessageHandler, and the specific processing Bean and Method have been created in the StreamListenerMessageHandler.

4. Add the observer ReceivingHandler to bridgeToModuleChannel.

The following analyzes the steps experienced when an MQ message arrives:

1. First, the onMessage method of SimpleMessageListenerContainer will be called. For detailed analysis, you can read the previous article on SpringBoot integrating Rabbit.

2. onMessage will call the sendMessage method.

	public void onMessage(Message message, Channel channel) throws Exception {
				Object payload = AmqpInboundChannelAdapter.this.messageConverter.fromMessage(message);
				Map<String, Object> headers =
						AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties());
				if (AmqpInboundChannelAdapter.this.messageListenerContainer.getAcknowledgeMode()
						== AcknowledgeMode.MANUAL) {
					headers.put(AmqpHeaders.DELIVERY_TAG, message.getMessageProperties().getDeliveryTag());
					headers.put(AmqpHeaders.CHANNEL, channel);
				}
				sendMessage(getMessageBuilderFactory().withPayload(payload).copyHeaders(headers).build());
	}
        protected void sendMessage(Message<?> message) {
		................
		try {
                        //这里的OutputChannel就是之前的bridgeToModuleChannel
			this.messagingTemplate.send(getOutputChannel(), message);
		}
                ................
	}
	public void send(D destination, Message<?> message) {
		doSend(destination, message);
	}
	protected final void doSend(MessageChannel channel, Message<?> message) {
		......................

		boolean sent = (timeout >= 0 ? channel.send(message, timeout) : channel.send(message));

		..................
	}

Copy code

The channel.send(message) method will call the send method of AbstractMessageChannel

①:

	public final boolean send(Message<?> message, long timeout) {
		Assert.notNull(message, "message must not be null");
		Assert.notNull(message.getPayload(), "message payload must not be null");
		.............
			sent = this.doSend(message, timeout);
			if (countsEnabled) {
				channelMetrics.afterSend(metrics, sent);
				metricsProcessed = true;
			}

			if (debugEnabled) {
				logger.debug("postSend (sent=" + sent + ") on channel '" + this + "', message: " + message);
			}
			if (interceptorStack != null) {
				interceptors.postSend(message, this, sent);
				interceptors.afterSendCompletion(message, this, sent, null, interceptorStack);
			}
			return sent;

		....................
	}

Copy code

The doSend method here will call the doSend method of AbstractSubscribableChannel

②:

	protected boolean doSend(Message<?> message, long timeout) {
		try {
			return getRequiredDispatcher().dispatch(message);
		}
		catch (MessageDispatchingException e) {
			String description = e.getMessage() + " for channel '" + this.getFullChannelName() + "'.";
			throw new MessageDeliveryException(message, description, e);
		}
	}
        //UnicastingDispatcher类的dispatch方法
	public final boolean dispatch(final Message<?> message) {
		if (this.executor != null) {
			Runnable task = createMessageHandlingTask(message);
			this.executor.execute(task);
			return true;
		}
		return this.doDispatch(message);
	}

	private boolean doDispatch(Message<?> message) {
		if (this.tryOptimizedDispatch(message)) {
			return true;
		}
		....................
		return success;
	}
	protected boolean tryOptimizedDispatch(Message<?> message) {
		MessageHandler handler = this.theOneHandler;
		if (handler != null) {
			try {
				handler.handleMessage(message);
				return true;
			}
			catch (Exception e) {
				throw wrapExceptionIfNecessary(message, e);
			}
		}
		return false;
	}

Copy code

Finally, the handler.handleMessage method will be called. From the creation just now, we can see that the handler of bridgeToModuleChannel is ReceivingHandler. So the ReceivingHandler.handleMessage method will be called. ReceivingHandler inherits from AbstractReplyProducingMessageHandler, and AbstractReplyProducingMessageHandler inherits from AbstractMessageHandler. So the AbstractMessageHandler.handleMessage method will be called.

③:

	public final void handleMessage(Message<?> message) {
	        ................
		try {
			............
			this.handleMessageInternal(message);
			............
		}
		catch (Exception e) {
			...........
		}
	}

Copy code

The AbstractReplyProducingMessageHandler.handleMessageInternal method will then be executed

④:

	protected final void handleMessageInternal(Message<?> message) {
		Object result;
		if (this.advisedRequestHandler == null) {
			result = handleRequestMessage(message);
		}
		else {
			result = doInvokeAdvisedRequestHandler(message);
		}
		if (result != null) {
			sendOutputs(result, message);
		}
		else if (this.requiresReply && !isAsync()) {
			throw new ReplyRequiredException(message, "No reply produced by handler '" +
					getComponentName() + "', and its 'requiresReply' property is set to true.");
		}
		else if (!isAsync() && logger.isDebugEnabled()) {
			logger.debug("handler '" + this + "' produced no reply for request Message: " + message);
		}
	}

Copy code

 The ReceivingHandler.handleRequestMessage method will be executed to deserialize the message, etc. sendOutputs will be executed later.

	protected void sendOutputs(Object result, Message<?> requestMessage) {
		if (result instanceof Iterable<?> && shouldSplitOutput((Iterable<?>) result)) {
			for (Object o : (Iterable<?>) result) {
				this.produceOutput(o, requestMessage);
			}
		}
		else if (result != null) {
			this.produceOutput(result, requestMessage);
		}
	}

	protected void produceOutput(Object reply, final Message<?> requestMessage) {
		final MessageHeaders requestHeaders = requestMessage.getHeaders();

		Object replyChannel = null;
		if (getOutputChannel() == null) {
			............
	
		}

		if (this.async && reply instanceof ListenableFuture<?>) {
		    .......................
		}
		else {
			sendOutput(createOutputMessage(reply, requestHeaders), replyChannel, false);
		}
	}

Copy code

	protected void sendOutput(Object output, Object replyChannel, boolean useArgChannel) {
		MessageChannel outputChannel = getOutputChannel();
		....................

		if (replyChannel instanceof MessageChannel) {
			if (output instanceof Message<?>) {
				this.messagingTemplate.send((MessageChannel) replyChannel, (Message<?>) output);
			}
			else {
				this.messagingTemplate.convertAndSend((MessageChannel) replyChannel, output);
			}
		}
		..................
	}

Copy code

Here the getOutputChannel of ReceivingHandler.sendOutput is the message channel created by BindableProxyFactory.

3. This.messagingTemplate.send((MessageChannel) replyChannel, (Message<?>) output) will be called later; that is, the previous steps ①, ②, ③, and ④ will be repeated. Enter the AbstractReplyProducingMessageHandler.handleMessageInternal method.

	protected final void handleMessageInternal(Message<?> message) {
		Object result;
		if (this.advisedRequestHandler == null) {
			result = handleRequestMessage(message);
		}
		else {
			result = doInvokeAdvisedRequestHandler(message);
		}
		if (result != null) {
			sendOutputs(result, message);
		}
		else if (this.requiresReply && !isAsync()) {
			throw new ReplyRequiredException(message, "No reply produced by handler '" +
					getComponentName() + "', and its 'requiresReply' property is set to true.");
		}
		else if (!isAsync() && logger.isDebugEnabled()) {
			logger.debug("handler '" + this + "' produced no reply for request Message: " + message);
		}
	}

Copy code

But the message channel at this time is created by BindableProxyFactory, and the observer at this time is StreamListenerMessageHandler.

		protected Object handleRequestMessage(Message<?> requestMessage) {
			try {
				return invocableHandlerMethod.invoke(requestMessage);
			}
			catch (Exception e) {
				if (e instanceof MessagingException) {
					throw (MessagingException) e;
				}
				else {
					throw new MessagingException(requestMessage, "Exception thrown while invoking " + invocableHandlerMethod.getShortLogMessage(), e);
				}
			}
		}

Copy code

From the previous source code analysis, we can know that invocableHandlerMethod has encapsulated the corresponding Bean and Method. This completes the analysis of finding the corresponding method from RabbitMQ to Spring Stream.

Spring Stream Rabbit message finding corresponding method source code summary:

1. Spring Stream created SimpleMessageListenerContainer for listening to the RabbitMQ server.

2. Create an AmqpInboundChannelAdapter object, and the input parameter is SimpleMessageListenerContainer. The two are associated, so that after receiving the information, SimpleMessageListenerContainer calls the onMessage information created in the onInit method of AmqpInboundChannelAdapter.

3. Create a DirectChannel object named bridgeToModuleChannel and set the OutputChannel of the Adapter to DirectChannel.

4. Create an observer ReceivingHandler to observe bridgeToModuleChannel. And set the OutputChannel of ReceivingHandler to the message channel created by BindableProxyFactory.

5. After RabbitMQ sends information, it will go to the ReceivingHandler.handleRequestMessage method for the first time to deserialize the message, etc. sendOutputs will be executed later.

6. After sendingOutputs again, the observer StreamListenerMessageHandler of the message channel created by BindableProxyFactory will be called. The StreamListenerMessageHandler.handleRequestMessage method will encapsulate the corresponding Bean and Method through the invocableHandlerMethod call. To find the corresponding class method.

The above is a simple analysis of Spring Cloud Stream. The use of Spring Stream needs to be combined with Spring Integration.

 

Guess you like

Origin blog.csdn.net/lqzixi/article/details/130792603