GH-3047: Add GatewayProxySpec for Java DSL

Fixes https://github.com/spring-projects/spring-integration/issues/3047

* Improve `GatewayProxyFactoryBean` to determine the return type of the
method call from the interface generic types, when the `serviceInterface`
is a `java.util.function.Function`
* Propagate `MethodArgsHolder` as a `rootObject` for SpEL evaluations
* Deprecate `#gatewayMethod` and `#args` evaluation context variables
in favor of `MethodArgsHolder` as root object.
They will be removed in the future release and a single
`EvaluationContext` will be used for all the gateway expressions
* Introduce an
`IntegrationFlows.from(Class<?> serviceInterface, Consumer<GatewayProxySpec> endpointConfigurer)`
to allow to configure any valid gateway proxy options similar to what
we have with the `<gateway>` and `@MessagingGateway`.
This way we are very close to consistency between different approaches

* * Remove `default` prefix from `GatewayProxySpec` options
* Document the change
This commit is contained in:
Artem Bilan
2019-09-06 13:46:16 -04:00
committed by GitHub
parent c668a046e1
commit 29bebdba97
17 changed files with 565 additions and 132 deletions

View File

@@ -91,8 +91,8 @@
<header name="foo" value="bar"/>
</method>
<method name="oneWayWithTimeouts" request-channel="otherRequestChannel"
request-timeout="#args[1]"
reply-timeout="#args[2]">
request-timeout="args[1]"
reply-timeout="args[2]">
</method>
</gateway>

View File

