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 3efa54d88..7f159931a 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 @@ -38,11 +38,13 @@ import org.springframework.cloud.function.context.FunctionProperties; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; import org.springframework.cloud.function.context.config.FunctionContextUtils; +import org.springframework.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration; import org.springframework.cloud.function.core.FunctionInvocationHelper; import org.springframework.cloud.function.json.JsonMapper; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.KotlinDetector; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.lang.Nullable; @@ -144,6 +146,13 @@ public class BeanFactoryAwareFunctionRegistry extends SimpleFunctionRegistry imp else if (functionCandidate instanceof BiFunction || functionCandidate instanceof BiConsumer) { functionRegistration = this.registerMessagingBiFunction(functionCandidate, functionName); } + else if (KotlinDetector.isKotlinType(functionCandidate.getClass())) { + KotlinLambdaToFunctionAutoConfiguration.KotlinFunctionWrapper wrapper = + new KotlinLambdaToFunctionAutoConfiguration.KotlinFunctionWrapper(functionCandidate); + wrapper.setName(functionName); + wrapper.setBeanFactory(this.applicationContext.getBeanFactory()); + functionRegistration = wrapper.getFunctionRegistration(); + } else if (this.isFunctionPojo(functionCandidate, functionName)) { Method functionalMethod = FunctionTypeUtils.discoverFunctionalMethod(functionCandidate.getClass()); functionCandidate = this.proxyTarget(functionCandidate, functionalMethod); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java index f9dc48484..3b6209447 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/KotlinLambdaToFunctionAutoConfiguration.java @@ -35,16 +35,7 @@ import reactor.core.publisher.Flux; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; @@ -83,46 +74,14 @@ public class KotlinLambdaToFunctionAutoConfiguration { }; } - /** - * Will transform all discovered Kotlin's Function lambdas to java - * Supplier, Function and Consumer, retaining the original Kotlin type - * characteristics. - * - * @return the bean factory post processor - */ - @Bean - public static BeanFactoryPostProcessor kotlinToFunctionTransformer(ConfigurableListableBeanFactory beanFactory) { - return new BeanFactoryPostProcessor() { - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { - String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames(); - for (String beanDefinitionName : beanDefinitionNames) { - BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName); - - if (beanDefinition instanceof AnnotatedBeanDefinition && ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata() != null) { - String typeName = ((AnnotatedBeanDefinition) beanDefinition).getFactoryMethodMetadata().getReturnTypeName(); - if (typeName.startsWith("kotlin.jvm.functions.Function")) { - RootBeanDefinition cbd = new RootBeanDefinition(KotlinFunctionWrapper.class); - ConstructorArgumentValues ca = new ConstructorArgumentValues(); - ca.addGenericArgumentValue(beanDefinition); - cbd.setConstructorArgumentValues(ca); - ((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(beanDefinitionName + FunctionRegistration.REGISTRATION_NAME_SUFFIX, cbd); - } - } - } - } - }; - } @SuppressWarnings({ "unchecked", "rawtypes" }) public static final class KotlinFunctionWrapper implements Function, Supplier, Consumer, Function0, Function1, Function2, - Function3, Function4, - FactoryBean, - BeanNameAware, - BeanFactoryAware { + Function3, Function4 { +// FactoryBean, +// BeanNameAware, +// BeanFactoryAware { private final Object kotlinLambdaTarget; @@ -192,8 +151,7 @@ public class KotlinLambdaToFunctionAutoConfiguration { return this.apply(null); } - @Override - public FunctionRegistration getObject() throws Exception { + public FunctionRegistration getFunctionRegistration() { String name = this.name.endsWith(FunctionRegistration.REGISTRATION_NAME_SUFFIX) ? this.name.replace(FunctionRegistration.REGISTRATION_NAME_SUFFIX, "") : this.name; @@ -284,17 +242,16 @@ public class KotlinLambdaToFunctionAutoConfiguration { return type.getTypeName().contains(clazz.getName()); } - @Override public Class getObjectType() { return FunctionRegistration.class; } - @Override - public void setBeanName(String name) { + + public void setName(String name) { this.name = name; } - @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; } diff --git a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinSuspendTests.java b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinSuspendTests.java index 53450d2ba..874de12a4 100644 --- a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinSuspendTests.java +++ b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinSuspendTests.java @@ -16,27 +16,16 @@ package org.springframework.cloud.function.kotlin; -import java.lang.reflect.ParameterizedType; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import kotlin.jvm.functions.Function2; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.function.context.FunctionCatalog; -import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.context.support.GenericApplicationContext; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Adrien Poupard @@ -60,67 +49,29 @@ public class ContextFunctionCatalogAutoConfigurationKotlinSuspendTests { create(new Class[] { KotlinSuspendFlowLambdasConfiguration.class, ContextFunctionCatalogAutoConfigurationKotlinTests.SimpleConfiguration.class }); - Object function = this.context.getBean("kotlinFunction"); - ParameterizedType functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinFunction", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); - assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + FunctionCatalog functionCatalog = this.context.getBean(FunctionCatalog.class); - function = this.context.getBean("kotlinConsumer"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinConsumer", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Consumer.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(1); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + FunctionInvocationWrapper kotlinFunction = functionCatalog.lookup("kotlinFunction"); + assertThat(kotlinFunction.isFunction()).isTrue(); + assertThat(kotlinFunction.getInputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + assertThat(kotlinFunction.getOutputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); - function = this.context.getBean("kotlinSupplier"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinSupplier", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Supplier.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(1); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + FunctionInvocationWrapper kotlinConsumer = functionCatalog.lookup("kotlinConsumer"); + assertThat(kotlinConsumer.isConsumer()).isTrue(); + assertThat(kotlinConsumer.getInputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); - function = this.context.getBean("kotlinPojoFunction"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinPojoFunction", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); - assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo("reactor.core.publisher.Flux"); - } - - @Test - public void shouldNotLoadKotlinSuspendLambasNotUsingFlow() { - create(new Class[] { KotlinSuspendLambdasConfiguration.class, - ContextFunctionCatalogAutoConfigurationKotlinTests.SimpleConfiguration.class }); - - assertThat(this.context.getBean("kotlinFunction")).isInstanceOf(Function2.class); - assertThatThrownBy(() -> { - this.catalog.lookup(Function.class, "kotlinFunction"); - }).isInstanceOf(BeanCreationException.class); - - assertThatThrownBy(() -> { - this.catalog.lookup(Function.class, "kotlinConsumer"); - }).isInstanceOf(BeanCreationException.class); - - assertThatThrownBy(() -> { - this.catalog.lookup(Supplier.class, "kotlinSupplier"); - }).isInstanceOf(BeanCreationException.class); + FunctionInvocationWrapper kotlinSupplier = functionCatalog.lookup("kotlinSupplier"); + assertThat(kotlinSupplier.isSupplier()).isTrue(); + assertThat(kotlinSupplier.getOutputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + FunctionInvocationWrapper kotlinPojoFunction = functionCatalog.lookup("kotlinPojoFunction"); + assertThat(kotlinPojoFunction.isFunction()).isTrue(); + assertThat(kotlinPojoFunction.getInputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); + assertThat(kotlinPojoFunction.getOutputType().getTypeName()).isEqualTo("reactor.core.publisher.Flux"); } private void create(Class[] types, String... props) { this.context = (GenericApplicationContext) new SpringApplicationBuilder(types).properties(props).run(); this.catalog = this.context.getBean(FunctionCatalog.class); } - - @EnableAutoConfiguration - @Configuration - protected static class SimpleConfiguration { - - @Bean - public Function function2() { - return value -> value + "function2"; - } - - } - } diff --git a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java index d8afa544e..2b3d794ee 100644 --- a/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java +++ b/spring-cloud-function-kotlin/src/test/java/org/springframework/cloud/function/kotlin/ContextFunctionCatalogAutoConfigurationKotlinTests.java @@ -16,8 +16,6 @@ package org.springframework.cloud.function.kotlin; -import java.lang.reflect.ParameterizedType; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -59,39 +57,37 @@ public class ContextFunctionCatalogAutoConfigurationKotlinTests { create(new Class[] { KotlinLambdasConfiguration.class, SimpleConfiguration.class }); - Object function = this.context.getBean("kotlinFunction"); - ParameterizedType functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinFunction", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo(String.class.getName()); - assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName()); + FunctionCatalog functionCatalog = this.context.getBean(FunctionCatalog.class); - function = this.context.getBean("kotlinConsumer"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinConsumer", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Consumer.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(1); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo(String.class.getName()); + FunctionInvocationWrapper kotlinFunction = functionCatalog.lookup("kotlinFunction"); + assertThat(kotlinFunction.isFunction()).isTrue(); + assertThat(kotlinFunction.getInputType()).isEqualTo(String.class); + assertThat(kotlinFunction.getOutputType()).isEqualTo(String.class); - function = this.context.getBean("kotlinSupplier"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinSupplier", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Supplier.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(1); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo(String.class.getName()); + FunctionInvocationWrapper kotlinConsumer = functionCatalog.lookup("kotlinConsumer"); + assertThat(kotlinConsumer.isConsumer()).isTrue(); + assertThat(kotlinConsumer.getInputType()).isEqualTo(String.class); - function = this.context.getBean("kotlinPojoFunction"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinPojoFunction", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo(Person.class.getName()); - assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName()); + FunctionInvocationWrapper kotlinSupplier = functionCatalog.lookup("kotlinSupplier"); + assertThat(kotlinSupplier.isSupplier()).isTrue(); + assertThat(kotlinSupplier.getOutputType()).isEqualTo(String.class); + FunctionInvocationWrapper kotlinPojoFunction = functionCatalog.lookup("kotlinPojoFunction"); + assertThat(kotlinPojoFunction.isFunction()).isTrue(); + assertThat(kotlinPojoFunction.getInputType()).isEqualTo(Person.class); + assertThat(kotlinPojoFunction.getOutputType()).isEqualTo(String.class); - function = this.context.getBean("kotlinListPojoFunction"); - functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinListPojoFunction", this.context); - assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); - assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); - assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("java.util.List"); - assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName()); + FunctionInvocationWrapper kotlinListPojoFunction = functionCatalog.lookup("kotlinListPojoFunction"); + assertThat(kotlinListPojoFunction.isFunction()).isTrue(); + assertThat(kotlinListPojoFunction.getInputType().getTypeName()).isEqualTo("java.util.List"); + assertThat(kotlinListPojoFunction.getOutputType()).isEqualTo(String.class); + +// function = this.context.getBean("kotlinListPojoFunction"); +// functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinListPojoFunction", this.context); +// assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName()); +// assertThat(functionType.getActualTypeArguments().length).isEqualTo(2); +// assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("java.util.List"); +// assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName()); } @Test