Properly resolve FactoryBean for function
Fixes: gh-118 When the `BeanDefinition` for `Function` is a `FactoryBean` (e.g. `GatewayProxyFactoryBean` in Spring Integration) and that `BeanDefinition` isn't registered as `@Bean` method (e.g. Spring Integration Java DSL parser), the `ContextFunctionCatalogAutoConfiguration` doesn't resolve the target `Function` type properly * Get the target `Function` bean type via `BeanFactory.getType(String)` * Make fallback to the `Object.class` instead of bean type since we are expecting here a generic type anyway
This commit is contained in:
@@ -34,7 +34,6 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -73,6 +72,7 @@ import reactor.core.publisher.Flux;
|
||||
* @author Dave Syer
|
||||
* @author Mark Fisher
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*/
|
||||
@FunctionScan
|
||||
@Configuration
|
||||
@@ -197,13 +197,18 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
protected static class ContextFunctionRegistry {
|
||||
|
||||
private Map<String, Object> suppliers = new HashMap<>();
|
||||
|
||||
private Map<String, Object> functions = new HashMap<>();
|
||||
|
||||
private Map<String, Object> consumers = new HashMap<>();
|
||||
|
||||
@Autowired
|
||||
private ConfigurableListableBeanFactory registry;
|
||||
|
||||
private ConversionService conversionService;
|
||||
|
||||
private Map<Object, String> registrations = new HashMap<>();
|
||||
|
||||
private Map<String, Map<ParamType, Class<?>>> types = new HashMap<>();
|
||||
|
||||
public Set<String> getSuppliers() {
|
||||
@@ -507,11 +512,10 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
return FunctionInspector
|
||||
.isWrapper(findType(function, ParamType.INPUT_WRAPPER))
|
||||
|| FunctionInspector
|
||||
.isWrapper(findType(function, ParamType.OUTPUT_WRAPPER));
|
||||
.isWrapper(findType(function, ParamType.OUTPUT_WRAPPER));
|
||||
}
|
||||
|
||||
private Class<?> findType(String name, AbstractBeanDefinition definition,
|
||||
ParamType paramType) {
|
||||
private Class<?> findType(String name, AbstractBeanDefinition definition, ParamType paramType) {
|
||||
Object source = definition.getSource();
|
||||
Type param = null;
|
||||
// Start by assuming output -> Function
|
||||
@@ -535,17 +539,10 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
param = extractType(type, paramType, index);
|
||||
}
|
||||
else if (source instanceof Resource) {
|
||||
try {
|
||||
Class<?> beanType = resolveBeanClass(definition);
|
||||
param = findTypeFromBeanClass(beanType, paramType);
|
||||
if (param == null) {
|
||||
// Last chance
|
||||
param = beanType;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot instrospect bean: " + definition, e);
|
||||
Class<?> beanType = this.registry.getType(name);
|
||||
param = findTypeFromBeanClass(beanType, paramType);
|
||||
if (param == null) {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -554,8 +551,8 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
if (resolvable != null) {
|
||||
param = resolvable.getGeneric(index).getGeneric(0).getType();
|
||||
}
|
||||
else if (registry instanceof BeanFactory) {
|
||||
Object bean = ((BeanFactory) registry).getBean(name);
|
||||
else {
|
||||
Object bean = this.registry.getBean(name);
|
||||
if (bean instanceof FunctionFactoryMetadata) {
|
||||
FunctionFactoryMetadata<?> factory = (FunctionFactoryMetadata<?>) bean;
|
||||
Type type = factory.getFactoryMethod().getGenericReturnType();
|
||||
@@ -563,8 +560,7 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
}
|
||||
}
|
||||
}
|
||||
Class<?> result = extractClass(name, param, paramType);
|
||||
return result;
|
||||
return extractClass(name, param, paramType);
|
||||
}
|
||||
|
||||
private Class<?> extractClass(String name, Type param, ParamType paramType) {
|
||||
@@ -600,17 +596,6 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Class<?> resolveBeanClass(AbstractBeanDefinition definition)
|
||||
throws ClassNotFoundException, LinkageError {
|
||||
try {
|
||||
return ClassUtils.forName(definition.getBeanClassName(), null);
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
return ClassUtils.forName(definition.getBeanClassName(),
|
||||
getClass().getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
private Type findBeanType(AbstractBeanDefinition definition,
|
||||
MethodMetadataReadingVisitor visitor) {
|
||||
Class<?> factory = ClassUtils
|
||||
@@ -681,7 +666,7 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
return Message.class
|
||||
.isAssignableFrom(findType(function, ParamType.INPUT_INNER_WRAPPER))
|
||||
|| Message.class.isAssignableFrom(
|
||||
findType(function, ParamType.OUTPUT_INNER_WRAPPER));
|
||||
findType(function, ParamType.OUTPUT_INNER_WRAPPER));
|
||||
}
|
||||
|
||||
private Class<?> findType(Object function, ParamType type) {
|
||||
@@ -710,7 +695,7 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
static enum ParamType {
|
||||
enum ParamType {
|
||||
INPUT, OUTPUT, INPUT_WRAPPER, OUTPUT_WRAPPER, INPUT_INNER_WRAPPER, OUTPUT_INNER_WRAPPER;
|
||||
|
||||
public boolean isOutput() {
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.function.context;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
@@ -31,8 +33,12 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
|
||||
@@ -46,6 +52,7 @@ import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DescriptiveResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
@@ -53,12 +60,11 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Artem Bilan
|
||||
*
|
||||
*/
|
||||
public class ContextFunctionCatalogAutoConfigurationTests {
|
||||
@@ -375,6 +381,15 @@ public class ContextFunctionCatalogAutoConfigurationTests {
|
||||
assertThat(ContextFunctionCatalogAutoConfigurationTests.value).isEqualTo("hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void factoryBeanFunction() {
|
||||
create(FactoryBeanConfiguration.class);
|
||||
assertThat(this.context.getBean("function")).isInstanceOf(Function.class);
|
||||
assertThat(this.catalog.lookupFunction("function")).isInstanceOf(Function.class);
|
||||
Function<Flux<String>, Flux<String>> f = this.catalog.lookupFunction("function");
|
||||
assertThat(f.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO-bar");
|
||||
}
|
||||
|
||||
private void create(Class<?> type, String... props) {
|
||||
create(new Class<?>[] { type }, props);
|
||||
}
|
||||
@@ -597,6 +612,39 @@ public class ContextFunctionCatalogAutoConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration
|
||||
protected static class FactoryBeanConfiguration implements BeanDefinitionRegistryPostProcessor {
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(FunctionFactoryBean.class);
|
||||
beanDefinition.setSource(new DescriptiveResource("Function"));
|
||||
registry.registerBeanDefinition("function", beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class FunctionFactoryBean extends AbstractFactoryBean<Function<String, String>> {
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Function.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<String, String> createInstance() throws Exception {
|
||||
return s -> s.toUpperCase() + "-bar";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Foo {
|
||||
private String value;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user