@@ -108,8 +108,8 @@ public class GatewayParserTests {
assertThat(meta.getReplyChannelName()).isEqualTo("foo");
meta = (GatewayMethodMetadata) methods.get("oneWayWithTimeouts");
assertThat(meta).isNotNull();
assertThat(meta.getRequestTimeout()).isEqualTo("#args[1]");
assertThat(meta.getReplyTimeout()).isEqualTo("#args[2]");
assertThat(meta.getRequestTimeout()).isEqualTo("args[1]");
assertThat(meta.getReplyTimeout()).isEqualTo("args[2]");
service.oneWayWithTimeouts("foo", 100L, 200L);
result = channel.receive(10000);
assertThat(result).isNotNull();
@@ -118,7 +118,7 @@ public class GatewayParserTests {
@Test
public void testSolicitResponse() {
PollableChannel channel = (PollableChannel) context.getBean("replyChannel");
channel.send(new GenericMessage<String>("foo"));
channel.send(new GenericMessage<>("foo"));
TestService service = (TestService) context.getBean("solicitResponse");
String result = service.solicitResponse();
assertThat(result).isEqualTo("foo");
@@ -161,7 +161,7 @@ public class GatewayParserTests {
}
@Test
public void testFactoryBeanObjectTypeWithServiceInterface() throws Exception {
public void testFactoryBeanObjectTypeWithServiceInterface() {
ConfigurableListableBeanFactory beanFactory = ((GenericApplicationContext) context).getBeanFactory();
Object attribute = beanFactory.getMergedBeanDefinition("&oneWay").getAttribute(
IntegrationConfigUtils.FACTORY_BEAN_OBJECT_TYPE);
@@ -169,7 +169,7 @@ public class GatewayParserTests {
}
@Test
public void testFactoryBeanObjectTypeWithNoServiceInterface() throws Exception {
public void testFactoryBeanObjectTypeWithNoServiceInterface() {
ConfigurableListableBeanFactory beanFactory = ((GenericApplicationContext) context).getBeanFactory();
Object attribute = beanFactory.getMergedBeanDefinition("&defaultConfig").getAttribute(
IntegrationConfigUtils.FACTORY_BEAN_OBJECT_TYPE);
@@ -177,7 +177,7 @@ public class GatewayParserTests {
}
@Test
public void testMonoGateway() throws Exception {
public void testMonoGateway() {
PollableChannel requestChannel = context.getBean("requestChannel", PollableChannel.class);
MessageChannel replyChannel = context.getBean("replyChannel", MessageChannel.class);
this.startResponder(requestChannel, replyChannel);

View File

@@ -810,7 +810,7 @@ public class IntegrationFlowTests {
@Bean
public IntegrationFlow errorRecovererFlow() {
return IntegrationFlows.from(Function.class, "errorRecovererFunction")
return IntegrationFlows.from(Function.class, (gateway) -> gateway.beanName("errorRecovererFunction"))
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");
}, e -> e.advice(retryAdvice()))
@@ -891,7 +891,8 @@ public class IntegrationFlowTests {
@Bean
public IntegrationFlow globalErrorChannelResolutionFlow(@Qualifier("taskScheduler") TaskExecutor taskExecutor) {
return IntegrationFlows.from(Consumer.class, "globalErrorChannelResolutionFunction")
return IntegrationFlows.from(Consumer.class,
(gateway) -> gateway.beanName("globalErrorChannelResolutionFunction"))
.channel(c -> c.executor(taskExecutor))
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");

View File

@@ -19,6 +19,9 @@ package org.springframework.integration.dsl.gateway;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.lang.reflect.Method;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +35,7 @@ import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.gateway.MethodArgsHolder;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
@@ -97,6 +101,27 @@ public class GatewayDslTests {
.withStackTraceContaining("intentional");
}
@Autowired
private Function<Object, Message<?>> functionGateay;
@Test
void testHeadersFromFunctionGateway() {
Message<?> message = this.functionGateay.apply("testPayload");
assertThat(message.getPayload()).isEqualTo("testPayload");
assertThat(message.getHeaders()).containsKeys("gatewayMethod", "gatewayArgs");
}
@Autowired
private RoutingGateway routingGateway;
@Test
void testRoutingGateway() {
String result = this.routingGateway.route1("test1");
assertThat(result).isEqualTo("route1");
result = this.routingGateway.route2("test2");
assertThat(result).isEqualTo("route2");
}
@Configuration
@EnableIntegration
public static class ContextConfiguration {
@@ -134,6 +159,40 @@ public class GatewayDslTests {
})));
}
@Bean
public IntegrationFlow functionGateway() {
return IntegrationFlows.from(MessageFunction.class,
(gateway) -> gateway
.header("gatewayMethod", MethodArgsHolder::getMethod)
.header("gatewayArgs", MethodArgsHolder::getArgs))
.bridge()
.get();
}
@Bean
public IntegrationFlow routingGateway() {
return IntegrationFlows.from(RoutingGateway.class,
(gateway) -> gateway.header("gatewayMethod", MethodArgsHolder::getMethod))
.route(Message.class, (message) ->
message.getHeaders().get("gatewayMethod", Method.class).getName(),
(router) -> router
.subFlowMapping("route1", (subFlow) -> subFlow.transform((payload) -> "route1"))
.subFlowMapping("route2", (subFlow) -> subFlow.transform((payload) -> "route2")))
.get();
}
}
interface MessageFunction extends Function<Object, Message<?>> {
}
interface RoutingGateway {
String route1(Object payload);
String route2(Object payload);
}
}

View File

@@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.expression.FunctionExpression;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
@@ -236,7 +237,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
map.put(2, "Two");
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
mapper.setBeanFactory(mock(BeanFactory.class));
mapper.setPayloadExpression("'hello'");
mapper.setPayloadExpression(new LiteralExpression("hello"));
Message<?> message = mapper.toMessage(new Object[] { map });
assertThat(message.getPayload()).isEqualTo("hello");
}
@@ -244,12 +245,12 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
@Test
public void toMessageWithNonHeaderMapPayloadExpressionB() throws Exception {
Method method = TestService.class.getMethod("sendNonHeadersMap", Map.class);
Map<Integer, Object> map = new HashMap<Integer, Object>();
Map<Integer, Object> map = new HashMap<>();
map.put(1, "One");
map.put(2, "Two");
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
mapper.setBeanFactory(mock(BeanFactory.class));
mapper.setPayloadExpression("#args[0]");
mapper.setPayloadExpression(new FunctionExpression<MethodArgsHolder>((methodArgs) -> methodArgs.getArgs()[0]));
Message<?> message = mapper.toMessage(new Object[] { map });
assertThat(message.getPayload()).isEqualTo(map);
}
@@ -277,7 +278,7 @@ public class GatewayMethodInboundMessageMapperToMessageTests {
mapB.put("2", "TWO");
GatewayMethodInboundMessageMapper mapper = new GatewayMethodInboundMessageMapper(method);
mapper.setBeanFactory(mock(BeanFactory.class));
mapper.setPayloadExpression("#args[0]");
mapper.setPayloadExpression(new FunctionExpression<MethodArgsHolder>((methodArgs) -> methodArgs.getArgs()[0]));
Message<?> message = mapper.toMessage(new Object[] { mapA, mapB });
assertThat(message.getPayload()).isEqualTo(mapA);
assertThat(message.getHeaders().get("1")).isEqualTo(mapB.get("1"));