GH-939 & GH-956 Fix Kotlin function registration regression
Resolves #939 Resolves #956
This commit is contained in:
@@ -31,6 +31,8 @@ import reactor.core.publisher.Flux;
|
|||||||
|
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
|
import org.springframework.cloud.function.context.catalog.FunctionTypeUtils;
|
||||||
|
import org.springframework.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration;
|
||||||
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@@ -105,11 +107,14 @@ public class FunctionRegistration<T> implements BeanNameAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FunctionRegistration<T> type(Type type) {
|
public FunctionRegistration<T> type(Type type) {
|
||||||
|
this.type = type;
|
||||||
|
if (KotlinDetector.isKotlinPresent() && this.target instanceof KotlinLambdaToFunctionAutoConfiguration.KotlinFunctionWrapper) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
Type discoveredFunctionType = FunctionTypeUtils.discoverFunctionTypeFromClass(this.target.getClass());
|
Type discoveredFunctionType = FunctionTypeUtils.discoverFunctionTypeFromClass(this.target.getClass());
|
||||||
if (discoveredFunctionType == null) { // only valid for Kafka Stream KStream[] return type.
|
if (discoveredFunctionType == null) { // only valid for Kafka Stream KStream[] return type.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.type = type;
|
|
||||||
|
|
||||||
Class<?> inputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(discoveredFunctionType));
|
Class<?> inputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getInputType(discoveredFunctionType));
|
||||||
Class<?> outputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(discoveredFunctionType));
|
Class<?> outputType = FunctionTypeUtils.getRawType(FunctionTypeUtils.getOutputType(discoveredFunctionType));
|
||||||
@@ -169,44 +174,10 @@ public class FunctionRegistration<T> implements BeanNameAware {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @SuppressWarnings({ "unchecked", "rawtypes" })
|
|
||||||
// public <S> FunctionRegistration<S> wrap() {
|
|
||||||
// this.isFunctionSignatureSupported();
|
|
||||||
// FunctionRegistration<S> result;
|
|
||||||
// if (this.type == null) {
|
|
||||||
// result = (FunctionRegistration<S>) this;
|
|
||||||
// }
|
|
||||||
// else if (this.target instanceof RoutingFunction) {
|
|
||||||
// S target = (S) this.target;
|
|
||||||
// result = new FunctionRegistration<S>(target);
|
|
||||||
// result.type(this.type.getType());
|
|
||||||
// result = result.target(target).names(this.names)
|
|
||||||
// .type(result.type.wrap(Flux.class)).properties(this.properties);
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// S target = (S) this.target;
|
|
||||||
// result = new FunctionRegistration<S>(target);
|
|
||||||
// result.type(this.type.getType());
|
|
||||||
// result = result.target(target).names(this.names)
|
|
||||||
// .type(result.type.wrap(Flux.class)).properties(this.properties);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return result;
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBeanName(String name) {
|
public void setBeanName(String name) {
|
||||||
if (CollectionUtils.isEmpty(this.names)) {
|
if (CollectionUtils.isEmpty(this.names)) {
|
||||||
this.name(name);
|
this.name(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void isFunctionSignatureSupported() {
|
|
||||||
// if (type != null) {
|
|
||||||
// Assert.isTrue(!(Mono.class.isAssignableFrom(this.type.getOutputWrapper())
|
|
||||||
// && Mono.class.isAssignableFrom(this.type.getInputWrapper())),
|
|
||||||
// "Function<Mono, Mono> is not supported.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import java.util.function.Supplier;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import kotlin.jvm.functions.Function0;
|
||||||
|
import kotlin.jvm.functions.Function1;
|
||||||
import net.jodah.typetools.TypeResolver;
|
import net.jodah.typetools.TypeResolver;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@@ -46,6 +48,7 @@ import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry
|
|||||||
import org.springframework.cloud.function.context.config.FunctionContextUtils;
|
import org.springframework.cloud.function.context.config.FunctionContextUtils;
|
||||||
import org.springframework.cloud.function.context.config.RoutingFunction;
|
import org.springframework.cloud.function.context.config.RoutingFunction;
|
||||||
import org.springframework.context.support.GenericApplicationContext;
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -187,6 +190,14 @@ public final class FunctionTypeUtils {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Type discoverFunctionTypeFromClass(Class<?> functionalClass) {
|
public static Type discoverFunctionTypeFromClass(Class<?> functionalClass) {
|
||||||
|
if (KotlinDetector.isKotlinPresent()) {
|
||||||
|
if (Function1.class.isAssignableFrom(functionalClass)) {
|
||||||
|
return TypeResolver.reify(Function1.class, (Class<Function1<?, ?>>) functionalClass);
|
||||||
|
}
|
||||||
|
else if (Function0.class.isAssignableFrom(functionalClass)) {
|
||||||
|
return TypeResolver.reify(Function0.class, (Class<Function0<?>>) functionalClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Function.class.isAssignableFrom(functionalClass)) {
|
if (Function.class.isAssignableFrom(functionalClass)) {
|
||||||
for (Type superInterface : functionalClass.getGenericInterfaces()) {
|
for (Type superInterface : functionalClass.getGenericInterfaces()) {
|
||||||
if (superInterface != null && !superInterface.equals(Object.class)) {
|
if (superInterface != null && !superInterface.equals(Object.class)) {
|
||||||
@@ -203,7 +214,7 @@ public final class FunctionTypeUtils {
|
|||||||
else if (Supplier.class.isAssignableFrom(functionalClass)) {
|
else if (Supplier.class.isAssignableFrom(functionalClass)) {
|
||||||
return TypeResolver.reify(Supplier.class, (Class<Supplier<?>>) functionalClass);
|
return TypeResolver.reify(Supplier.class, (Class<Supplier<?>>) functionalClass);
|
||||||
}
|
}
|
||||||
return null;
|
return TypeResolver.reify(functionalClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ public class ContextFunctionCatalogAutoConfigurationKotlinTests {
|
|||||||
public void typeDiscoveryTests() {
|
public void typeDiscoveryTests() {
|
||||||
create(new Class[] { KotlinLambdasConfiguration.class,
|
create(new Class[] { KotlinLambdasConfiguration.class,
|
||||||
SimpleConfiguration.class,
|
SimpleConfiguration.class,
|
||||||
KotlinComponentFunction.class});
|
KotlinComponentFunction.class,
|
||||||
|
ComponentUppercase.class});
|
||||||
|
|
||||||
FunctionCatalog functionCatalog = this.context.getBean(FunctionCatalog.class);
|
FunctionCatalog functionCatalog = this.context.getBean(FunctionCatalog.class);
|
||||||
|
|
||||||
@@ -89,12 +90,19 @@ public class ContextFunctionCatalogAutoConfigurationKotlinTests {
|
|||||||
assertThat(kotlinListPojoFunction.getInputType().getTypeName()).isEqualTo("java.util.List<org.springframework.cloud.function.kotlin.Person>");
|
assertThat(kotlinListPojoFunction.getInputType().getTypeName()).isEqualTo("java.util.List<org.springframework.cloud.function.kotlin.Person>");
|
||||||
assertThat(kotlinListPojoFunction.getOutputType()).isEqualTo(String.class);
|
assertThat(kotlinListPojoFunction.getOutputType()).isEqualTo(String.class);
|
||||||
|
|
||||||
// function = this.context.getBean("kotlinListPojoFunction");
|
FunctionInvocationWrapper componentUppercase = functionCatalog.lookup("componentUppercase");
|
||||||
// functionType = (ParameterizedType) FunctionTypeUtils.discoverFunctionType(function, "kotlinListPojoFunction", this.context);
|
assertThat(componentUppercase.isFunction()).isTrue();
|
||||||
// assertThat(functionType.getRawType().getTypeName()).isEqualTo(Function.class.getName());
|
assertThat(componentUppercase.getInputType()).isEqualTo(String.class);
|
||||||
// assertThat(functionType.getActualTypeArguments().length).isEqualTo(2);
|
assertThat(componentUppercase.getOutputType()).isEqualTo(String.class);
|
||||||
// assertThat(functionType.getActualTypeArguments()[0].getTypeName()).isEqualTo("java.util.List<org.springframework.cloud.function.kotlin.Person>");
|
|
||||||
// assertThat(functionType.getActualTypeArguments()[1].getTypeName()).isEqualTo(String.class.getName());
|
assertThat(componentUppercase.apply("hello")).isEqualTo("HELLO");
|
||||||
|
|
||||||
|
FunctionInvocationWrapper uppercaseBean = functionCatalog.lookup("uppercase");
|
||||||
|
assertThat(uppercaseBean.isFunction()).isTrue();
|
||||||
|
assertThat(uppercaseBean.getInputType()).isEqualTo(String.class);
|
||||||
|
assertThat(uppercaseBean.getOutputType()).isEqualTo(String.class);
|
||||||
|
|
||||||
|
assertThat(uppercaseBean.apply("hello")).isEqualTo("HELLO");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.springframework.cloud.function.kotlin
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class ComponentUppercase : (String) -> String {
|
||||||
|
override fun invoke(p1: String): String {
|
||||||
|
return p1.uppercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,9 @@ import java.util.List
|
|||||||
@EnableAutoConfiguration
|
@EnableAutoConfiguration
|
||||||
@Configuration
|
@Configuration
|
||||||
class KotlinLambdasConfiguration {
|
class KotlinLambdasConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun uppercase(): Function<String, String> = KotlinComponentFunction()
|
||||||
@Bean
|
@Bean
|
||||||
fun kotlinFunction(): (String) -> String {
|
fun kotlinFunction(): (String) -> String {
|
||||||
return { it.toUpperCase() }
|
return { it.toUpperCase() }
|
||||||
|
|||||||
Reference in New Issue
Block a user