spring-cloud-stream学习笔记

spring-cloud-stream学习笔记

本文研究SpringCloudStream配置流程及消息中间件的加入,以@OutPut为例

Stream配置

org.springframework.cloud.stream.config.BindingServiceProperties

stream的管道配置一般写为spring.cloud.stream.bindings.<channelName>.*,这里接受的主要参数为:

	private String destination;//消息topic

	private String group;//组,输出管道时可以不填,这时组名会与topic相同

	private String contentType = DEFAULT_CONTENT_TYPE.toString();//消息类型,默认json

	private String binder;//存在多个消息中间件时需要指定中间件名称,该名称在对应的spring-cloud-stream-binder-*的META-INF文件夹中,spring.binders

	private ConsumerProperties consumer;

	private ProducerProperties producer;

这里给出一个配置例子:

spring:
	cloud:
		stream:
			bindings:
				test-input:
					destination: binderTest
					group: test-consumer
					binder: rocketmq
				test-out-put:
					destination: binderTest
					group: test-producer
					binder: rocketmq

管道绑定

在系统启动时,spring会通过org.springframework.cloud.stream.config.BindingServiceConfiguration加载org.springframework.cloud.stream.binding.OutputBindingLifecycle,根据@EnableBinding配置的类循环调用doStartWithBindable(Bindable bindable)后执行管道配置。

abstract class AbstractBindingLifecycle implements SmartLifecycle {
	.......
	@Override
	public void start() {
		if (!this.running) {
			if (this.context != null) {
				this.bindables.putAll(context.getBeansOfType(Bindable.class));
			}

			this.bindables.values().forEach(this::doStartWithBindable);//这里的bandables即为@EnableBinding配置的信息map
			this.running = true;
		}
	.......
}
管道实现类配置

调用output的绑定时,通过Bindable携带管道类信息,比如:

public interface RocketMQMessageChannel {
    @Input("test-input")
    SubscribableChannel orderInput();

