问题描述
在集成spring cloud stream框架时,会出现下面错误:
A default binder has been requested, but there is no binder available??
网上大多数都是让在application.yml 文件中引入一个默认的 binder就可以解决,但是我这个不是这个原因导致的,所以记录下
问题分析
我没有用这块的功能啊,为啥非要我指定一个binder?
我们知道spring cloud stream是用于构建高度可扩展的基于事件驱动的微服务,其目的是为了简化消息在 Spring Cloud 应用程序中的开发,就是说是给消息做发布订阅用的。
里面大量用到java的函数编程,简化代码逻辑,使得编码简单易上手,但是问题往往就出现在这不经意间。
跟踪报错位置,发现在绑定生产者时出现了异常
就是inMemorySwaggerResourcesProvider-out-0这个里面的配置文件中没有指定binder导致启动出现了异常,这个明显跟stream没有任何关系啊,看着让人费解
这个是项目中集成了swagger后,swagger自己会将这个实例注入到容器中,跟stream是没有关系的,我把swagger的依赖给去掉了,现在项目就可以正常启动了,那么就是swagger的问题,但是为啥swagger会影响stream的消息通道构建呢?
只能进行debugger分析:
通过链路分析,最终定位到org.springframework.cloud.function.context.catalog.BeanFactoryAwareFunctionRegistry,这个类大概就是函数类的注册工厂,他把所有注入到容器的Function.class、 Consumer.class、Supplier.class全部都拿来找binder,直接看代码:
String discoverDefaultDefinitionIfNecessary(String definition) {
if (StringUtils.isEmpty(definition) || definition.endsWith("|")) {
// the underscores are for Kotlin function registrations (see KotlinLambdaToFunctionAutoConfiguration)
String[] functionNames = Stream.of(this.applicationContext.getBeanNamesForType(Function.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
String[] consumerNames = Stream.of(this.applicationContext.getBeanNamesForType(Consumer.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
String[] supplierNames = Stream.of(this.applicationContext.getBeanNamesForType(Supplier.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
/*
* we may need to add BiFunction and BiConsumer at some point
*/
List<String> names = Stream
.concat(Stream.of(functionNames), Stream.concat(Stream.of(consumerNames), Stream.of(supplierNames)))
.collect(Collectors.toList());
if (definition.endsWith("|")) {
Set<String> fNames = this.getNames(null);
definition = this.determinImpliedDefinition(fNames, definition);
}
else if (!ObjectUtils.isEmpty(names)) {
if (names.size() > 1) {
logger.debug("Found more then one function beans in BeanFactory: " + names
+ ". If you did not intend to use functions, ignore this message. However, if you did "
+ "intend to use functions in the context of spring-cloud-function, consider "
+ "providing 'spring.cloud.function.definition' property pointing to a function bean(s) "
+ "you intend to use. For example, 'spring.cloud.function.definition=myFunction'");
return null;
}
definition = names.get(0);
}
else {
definition = this.discoverDefaultDefinitionFromRegistration();
}
if (StringUtils.hasText(definition) && this.applicationContext.containsBean(definition)) {
Type functionType = discoverFunctionType(this.applicationContext.getBean(definition), definition);
if (!FunctionTypeUtils.isSupplier(functionType) && !FunctionTypeUtils
.isFunction(functionType) && !FunctionTypeUtils.isConsumer(functionType)) {
logger.debug("Discovered functional instance of bean '" + definition + "' as a default function, however its "
+ "function argument types can not be determined. Discarding.");
definition = null;
}
}
}
if (!StringUtils.hasText(definition)) {
String[] functionRegistrationNames = Stream.of(applicationContext.getBeanNamesForType(FunctionRegistration.class))
.filter(n -> !n.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) && !n
.equals(RoutingFunction.FUNCTION_NAME)).toArray(String[]::new);
if (functionRegistrationNames != null) {
if (functionRegistrationNames.length == 1) {
definition = functionRegistrationNames[0];
}
else {
logger.debug("Found more then one function registration beans in BeanFactory: " + functionRegistrationNames
+ ". If you did not intend to use functions, ignore this message. However, if you did "
+ "intend to use functions in the context of spring-cloud-function, consider "
+ "providing 'spring.cloud.function.definition' property pointing to a function bean(s) "
+ "you intend to use. For example, 'spring.cloud.function.definition=myFunction'");
}
}
}
return definition;
}
没错!InMemorySwaggerResourcesProvider就是Supplier的一个实现,正好被扫描到了,并且该项目中又只有一个,便取了第一个去注册生产者去了,问题是这个又不是干这个活的,必然就报错了
spring-cloud-function-context 里面也太霸道了,竟然直接使用了java的顶层接口做设计,这样会影响其他框架的集成啊
结论
SCS需要配置spring.cloud.function.definition=myFunction,作为默认的函数,不然就得引入其他Supplier的类到容器中,保证names.size() > 1,不过这种代码我觉得不应该出现在spring中啊,太难排查了