在将Spring Cloud Stream升级到3.1.3之后。某些项目启动会报***A default binder has been requested, but there is more than one binder available for ‘org.springframework.cloud.stream.messaging.DirectWithAttributesChannel’ : rocketmq,mqtt,kafka,rabbit,
amqpjms, and no default binder has been set.***错误
从报错信息可知,项目启动时创建了一个通道,但并没有指定绑定器。因此会去使用默认配置的绑定起器,但默认绑定器没有配置,并且发现了rocketmq,mqtt,kafka,rabbit,amqpjms这几种类型的绑定器。当然这个通道可能并不是你项目里面显示创建的,也许是你依赖的某个组件其内部自动创建的
对于自己创建的通道,如果报上面的错误,或类似的没有默认绑定器的错误,请设置默认绑定器或者指定自己创建通道的绑定器
这个情况比较复杂,根据自己需求确定需要采取何种处理方式
对于这个通道如果无需使用,也不想设置默认绑定器,则需要根据依赖组件的具体使用方式,想办法不让其创建通道,下面会有具体案例。例如通道是通过实现Supplier、Function、Consumer,并通过函数式通道自动发现的,可以通过spring.cloud.stream.function.autodetect=false,关闭自动发现
如果需要这个通道,则可以设置默认绑定器
根据报错日志可定位到报错代码位置为DefaultBinderFactory类的doGetBinder方法,该方法获取绑定器。查看报错位置代码可知,candidatesForBindableType候选绑定器不为1,则会报出此错误,而candidatesForBindableType则是通过binderConfigurations解析出来的,binderConfigurations是Spring Cloud Stream启动时扫描绑定器的配置文件来的,每种类型的绑定器都会有
private Binder doGetBinder(String name, Class extends T> bindingTargetType) {
String configurationName;
Binder binderInstance;
if (!MessageChannel.class.isAssignableFrom(bindingTargetType) && !PollableMessageSource.class.isAssignableFrom(bindingTargetType)) {
configurationName = StringUtils.hasText(name) ? name : bindingTargetType.getSimpleName().toLowerCase();
binderInstance = this.getBinderInstance(configurationName);
return binderInstance;
} else {
if (StringUtils.isEmpty(name)) {
Assert.notEmpty(this.binderConfigurations, "A default binder has been requested, but there is no binder available");
if (!StringUtils.hasText(this.defaultBinder)) {
Set defaultCandidateConfigurations = new HashSet();
Iterator var5 = this.binderConfigurations.entrySet().iterator();
while(var5.hasNext()) {
Entry binderConfigurationEntry = (Entry)var5.next();
if (((BinderConfiguration)binderConfigurationEntry.getValue()).isDefaultCandidate()) {
defaultCandidateConfigurations.add(binderConfigurationEntry.getKey());
}
}
if (defaultCandidateConfigurations.size() == 1) {
configurationName = (String)defaultCandidateConfigurations.iterator().next();
this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
} else {
List candidatesForBindableType = new ArrayList();
Iterator var12 = defaultCandidateConfigurations.iterator();
while(var12.hasNext()) {
String defaultCandidateConfiguration = (String)var12.next();
Binder
通过方法调用栈可知,doGetBinder方法在DefaultBinderFactory类的getBinder方法中被调用,在getBinder方法中,会首先处理绑定器名称,如果绑定器名称为空,则使用默认绑定器名称,但当前我们运行项目并没有设置绑定器名称,也没有设置默认绑定器名称。因此走到了doGetBinder方法
public synchronized Binder getBinder(String name, Class extends T> bindingTargetType) {
String binderName = StringUtils.hasText(name) ? name : this.defaultBinder;
Map binders = this.context == null ? Collections.emptyMap() : this.context.getBeansOfType(Binder.class);
Binder binder;
if (StringUtils.hasText(binderName) && binders.containsKey(binderName)) {
binder = (Binder)this.context.getBean(binderName);
} else if (binders.size() == 1) {
binder = (Binder)binders.values().iterator().next();
} else {
if (binders.size() > 1) {
throw new IllegalStateException("Multiple binders are available, however neither default nor per-destination binder name is provided. Available binders are " + binders.keySet());
}
binder = this.doGetBinder(binderName, bindingTargetType);
}
if (this.binderCustomizer != null) {
this.binderCustomizer.customize(binder, binderName);
}
return binder;
}
在大概清楚了为什么报错之后,我们需要寻找到是那里进行的通道创建,及创建的逻辑。通过分析调用栈我们可以找到AbstractBindableProxyFactory类的createAndBindOutputs方法,在此方法中,我们发现outputHolders属性,此属性中存放了解析生成的错误通道,此方法就是在将绑定器绑定到输出通道。我们需要找到outputHolders属性是在哪里进行的设置
public Collection> createAndBindOutputs(
BindingService bindingService) {
List> bindings = new ArrayList<>();
if (log.isDebugEnabled()) {
log.debug(String.format("Binding outputs for %s:%s", this.namespace,
this.type));
}
for (Map.Entry 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));
}
}
return bindings;
}
通过分析代码可知BindableFunctionProxyFactory类的createOutput方法进行了设置
private void createOutput(String name) {
if (this.functionProperties.getBindings().containsKey(name)) {
name = this.functionProperties.getBindings().get(name);
}
this.outputHolders.put(name,
new BoundTargetHolder(getBindingTargetFactory(MessageChannel.class)
.createOutput(name), true));
}
createOutput方法的调用是在BindableFunctionProxyFactory类的afterPropertiesSet方法中调用的,这个方法是bean初始化后调用,我们观察到在此类中functionDefinition属性值为***inMemorySwaggerResourcesProvider***,此属性是在构造函数中设置
public void afterPropertiesSet() {
populateBindingTargetFactories(context.getBeanFactory());
Assert.notEmpty(BindableFunctionProxyFactory.this.bindingTargetFactories,
"'bindingTargetFactories' cannot be empty");
if (this.inputCount > 0) {
for (int i = 0; i < inputCount; i++) {
this.createInput(this.buildInputNameForIndex(i));
}
}
if (this.outputCount > 0) {
for (int i = 0; i < outputCount; i++) {
this.createOutput(this.buildOutputNameForIndex(i));
}
}
}
BindableFunctionProxyFactory(String functionDefinition, int inputCount, int outputCount, StreamFunctionProperties functionProperties,
boolean pollable) {
super(null);
this.inputCount = inputCount;
this.outputCount = outputCount;
this.functionDefinition = functionDefinition;
this.functionProperties = functionProperties;
this.pollable = pollable;
}
下载依赖组件源码,全局搜索functionDefinition属性值,发现InMemorySwaggerResourcesProvider类,此类为依赖Swagger组件中的类,观察此类,发现此类实现了Supplier接口。在最新的3.1.3版本的Spring Cloud Stream支持函数编程,实现消息的发送与消费,详情查看Spring Cloud Stream,由此可知产生的输出通道其实就是InMemorySwaggerResourcesProvider类产生的
@Component
public class InMemorySwaggerResourcesProvider implements SwaggerResourcesProvider, ApplicationContextAware
public interface SwaggerResourcesProvider extends Supplier>
我们目前知道了产生问题的地方,如果对Spring Cloud Stream很属性也应该知道解决方法是什么。我们这边再继续跟踪源码,进行更深入的分析。我们需要知道哪里调用了BindableFunctionProxyFactory的构造方法设置functionDefinition属性。通过代码分析我们可以发现FunctionBindingRegistrar完成bean初始化后在afterPropertiesSet方法中实现了对BindableFunctionProxyFactory构造函数的调用,调用方式是通过获取其RootBeanDefinition,动态调用的构造方法并注册functionDefinition + "_binding"命名的bean。我们发现通过判断streamFunctionProperties的definition属性是否有值决定是否进入之后的处理方法
public void afterPropertiesSet() throws Exception {
if (ObjectUtils.isEmpty(applicationContext.getBeanNamesForAnnotation(EnableBinding.class))) {
this.determineFunctionName(functionCatalog, environment);
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
if (StringUtils.hasText(streamFunctionProperties.getDefinition())) {
String[] functionDefinitions = this.filterEligibleFunctionDefinitions();
for (String functionDefinition : functionDefinitions) {
RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class);
FunctionInvocationWrapper function = functionCatalog.lookup(functionDefinition);
if (function != null) {
Type functionType = function.getFunctionType();
if (function.isSupplier()) {
this.inputCount = 0;
this.outputCount = this.getOutputCount(functionType, true);
}
else if (function.isConsumer() || functionDefinition.equals(RoutingFunction.FUNCTION_NAME)) {
this.inputCount = FunctionTypeUtils.getInputCount(functionType);
this.outputCount = 0;
}
else {
this.inputCount = FunctionTypeUtils.getInputCount(functionType);
this.outputCount = this.getOutputCount(functionType, false);
}
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(functionDefinition);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.inputCount);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.outputCount);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties);
registry.registerBeanDefinition(functionDefinition + "_binding", functionBindableProxyDefinition);
}
else {
logger.warn("The function definition '" + streamFunctionProperties.getDefinition() +
"' is not valid. The referenced function bean or one of its components does not exist");
}
}
}
if (StringUtils.hasText(this.environment.getProperty(SOURCE_PROPERY))) {
String[] sourceNames = this.environment.getProperty(SOURCE_PROPERY).split(";");
for (String sourceName : sourceNames) {
if (functionCatalog.lookup(sourceName) == null) {
RootBeanDefinition functionBindableProxyDefinition = new RootBeanDefinition(BindableFunctionProxyFactory.class);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(sourceName);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(0);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(1);
functionBindableProxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(this.streamFunctionProperties);
registry.registerBeanDefinition(sourceName + "_binding", functionBindableProxyDefinition);
}
}
}
}
else {
logger.info("Functional binding is disabled due to the presense of @EnableBinding annotation in your configuration");
}
}
通过代码分析发现determineFunctionName方法实现了对streamFunctionProperties属性的设置。在此方法中首先获取环境变量中设置的spring.cloud.stream.function.autodetect,如果未设置则默认为true。从streamFunctionProperties获取definition值,如果没有则从环境变量中获取spring.cloud.function.definition值作为definition,并回写到streamFunctionProperties,如果没有则继续查找,最后如果前面都没找到并且autodetect为true,则会通过自动扫描函数接口获取。如果将spring.cloud.stream.function.autodetect设置为false则不会自动扫描
private boolean determineFunctionName(FunctionCatalog catalog, Environment environment) {
boolean autodetect = environment.getProperty("spring.cloud.stream.function.autodetect", boolean.class, true);
String definition = streamFunctionProperties.getDefinition();
if (!StringUtils.hasText(definition)) {
definition = environment.getProperty("spring.cloud.function.definition");
}
if (StringUtils.hasText(definition)) {
streamFunctionProperties.setDefinition(definition);
}
else if (Boolean.parseBoolean(environment.getProperty("spring.cloud.stream.function.routing.enabled", "false"))
|| environment.containsProperty("spring.cloud.function.routing-expression")) {
streamFunctionProperties.setDefinition(RoutingFunction.FUNCTION_NAME);
}
else if (autodetect) {
streamFunctionProperties.setDefinition(((FunctionInspector) functionCatalog).getName(functionCatalog.lookup("")));
}
return StringUtils.hasText(streamFunctionProperties.getDefinition());
}
继续跟踪FunctionCatalog的lookup方法,我们会发现所有实现了Function、Supplier、Consumer的类都会被扫出来
public Set getNames(Class> type) {
Set registeredNames = super.getNames(type);
if (type == null) {
registeredNames
.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(Function.class)));
registeredNames
.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(Supplier.class)));
registeredNames
.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(Consumer.class)));
}
else {
registeredNames.addAll(Arrays.asList(this.applicationContext.getBeanNamesForType(type)));
}
return registeredNames;
}
通过上面源码分析,对于报错的原因有了深入的了解。我们可以根据自己的需求进行设置。比如将spring.cloud.stream.function.autodetect设置为false禁用自动扫描,去除不需要的函数类生成消息通道