Properly resolve FactoryBean for function

Fixes: gh-118

When the `BeanDefinition` for `Function` is a `FactoryBean`
(e.g. `GatewayProxyFactoryBean` in Spring Integration) and that
`BeanDefinition` isn't registered as `@Bean` method (e.g.
Spring Integration Java DSL parser), the `ContextFunctionCatalogAutoConfiguration`
doesn't resolve the target `Function` type properly

* Get the target `Function` bean type via `BeanFactory.getType(String)`
* Make fallback to the `Object.class` instead of bean type since we are
expecting here a generic type anyway
This commit is contained in:
Artem Bilan
2017-11-10 17:00:11 -05:00
committed by Dave Syer
parent 47fd4c3ed2
commit 46fdca479b
2 changed files with 67 additions and 34 deletions

View File

@@ -34,7 +34,6 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -73,6 +72,7 @@ import reactor.core.publisher.Flux;
* @author Dave Syer
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Artem Bilan
*/
@FunctionScan
@Configuration
@@ -197,13 +197,18 @@ public class ContextFunctionCatalogAutoConfiguration {
protected static class ContextFunctionRegistry {
private Map<String, Object> suppliers = new HashMap<>();
private Map<String, Object> functions = new HashMap<>();
private Map<String, Object> consumers = new HashMap<>();
@Autowired
private ConfigurableListableBeanFactory registry;
private ConversionService conversionService;
private Map<Object, String> registrations = new HashMap<>();
private Map<String, Map<ParamType, Class<?>>> types = new HashMap<>();
public Set<String> getSuppliers() {
@@ -507,11 +512,10 @@ public class ContextFunctionCatalogAutoConfiguration {
return FunctionInspector
.isWrapper(findType(function, ParamType.INPUT_WRAPPER))
|| FunctionInspector
.isWrapper(findType(function, ParamType.OUTPUT_WRAPPER));
.isWrapper(findType(function, ParamType.OUTPUT_WRAPPER));
}
private Class<?> findType(String name, AbstractBeanDefinition definition,
ParamType paramType) {
private Class<?> findType(String name, AbstractBeanDefinition definition, ParamType paramType) {
Object source = definition.getSource();
Type param = null;
// Start by assuming output -> Function
@@ -535,17 +539,10 @@ public class ContextFunctionCatalogAutoConfiguration {
param = extractType(type, paramType, index);
}
else if (source instanceof Resource) {
try {
Class<?> beanType = resolveBeanClass(definition);
param = findTypeFromBeanClass(beanType, paramType);
if (param == null) {
// Last chance
param = beanType;
}
}
catch (ClassNotFoundException e) {
throw new IllegalStateException(
"Cannot instrospect bean: " + definition, e);
Class<?> beanType = this.registry.getType(name);
param = findTypeFromBeanClass(beanType, paramType);
if (param == null) {
return Object.class;
}
}
else {
@@ -554,8 +551,8 @@ public class ContextFunctionCatalogAutoConfiguration {
if (resolvable != null) {
param = resolvable.getGeneric(index).getGeneric(0).getType();
}
else if (registry instanceof BeanFactory) {
Object bean = ((BeanFactory) registry).getBean(name);
else {
Object bean = this.registry.getBean(name);
if (bean instanceof FunctionFactoryMetadata) {
FunctionFactoryMetadata<?> factory = (FunctionFactoryMetadata<?>) bean;
Type type = factory.getFactoryMethod().getGenericReturnType();
@@ -563,8 +560,7 @@ public class ContextFunctionCatalogAutoConfiguration {
}
}
}
Class<?> result = extractClass(name, param, paramType);
return result;
return extractClass(name, param, paramType);
}
private Class<?> extractClass(String name, Type param, ParamType paramType) {
@@ -600,17 +596,6 @@ public class ContextFunctionCatalogAutoConfiguration {
return null;
}
private Class<?> resolveBeanClass(AbstractBeanDefinition definition)
throws ClassNotFoundException, LinkageError {
try {
return ClassUtils.forName(definition.getBeanClassName(), null);
}
catch (ClassNotFoundException e) {
return ClassUtils.forName(definition.getBeanClassName(),
getClass().getClassLoader());
}
}
private Type findBeanType(AbstractBeanDefinition definition,
MethodMetadataReadingVisitor visitor) {
Class<?> factory = ClassUtils
@@ -681,7 +666,7 @@ public class ContextFunctionCatalogAutoConfiguration {
return Message.class
.isAssignableFrom(findType(function, ParamType.INPUT_INNER_WRAPPER))
|| Message.class.isAssignableFrom(
findType(function, ParamType.OUTPUT_INNER_WRAPPER));
findType(function, ParamType.OUTPUT_INNER_WRAPPER));
}
private Class<?> findType(Object function, ParamType type) {
@@ -710,7 +695,7 @@ public class ContextFunctionCatalogAutoConfiguration {
}
}
static enum ParamType {
enum ParamType {
INPUT, OUTPUT, INPUT_WRAPPER, OUTPUT_WRAPPER, INPUT_INNER_WRAPPER, OUTPUT_INNER_WRAPPER;
public boolean isOutput() {

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.function.context;
import static org.assertj.core.api.Assertions.assertThat;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
@@ -31,8 +33,12 @@ import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
@@ -46,6 +52,7 @@ import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
@@ -53,12 +60,11 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.core.publisher.Flux;
/**
* @author Dave Syer
* @author Artem Bilan
*
*/
public class ContextFunctionCatalogAutoConfigurationTests {
@@ -375,6 +381,15 @@ public class ContextFunctionCatalogAutoConfigurationTests {
assertThat(ContextFunctionCatalogAutoConfigurationTests.value).isEqualTo("hello");
}
@Test
public void factoryBeanFunction() {
create(FactoryBeanConfiguration.class);
assertThat(this.context.getBean("function")).isInstanceOf(Function.class);
assertThat(this.catalog.lookupFunction("function")).isInstanceOf(Function.class);
Function<Flux<String>, Flux<String>> f = this.catalog.lookupFunction("function");
assertThat(f.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO-bar");
}
private void create(Class<?> type, String... props) {
create(new Class<?>[] { type }, props);
}
@@ -597,6 +612,39 @@ public class ContextFunctionCatalogAutoConfigurationTests {
}
}
@EnableAutoConfiguration
@Configuration
protected static class FactoryBeanConfiguration implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
RootBeanDefinition beanDefinition = new RootBeanDefinition(FunctionFactoryBean.class);
beanDefinition.setSource(new DescriptiveResource("Function"));
registry.registerBeanDefinition("function", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
private static class FunctionFactoryBean extends AbstractFactoryBean<Function<String, String>> {
@Override
public Class<?> getObjectType() {
return Function.class;
}
@Override
protected Function<String, String> createInstance() throws Exception {
return s -> s.toUpperCase() + "-bar";
}
}
public static class Foo {
private String value;