From 46fdca479b8f9f9fd8c96b391b46bd70402e8434 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Fri, 10 Nov 2017 17:00:11 -0500 Subject: [PATCH] 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 --- ...ntextFunctionCatalogAutoConfiguration.java | 49 ++++++----------- ...FunctionCatalogAutoConfigurationTests.java | 52 ++++++++++++++++++- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java index 630f88e09..a4ffae496 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfiguration.java @@ -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 suppliers = new HashMap<>(); + private Map functions = new HashMap<>(); + private Map consumers = new HashMap<>(); @Autowired private ConfigurableListableBeanFactory registry; + private ConversionService conversionService; + private Map registrations = new HashMap<>(); + private Map>> types = new HashMap<>(); public Set 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() { diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java index 7256f3de4..067d82fe7 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/ContextFunctionCatalogAutoConfigurationTests.java @@ -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> 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> { + + @Override + public Class getObjectType() { + return Function.class; + } + + @Override + protected Function createInstance() throws Exception { + return s -> s.toUpperCase() + "-bar"; + } + + } + public static class Foo { private String value;