A default binder has been requested

A default binder has been requested

在将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 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 binderInstance = this.getBinderInstance(defaultCandidateConfiguration);
                        Class binderType = GenericsUtils.getParameterType(binderInstance.getClass(), Binder.class, 0);
                        if (binderType.isAssignableFrom(bindingTargetType)) {
                            candidatesForBindableType.add(defaultCandidateConfiguration);
                        }
                    }

                    if (candidatesForBindableType.size() != 1) {
                        // 报错位置
                        String countMsg = candidatesForBindableType.size() == 0 ? "are no binders" : "is more than one binder";
                        throw new IllegalStateException("A default binder has been requested, but there " + countMsg + " available for '" + bindingTargetType.getName() + "' : " + StringUtils.collectionToCommaDelimitedString(candidatesForBindableType) + ", and no default binder has been set.");
                    }

                    configurationName = (String)candidatesForBindableType.iterator().next();
                    this.defaultBinderForBindingTargetType.put(bindingTargetType.getName(), configurationName);
                }
            } else {
                configurationName = this.defaultBinder;
            }
        } else {
            configurationName = name;
        }

        binderInstance = this.getBinderInstance(configurationName);
        Assert.state(this.verifyBinderTypeMatchesTarget(binderInstance, bindingTargetType), "The binder '" + configurationName + "' cannot bind a " + bindingTargetType.getName());
        return binderInstance;
    }
}

通过方法调用栈可知,doGetBinder方法在DefaultBinderFactory类的getBinder方法中被调用,在getBinder方法中,会首先处理绑定器名称,如果绑定器名称为空,则使用默认绑定器名称,但当前我们运行项目并没有设置绑定器名称,也没有设置默认绑定器名称。因此走到了doGetBinder方法

public synchronized  Binder getBinder(String name, Class 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禁用自动扫描,去除不需要的函数类生成消息通道

你可能感兴趣的:(Spring,Cloud,Stream,spring,cloud,Cloud,Stream)