diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java index da7c01f57..ad555fd57 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java @@ -47,6 +47,7 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.cloud.function.context.FunctionCatalog; @@ -188,6 +189,10 @@ public class BeanFactoryAwareFunctionRegistry boolean beanDefinitionExists = false; for (int i = 0; i < names.length && !beanDefinitionExists; i++) { beanDefinitionExists = this.applicationContext.getBeanFactory().containsBeanDefinition(names[i]); + if (this.applicationContext.containsBean("&" + names[i])) { + Class objectType = this.applicationContext.getBean("&" + names[i], FactoryBean.class).getObjectType(); + return FunctionTypeUtils.discoverFunctionTypeFromClass(objectType); + } } if (!beanDefinitionExists) { logger.info("BeanDefinition for function name(s) '" + Arrays.asList(names) + @@ -430,7 +435,9 @@ public class BeanFactoryAwareFunctionRegistry FunctionInvocationWrapper(Object target, Type functionType, String functionDefinition, String... acceptedOutputMimeTypes) { this.target = target; - this.composed = !target.getClass().getName().contains("EnhancerBySpringCGLIB") && target.getClass().getDeclaredFields().length > 1; + this.composed = !target.getClass().getName().contains("$$EnhancerBySpringCGLIB") + && !AopUtils.isAopProxy(target) && !AopUtils.isJdkDynamicProxy(target) + && target.getClass().getDeclaredFields().length > 1; this.functionType = functionType; this.acceptedOutputMimeTypes = acceptedOutputMimeTypes; this.functionDefinition = functionDefinition; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index f7a942019..b8ebe8586 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java @@ -105,7 +105,34 @@ public final class FunctionTypeUtils { return methods.get(0); } - public static Type getFunctionTypeFromFunctionMethod(Method functionMethod) { + public static Type discoverFunctionTypeFromClass(Class functionalClass) { + Assert.isTrue(isFunctional(functionalClass), "Type must be one of Supplier, Function or Consumer"); + Type[] generics = functionalClass.getGenericInterfaces(); + if (ObjectUtils.isEmpty(generics)) { + return functionalClass; + } + else { + for (Type generic : generics) { + if (generic instanceof ParameterizedType) { + Class rawClsss = (Class) ((ParameterizedType) generic).getRawType(); + if (rawClsss.isAssignableFrom(Function.class) + || rawClsss.isAssignableFrom(Consumer.class) + || rawClsss.isAssignableFrom(Supplier.class)) { + return generic; + } + else { + return discoverFunctionTypeFromClass(rawClsss); + } + } + else { + return discoverFunctionTypeFromClass((Class) generic); + } + } + } + return null; + } + + public static Type discoverFunctionTypeFromFunctionMethod(Method functionMethod) { Assert.isTrue( functionMethod.getName().equals("apply") || functionMethod.getName().equals("accept") || diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java index fea398297..40460b2ea 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtilsTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.function.context.catalog; import java.lang.reflect.Type; +import java.sql.Date; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -30,6 +31,9 @@ import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuple3; +import org.springframework.cloud.function.context.FunctionType; +import org.springframework.messaging.Message; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -90,6 +94,26 @@ public class FunctionTypeUtilsTests { assertThat(outputCount).isEqualTo(2); } + @Test + public void testFunctionTypeByClassDiscovery() { + FunctionType type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(Function.class)); + assertThat(type.getInputType()).isAssignableFrom(Object.class); + + type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MessageFunction.class)); + assertThat(type.getInputType()).isAssignableFrom(String.class); + assertThat(type.getOutputType()).isAssignableFrom(String.class); + + type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageFunction.class)); + assertThat(type.getInputType()).isAssignableFrom(String.class); + assertThat(type.getOutputType()).isAssignableFrom(String.class); + + type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MessageConsumer.class)); + assertThat(type.getInputType()).isAssignableFrom(String.class); + + type = FunctionType.of(FunctionTypeUtils.discoverFunctionTypeFromClass(MyMessageConsumer.class)); + assertThat(type.getInputType()).isAssignableFrom(String.class); + } + // @Test // public void testInputTypeByIndex() throws Exception { // Type functionType = getReturnType("function"); @@ -163,4 +187,22 @@ public class FunctionTypeUtilsTests { private Type getReturnType(String methodName) throws Exception { return FunctionTypeUtilsTests.class.getDeclaredMethod(methodName).getGenericReturnType(); } + + //============ + + private interface MessageFunction extends Function, Message> { + + } + + private interface MyMessageFunction extends MessageFunction { + + } + + private interface MessageConsumer extends Consumer> { + + } + + private interface MyMessageConsumer extends MessageConsumer { + + } } diff --git a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionArchiveDeployer.java b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionArchiveDeployer.java index 4b7afd152..1f2f588c8 100644 --- a/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionArchiveDeployer.java +++ b/spring-cloud-function-deployer/src/main/java/org/springframework/cloud/function/deployer/FunctionArchiveDeployer.java @@ -204,7 +204,7 @@ class FunctionArchiveDeployer extends JarLauncher { ReflectionUtils.doWithMethods(functionClass, new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - typeRef.set(FunctionTypeUtils.getFunctionTypeFromFunctionMethod(method)); + typeRef.set(FunctionTypeUtils.discoverFunctionTypeFromFunctionMethod(method)); } }, new MethodFilter() { @Override diff --git a/spring-cloud-function-samples/function-sample-spring-integration/src/main/java/example/FunctionSampleSpringIntegrationApplication.java b/spring-cloud-function-samples/function-sample-spring-integration/src/main/java/example/FunctionSampleSpringIntegrationApplication.java index 63cd82ae7..42ebc98b2 100644 --- a/spring-cloud-function-samples/function-sample-spring-integration/src/main/java/example/FunctionSampleSpringIntegrationApplication.java +++ b/spring-cloud-function-samples/function-sample-spring-integration/src/main/java/example/FunctionSampleSpringIntegrationApplication.java @@ -26,14 +26,14 @@ import org.springframework.integration.dsl.IntegrationFlows; import org.springframework.integration.handler.LoggingHandler; import org.springframework.messaging.Message; -//@SpringBootApplication +@SpringBootApplication public class FunctionSampleSpringIntegrationApplication { public static void main(String[] args) { SpringApplication.run(FunctionSampleSpringIntegrationApplication.class, args); } - //@Bean + @Bean public IntegrationFlow uppercaseFlow() { return IntegrationFlows.from(MessageFunction.class, "uppercase") .transform(String::toUpperCase) @@ -43,5 +43,4 @@ public class FunctionSampleSpringIntegrationApplication { public interface MessageFunction extends Function, Message> { } - } diff --git a/spring-cloud-function-samples/function-sample-spring-integration/src/test/java/example/FunctionSampleSpringIntegrationApplicationTests.java b/spring-cloud-function-samples/function-sample-spring-integration/src/test/java/example/FunctionSampleSpringIntegrationApplicationTests.java index 4d7d7e491..1cf1559c9 100644 --- a/spring-cloud-function-samples/function-sample-spring-integration/src/test/java/example/FunctionSampleSpringIntegrationApplicationTests.java +++ b/spring-cloud-function-samples/function-sample-spring-integration/src/test/java/example/FunctionSampleSpringIntegrationApplicationTests.java @@ -29,14 +29,14 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; -//@RunWith(SpringRunner.class) -//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class FunctionSampleSpringIntegrationApplicationTests { @Autowired private TestRestTemplate restTemplate; - //@Test + @Test public void upperCase() { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON);