From 12f45f1507c54da3bc5582bb149ee4e5f3f57a44 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Thu, 11 Aug 2022 16:55:49 +0200 Subject: [PATCH] GH-915 Make Kotlin initialization 'lazy' This way just like with any other function, Kotlin initialization, type discovery etc will be performed on function lookup This will also ensure that order of various post processors doesn't get in the way. Resolves #915 --- .../BeanFactoryAwareFunctionRegistry.java | 9 +++ ...tlinLambdaToFunctionAutoConfiguration.java | 59 ++------------ ...ogAutoConfigurationKotlinSuspendTests.java | 81 ++++--------------- ...onCatalogAutoConfigurationKotlinTests.java | 56 ++++++------- 4 files changed, 59 insertions(+), 146 deletions(-) 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