Fix FunctionTypeUtils for FactoryBean

The function bean can be declared as a `FactoryBean`,
for example with Spring Integration's `GatewayProxyFactoryBean`.
See `LogConsumerConfiguration` in Spring Functions Catalog project.

* Fix `FunctionTypeUtils` to react to the `ParameterizedType`
and check its assignments against `Factory` bean.
Then resolves its generic to the proper target function type.
* Add `BeanFactoryAwareFunctionRegistryTests.functionFromFactoryBeanIsProperlyResolved()`
to verify that `FactoryBean<Function<?, ?>>` is resolved properly
This commit is contained in:
Artem Bilan
2024-12-26 14:02:19 -05:00
committed by Oleg Zhurakousky
parent 93f0c5e358
commit 5c92db4f80
2 changed files with 36 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2022 the original author or authors. * Copyright 2019-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -63,6 +63,7 @@ import org.springframework.util.StringUtils;
* *
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
* @author Andrey Shlykov * @author Andrey Shlykov
* @author Artem Bilan
* *
* @since 3.0 * @since 3.0
*/ */
@@ -382,6 +383,12 @@ public final class FunctionTypeUtils {
type = FunctionContextUtils.findType(applicationContext.getBeanFactory(), beanDefinitionName); type = FunctionContextUtils.findType(applicationContext.getBeanFactory(), beanDefinitionName);
} }
} }
else if (type instanceof ParameterizedType) {
ResolvableType resolvableType = ResolvableType.forType(type);
if (FactoryBean.class.isAssignableFrom(resolvableType.toClass())) {
return resolvableType.getGeneric(0).getType();
}
}
return type; return type;
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2019-2021 the original author or authors. * Copyright 2019-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -54,6 +54,7 @@ import reactor.util.function.Tuple2;
import reactor.util.function.Tuple3; import reactor.util.function.Tuple3;
import reactor.util.function.Tuples; import reactor.util.function.Tuples;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
@@ -85,6 +86,7 @@ import static org.junit.jupiter.api.Assertions.fail;
/** /**
* *
* @author Oleg Zhurakousky * @author Oleg Zhurakousky
* @author Artem Bilan
* *
*/ */
public class BeanFactoryAwareFunctionRegistryTests { public class BeanFactoryAwareFunctionRegistryTests {
@@ -891,6 +893,14 @@ public class BeanFactoryAwareFunctionRegistryTests {
assertThat(result.size()).isEqualTo(3); assertThat(result.size()).isEqualTo(3);
} }
@Test
void functionFromFactoryBeanIsProperlyResolved() {
FunctionCatalog catalog = configureCatalog();
Function<Number, String> numberToStringFactoryBean = catalog.lookup("numberToStringFactoryBean");
assertThat(numberToStringFactoryBean).isNotNull();
assertThat(numberToStringFactoryBean.apply(1)).isEqualTo("1");
}
@Test @Test
// see GH-707 // see GH-707
public void testConcurrencyOnLookup() throws Exception { public void testConcurrencyOnLookup() throws Exception {
@@ -1342,6 +1352,23 @@ public class BeanFactoryAwareFunctionRegistryTests {
public Consumer<Flux<Person>> reactivePojoConsumer() { public Consumer<Flux<Person>> reactivePojoConsumer() {
return flux -> flux.subscribe(v -> consumerInputRef.set(v)); return flux -> flux.subscribe(v -> consumerInputRef.set(v));
} }
@Bean
FactoryBean<Function<Number, String>> numberToStringFactoryBean() {
return new FactoryBean<>() {
@Override
public Function<Number, String> getObject() {
return Number::toString;
}
@Override
public Class<?> getObjectType() {
return Function.class;
}
};
}
} }
@EnableAutoConfiguration @EnableAutoConfiguration