Discover function type from FactoryBean

* Fix spring-integration sample

* Added discoverFunctionTypeFromClass() method to FunctionTypeUtils

* A `FactoryBean` may produce a function instance as well.
Add a logic into `BeanFactoryAwareFunctionRegistry` to discover a function type from the `FactoryBean.getObjectType()`
This commit is contained in:
Oleg Zhurakousky
2019-09-25 10:24:07 -04:00
committed by Artem Bilan
parent bdd4320a59
commit 97bea81836
6 changed files with 84 additions and 9 deletions

View File

@@ -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;

View File

@@ -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") ||

View File

@@ -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<T> extends Function<Message<String>, Message<String>> {
}
private interface MyMessageFunction extends MessageFunction<Date> {
}
private interface MessageConsumer<T> extends Consumer<Message<String>> {
}
private interface MyMessageConsumer extends MessageConsumer<Date> {
}
}

View File

@@ -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

View File

@@ -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")
.<String, String>transform(String::toUpperCase)
@@ -43,5 +43,4 @@ public class FunctionSampleSpringIntegrationApplication {
public interface MessageFunction extends Function<Message<String>, Message<String>> {
}
}

View File

@@ -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);