    @Output("test-output")
    MessageChannel orderOutput();
}
//那这里组成的bandable实现为org.springframework.cloud.stream.binding.BindableProxyFactory
//type=com.springcloudstudy.RocketMQstream.config.RocketMQMessageChannel

通过bindable.createAndBindOutputs方法组装管道名与对应messageHandle的对应关系,以Binding接口形式返回,这里会返回所有的Output管道对应的messageHandle。这里的bindable对象包含了所有的input和output节点,分别保存在outputHoldersinputHolders两个map里面。

public class OutputBindingLifecycle extends AbstractBindingLifecycle {
	......
	@Override
	void doStartWithBindable(Bindable bindable) {
		Collection<Binding<Object>> bindableBindings = bindable
				.createAndBindOutputs(this.bindingService);
		if (!CollectionUtils.isEmpty(bindableBindings)) {
			this.outputBindings.addAll(bindableBindings);
		}
	}
	......
}

进入bindable.createAndBindOutputs,调用bindingService.bindProducer解析每个holder,outputTargetName即为当前管道名称

public Collection<Binding<Object>> createAndBindOutputs(
		BindingService bindingService) {
		......
		for (Map.Entry<String, BoundTargetHolder> boundTargetHolderEntry : this.outputHolders
			.entrySet()) {
			BoundTargetHolder boundTargetHolder = boundTargetHolderEntry.getValue();
			String outputTargetName = boundTargetHolderEntry.getKey();
			if (boundTargetHolderEntry.getValue().isBindable()) {
				if (log.isDebugEnabled()) {
					log.debug(String.format("Binding %s:%s:%s", this.namespace, this.type,
						outputTargetName));
				}
				bindings.add(bindingService.bindProducer(
					boundTargetHolder.getBoundTarget(), outputTargetName));//BindingService解析
			}
		}
		return bindings;
}
org.springframework.cloud.stream.binding.BindingService

BindingService是将MQBinder和参数属性绑定的服务类,这里会根据管道名找出对应配置的binder,如果未指定且存在多个消息中间件binder时会抛出异常。

public <T> Binding<T> bindProducer(T output, String outputName) {
		String bindingTarget = this.bindingServiceProperties
				.getBindingDestination(outputName);//topic
    	//获取binder对应的MessageChannelBinder
		Binder<T, ?, ProducerProperties> binder = (Binder<T, ?, ProducerProperties>) getBinder(
				outputName, output.getClass());
    	//这里是spring.cloud.stream.bindings.<channelName>.producer.*配置内容,outputName = channelName
		ProducerProperties producerProperties = this.bindingServiceProperties
				.getProducerProperties(outputName);
		if (binder instanceof ExtendedPropertiesBinder) {
			//这里取的是spring.cloud.stream.<MQName>.bindings.<channelname>.producer.*的内容,即为mq的特有配置
			Object extension = ((ExtendedPropertiesBinder) binder)
					.getExtendedProducerProperties(outputName);
			ExtendedProducerProperties extendedProducerProperties = new ExtendedProducerProperties<>(
					extension);
			BeanUtils.copyProperties(producerProperties, extendedProducerProperties);

			producerProperties = extendedProducerProperties;
		}
		validate(producerProperties);
		Binding<T> binding = doBindProducer(output, bindingTarget, binder,
				producerProperties);
		this.producerBindings.put(outputName, binding);
		return binding;
	}
binder获取

调用getBander方法时会根据outputName获取配置中的bander,没配置的情况接下来展示。之后会调用org.springframework.cloud.stream.binde.DefaultBinderFactory.getBinder(String name, Class<? extends T> bindingTargetType)方法,根据SpringCloudStream实现的要求,需要在META-INF目录下新增spring.binders存放中间件名称与自动配置类对应关系,保存在DefaultBinderFactorybinderTypeRegistry属性中。

public synchronized <T> Binder<T, ?, ?> getBinder(String name,
			Class<? extends T> bindingTargetType) {
		......
		Map<String, Binder> binders = this.context == null ? Collections.emptyMap()
				: this.context.getBeansOfType(Binder.class);
		Binder<T, ConsumerProperties, ProducerProperties> binder;
		......
			binder = this.doGetBinder(binderName, bindingTargetType);
    	......
}

注意,在执行这个流程时,可能因为优先级等原因对应的MQAutoConfiguration还没有进行自动配置,这时会调用doGetBinder(String name,Class<? extends T> bindingTargetType)方法临时加塞,触发对应MQAutoConfiguration自动配置。

private <T> Binder<T, ConsumerProperties, ProducerProperties> doGetBinder(String name,
			Class<? extends T> bindingTargetType) {
		String configurationName;//mq名称
		if (StringUtils.isEmpty(name)) {
			......
                //查找所有符合条件的mq,当未指定binderName且存在多个binder时,会抛出错误: no default binder has been set
				for (Map.Entry<String, BinderConfiguration> binderConfigurationEntry : this.binderConfigurations
						.entrySet()) {
					if (binderConfigurationEntry.getValue().isDefaultCandidate()) {
						defaultCandidateConfigurations
								.add(binderConfigurationEntry.getKey());
					}
				}
            ......
		}
		else {
			configurationName = name;
		}
		Binder<T, ConsumerProperties, ProducerProperties> binderInstance = getBinderInstance(
				configurationName);
    	........
	}

调用getBinderInstance(String configurationName)方法时,需要拿到binder实现类,所以需要SpringApplicationBuilder来触发对应的MQAutoConfigration,这里的SpringApplicationBuilder会绑定原有的springContext使用,所以是相当于把需要在之后执行的MQAutoConfigration放到这里执行,不会影响原来的启动流程。至此,主要的binder实现类拿到了。

private <T> Binder<T, ConsumerProperties, ProducerProperties> getBinderInstance(
			String configurationName) {
		if (!this.binderInstanceCache.containsKey(configurationName)) {
            
			BinderConfiguration binderConfiguration = this.binderConfigurations
					.get(configurationName);
			//binderTypeRegistry保存的是mq名称与自动配置类关系
			BinderType binderType = this.binderTypeRegistry
					.get(binderConfiguration.getBinderType());
			......
			ArrayList<String> args = new ArrayList<>();
			......
			SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(
					binderType.getConfigurationClasses())
							.bannerMode(Mode.OFF).logStartupInfo(false)
							.web(WebApplicationType.NONE);
            
			........
                springApplicationBuilder.parent(this.context);
            ........
			ConfigurableApplicationContext binderProducingContext = springApplicationBuilder
					.run(args.toArray(new String[0]));

			Binder<T, ?, ?> binder = binderProducingContext.getBean(Binder.class);
			
			......
            //缓存已生成的mqBinder
			this.binderInstanceCache.put(configurationName,
					new SimpleImmutableEntry<>(binder, binderProducingContext));
		}
		return (Binder<T, ConsumerProperties, ProducerProperties>) this.binderInstanceCache
				.get(configurationName).getKey();
	}

@OutPut@Input同名情况

当我们把@OutPut@Input填入相同的值之后,有一定几率会报MessageDeliveryException: Dispatcher has no subscribers for channel 'test-output',为什么是有几率呢,是因为@OutPut@Input会以value为名称生成MessageChannel的bean到beanFactory中,绑定到inputHoldersoutputHolders里面,但是在设置该MessageChannelhandle属性时又是以名称去spring上下文中找bean,也就是beanFactory中的值,这时候就看运气了,如果inputHolders中的bean在outputHolders之后生成,这时spring上下文中保存的bean就是对的,反之则会报错。

跟踪源码获知,inputHoldersoutputHolders赋值是在org.springframework.cloud.stream.binding.BindableProxyFactory#afterPropertiesSet方法中执行的:

public void afterPropertiesSet() {
    	/**
    	* 根据标记@Input和@Output注解的方法执行
    	* type @EnableBinding的值
    	* method 标记过的方法
    	*/
		ReflectionUtils.doWithMethods(this.type, method -> {
			Input input = AnnotationUtils.findAnnotation(method, Input.class);
			if (input != null) {
				String name = BindingBeanDefinitionRegistryUtils
						.getBindingTargetName(input, method);
				Class<?> returnType = method.getReturnType();

				BindableProxyFactory.this.inputHolders.put(name,
						new BoundTargetHolder(getBindingTargetFactory(returnType)
								.createInput(name), true));
			}
		});
		ReflectionUtils.doWithMethods(this.type, method -> {
			Output output = AnnotationUtils.findAnnotation(method, Output.class);
			if (output != null) {
				String name = BindingBeanDefinitionRegistryUtils
						.getBindingTargetName(output, method);
				Class<?> returnType = method.getReturnType();

				BindableProxyFactory.this.outputHolders.put(name,
						new BoundTargetHolder(getBindingTargetFactory(returnType)
								.createOutput(name), true));
			}
		});
	}

这里最后生成的键值对类型为map<Stirng,DirectWithAttributesChannel>

在执行生成DirectWithAttributesChannel实例的BindingTargetFactory(returnType).createInput(name)方法中是这样注册bean的:

public SubscribableChannel createInput(String name) {
		DirectWithAttributesChannel subscribableChannel = new DirectWithAttributesChannel();
		subscribableChannel.setComponentName(name);
		subscribableChannel.setAttribute("type", Sink.INPUT);
		this.messageChannelConfigurer.configureInputChannel(subscribableChannel, name);
		if (context != null && !context.containsBean(name)) {
			context.registerBean(name, DirectWithAttributesChannel.class, () -> subscribableChannel);
		}
		return subscribableChannel;
	}

这里的context.registerBean会以名称为key注册实例,这里问题已经出现了,下面解释问题出现点:

org.springframework.cloud.stream.binding.StreamListenerAnnotationBeanPostProcessor#afterSingletonsInstantiated方法执行,这里会设置消息消费者的回调方法到管道绑定的DirectWithAttributesChannel对象中:

public final void afterSingletonsInstantiated() {
		......
		for (Map.Entry<String, List<StreamListenerHandlerMethodMapping>> mappedBindingEntry : this.mappedListenerMethods
				.entrySet()) {//mappedListenerMethods记录了@StreamListener绑定的名称与方法
			......
			this.applicationContext.getBeanFactory().registerSingleton(
					handler.getClass().getSimpleName() + handler.hashCode(), handler);//注册当前消息回调方法到spring上下文中
			this.applicationContext
					.getBean(mappedBindingEntry.getKey(), SubscribableChannel.class)//根据当前@StreamListener的value获取channel
					.subscribe(handler);//绑定回调方法
		}
		this.mappedListenerMethods.clear();
	}

这里获取DirectWithAttributesChannel时是通过@StreamListener绑定的管道名获取的,结合之前的代码,当@Input@Output值相同时,如果同名的@Input绑定的DirectWithAttributesChannel实例在@Output之后设置到spring上下文时,不会报错,这里需要注意一下,否则问题不会每次都出现,比较闹心。

总结

SpringCloudStream相当于MQ与系统的接口,MQ的发送接收消息逻辑在MessageHandle里面,跟管道绑定,而stream只需要关注消息应该发送至哪个管道即可。

SpringCloudStream的实现规则
  • 实现Binder接口
  • 新增spring.binders文件,并写入名称与配置类对应关系::
binder加载
  • 扫描@EnableBinding的类中的方法,获取注解信息
  • 将标记了@Input@Output的方法存入inputHoldersoutputHolders
  • 获取标记了@StreamListener的方法,并关联到inputChannel中
  • 根据channelName的配置的binder实现各自消息发送方法

未完待续。。。。。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值