Merge branch '4.1.x' of github.com:spring-cloud/spring-cloud-function into 4.1.x

This commit is contained in:
Ryan Baxter
2025-04-13 09:35:42 -04:00
71 changed files with 307 additions and 136 deletions

View File

@@ -21,21 +21,22 @@ import org.springframework.messaging.Message;
/**
* Java-based strategy to assist with determining the name of the route-to function definition.
* Once implementation is registered as a bean in application context
* Once an implementation is registered as a bean in application context
* it will be picked up by the {@link RoutingFunction}.
*
* <p/>
* While {@link RoutingFunction} provides several mechanisms to determine the route-to function definition
* this callback takes precedence over all of them.
*
* @author Oleg Zhurakousky
* @author John Blum
* @since 3.1
*/
public interface MessageRoutingCallback {
/**
* Computes and returns the instance of {@link String} which encapsulates,
* at the very minimum, function definition.
* <br><br>
* Computes and returns an instance of {@link String}, which encapsulates,
* at the very minimum, a function definition.
* <p/>
* Providing such message is primarily an optimization feature. It could be useful for cases
* where routing procedure is complex and results in, let's say, conversion of the payload to
* the target type, which would effectively be thrown away if the ability to modify the target

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");
* 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 Andrey Shlykov
* @author Artem Bilan
*
* @since 3.0
*/
@@ -382,6 +383,12 @@ public final class FunctionTypeUtils {
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;
}

View File

@@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.gson.Gson;
import io.cloudevents.spring.messaging.CloudEventMessageConverter;
@@ -228,7 +229,7 @@ public class ContextFunctionCatalogAutoConfiguration {
mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
}
mapper.registerModule(new JodaModule());
if (KotlinDetector.isKotlinPresent()) {
try {
if (!mapper.getRegisteredModuleIds().contains("com.fasterxml.jackson.module.kotlin.KotlinModule")) {

View File

@@ -45,10 +45,11 @@ import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* An implementation of Function which acts as a gateway/router by actually
* An implementation of {@link Function} which acts as a gateway/router by actually
* delegating incoming invocation to a function specified .. .
*
* @author Oleg Zhurakousky
* @author John Blum
* @since 2.1
*
*/
@@ -127,8 +128,7 @@ public class RoutingFunction implements Function<Object, Object> {
private Object route(Object input, boolean originalInputIsPublisher) {
FunctionInvocationWrapper function = null;
if (input instanceof Message) {
Message<?> message = (Message<?>) input;
if (input instanceof Message<?> message) {
if (this.routingCallback != null) {
String functionDefinition = this.routingCallback.routingResult(message);
if (StringUtils.hasText(functionDefinition)) {
@@ -155,7 +155,7 @@ public class RoutingFunction implements Function<Object, Object> {
}
}
}
else if (input instanceof Publisher) {
else if (input instanceof Publisher<?> publisher) {
if (StringUtils.hasText(functionProperties.getDefinition())) {
function = functionFromDefinition(functionProperties.getDefinition());
}
@@ -163,9 +163,9 @@ public class RoutingFunction implements Function<Object, Object> {
function = this.functionFromExpression(functionProperties.getRoutingExpression(), input);
}
else {
return input instanceof Mono
? Mono.from((Publisher<?>) input).map(v -> route(v, originalInputIsPublisher))
: Flux.from((Publisher<?>) input).map(v -> route(v, originalInputIsPublisher));
return input instanceof Mono<?> mono
? Mono.from(mono).map(v -> route(v, originalInputIsPublisher))
: Flux.from(publisher).map(v -> route(v, originalInputIsPublisher));
}
}
else {
@@ -184,7 +184,7 @@ public class RoutingFunction implements Function<Object, Object> {
}
}
if (function.getTarget().equals(this)) {
if (this.equals(function.getTarget())) {
throw new IllegalStateException("Failed to establish route, and routing to itself is not allowed as it creates a loop. Please provide: "
+ "'spring.cloud.function.definition' as Message header or as application property or "
+ "'spring.cloud.function.routing-expression' as application property.");

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");
* 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.Tuples;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.builder.SpringApplicationBuilder;
@@ -85,6 +86,7 @@ import static org.junit.jupiter.api.Assertions.fail;
/**
*
* @author Oleg Zhurakousky
* @author Artem Bilan
*
*/
public class BeanFactoryAwareFunctionRegistryTests {
@@ -895,6 +897,14 @@ public class BeanFactoryAwareFunctionRegistryTests {
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
// see GH-707
public void testConcurrencyOnLookup() throws Exception {
@@ -1346,6 +1356,23 @@ public class BeanFactoryAwareFunctionRegistryTests {
public Consumer<Flux<Person>> reactivePojoConsumer() {
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