GH-3266: Don't override props in AnnGWProxyFB

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

It turns out that `AnnotationGatewayProxyFactoryBean.onInit()` implementation
parses a `@MessagingGateway` attributes ignoring possible properties
population by setters.
This way a Java DSL `GatewayProxySpec` becomes useless since all
its options are overridden by default values from a synthesized
`@MessagingGateway`.
Also it is inconsistency when we declare an `AnnotationGatewayProxyFactoryBean`
as regular bean, but then called setters are ignored

* Add `protected` getters into `GatewayProxyFactoryBean` for all
the properties which can be overridden by annotation attributes
* Fix `AnnotationGatewayProxyFactoryBean` to consult with those getters
before populating a property with value from the annotation

**Cherry-pick to 5.2.x**
This commit is contained in:
Artem Bilan
2020-04-28 12:22:10 -04:00
committed by Gary Russell
parent 000632e298
commit 2a79b07093
3 changed files with 133 additions and 35 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 the original author or authors.
* Copyright 2017-2020 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.
@@ -72,23 +72,38 @@ public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean {
protected void onInit() {
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();
populateGatewayMethodMetadata();
if (getGlobalMethodMetadata() == null) {
populateGatewayMethodMetadata();
}
String defaultRequestTimeout = resolveAttribute("defaultRequestTimeout");
String defaultReplyTimeout = resolveAttribute("defaultReplyTimeout");
JavaUtils.INSTANCE
.acceptIfHasText(resolveAttribute("defaultRequestChannel"), this::setDefaultRequestChannelName)
.acceptIfHasText(resolveAttribute("defaultReplyChannel"), this::setDefaultReplyChannelName)
.acceptIfHasText(resolveAttribute("errorChannel"), this::setErrorChannelName)
.acceptIfHasText(resolveAttribute("defaultRequestTimeout"),
.acceptIfCondition(getDefaultRequestChannel() == null && getDefaultRequestChannelName() == null,
resolveAttribute("defaultRequestChannel"),
this::setDefaultRequestChannelName)
.acceptIfCondition(getDefaultReplyChannel() == null && getDefaultReplyChannelName() == null,
resolveAttribute("defaultReplyChannel"),
this::setDefaultReplyChannelName)
.acceptIfCondition(getErrorChannel() == null && getErrorChannelName() == null,
resolveAttribute("errorChannel"),
this::setErrorChannelName)
.acceptIfCondition(getDefaultRequestTimeout() == null && StringUtils.hasText(defaultRequestTimeout),
defaultRequestTimeout,
value -> setDefaultRequestTimeout(Long.parseLong(value)))
.acceptIfHasText(resolveAttribute("defaultReplyTimeout"),
.acceptIfCondition(getDefaultReplyTimeout() == null && StringUtils.hasText(defaultReplyTimeout),
defaultReplyTimeout,
value -> setDefaultReplyTimeout(Long.parseLong(value)));
String asyncExecutor = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("asyncExecutor"));
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
setAsyncExecutor(null);
}
else if (StringUtils.hasText(asyncExecutor)) {
setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
if (!isAsyncExecutorExplicitlySet()) {
String asyncExecutor = resolveAttribute("asyncExecutor");
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
setAsyncExecutor(null);
}
else if (StringUtils.hasText(asyncExecutor)) {
setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
}
}
super.onInit();
@@ -114,7 +129,7 @@ public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean {
"'defaultHeaders' are not allowed when a 'mapper' is provided");
JavaUtils.INSTANCE
.acceptIfHasText(mapper,
.acceptIfCondition(hasMapper && getMapper() == null, mapper,
value -> setMapper(beanFactory.getBean(value, MethodArgsMessageMapper.class)));
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
@@ -124,23 +139,26 @@ public class AnnotationGatewayProxyFactoryBean extends GatewayProxyFactoryBean {
gatewayMethodMetadata.setPayloadExpression(EXPRESSION_PARSER.parseExpression(defaultPayloadExpression));
}
Map<String, Expression> headerExpressions = Arrays.stream(defaultHeaders)
.collect(Collectors.toMap(
header -> beanFactory.resolveEmbeddedValue((String) header.get("name")),
header -> {
String headerValue = beanFactory.resolveEmbeddedValue((String) header.get("value"));
boolean hasValue = StringUtils.hasText(headerValue);
Map<String, Expression> headerExpressions =
Arrays.stream(defaultHeaders)
.collect(Collectors.toMap(
header -> beanFactory.resolveEmbeddedValue((String) header.get("name")),
header -> {
String headerValue =
beanFactory.resolveEmbeddedValue((String) header.get("value"));
boolean hasValue = StringUtils.hasText(headerValue);
String headerExpression =
beanFactory.resolveEmbeddedValue((String) header.get("expression"));
String headerExpression =
beanFactory.resolveEmbeddedValue((String) header.get("expression"));
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
"exactly one of 'value' or 'expression' is required on a gateway's header.");
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
"exactly one of 'value' or 'expression' is required on a gateway's " +
"header.");
return hasValue ?
new LiteralExpression(headerValue) :
EXPRESSION_PARSER.parseExpression(headerExpression);
}));
return hasValue ?
new LiteralExpression(headerValue) :
EXPRESSION_PARSER.parseExpression(headerExpression);
}));
gatewayMethodMetadata.setHeaderExpressions(headerExpressions);

View File

@@ -137,6 +137,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
private AsyncTaskExecutor asyncExecutor = new SimpleAsyncTaskExecutor();
private boolean asyncExecutorExplicitlySet;
private Class<?> asyncSubmitType;
private Class<?> asyncSubmitListenableType;
@@ -198,6 +200,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.defaultRequestChannelName = defaultRequestChannelName;
}
@Nullable
protected MessageChannel getDefaultRequestChannel() {
return this.defaultRequestChannel;
}
@Nullable
protected String getDefaultRequestChannelName() {
return this.defaultRequestChannelName;
}
/**
* Set the default reply channel. If no default reply channel is provided,
* and no reply channel is configured with annotations, an anonymous,
@@ -221,6 +233,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.defaultReplyChannelName = defaultReplyChannelName;
}
@Nullable
protected MessageChannel getDefaultReplyChannel() {
return this.defaultReplyChannel;
}
@Nullable
protected String getDefaultReplyChannelName() {
return this.defaultReplyChannelName;
}
/**
* Set the error channel. If no error channel is provided, this gateway will
* propagate Exceptions to the caller. To completely suppress Exceptions, provide
@@ -242,6 +264,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.errorChannelName = errorChannelName;
}
@Nullable
protected MessageChannel getErrorChannel() {
return this.errorChannel;
}
@Nullable
protected String getErrorChannelName() {
return this.errorChannelName;
}
/**
* Set the default timeout value for sending request messages. If not explicitly
* configured with an annotation, or on a method element, this value will be used.
@@ -275,6 +307,11 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
}
}
@Nullable
protected Expression getDefaultRequestTimeout() {
return this.defaultRequestTimeout;
}
/**
* Set the default timeout value for receiving reply messages. If not explicitly
* configured with an annotation, or on a method element, this value will be used.
@@ -308,6 +345,11 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
}
}
@Nullable
protected Expression getDefaultReplyTimeout() {
return this.defaultReplyTimeout;
}
@Override
public void setShouldTrack(boolean shouldTrack) {
this.shouldTrack = shouldTrack;
@@ -326,12 +368,15 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
* @param executor The executor.
*/
public void setAsyncExecutor(@Nullable Executor executor) {
if (executor == null && logger.isInfoEnabled()) {
if (executor == null) {
logger.info("A null executor disables the async gateway; " +
"methods returning Future<?> will run on the calling thread");
}
this.asyncExecutor = (executor instanceof AsyncTaskExecutor || executor == null) ? (AsyncTaskExecutor) executor
: new TaskExecutorAdapter(executor);
this.asyncExecutor =
(executor instanceof AsyncTaskExecutor || executor == null)
? (AsyncTaskExecutor) executor
: new TaskExecutorAdapter(executor);
this.asyncExecutorExplicitlySet = true;
}
public void setTypeConverter(TypeConverter typeConverter) {
@@ -347,6 +392,11 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.globalMethodMetadata = globalMethodMetadata;
}
@Nullable
protected GatewayMethodMetadata getGlobalMethodMetadata() {
return this.globalMethodMetadata;
}
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader;
@@ -361,10 +411,20 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.argsMapper = mapper;
}
@Nullable
protected MethodArgsMessageMapper getMapper() {
return this.argsMapper;
}
@Nullable
protected AsyncTaskExecutor getAsyncExecutor() {
return this.asyncExecutor;
}
protected boolean isAsyncExecutorExplicitlySet() {
return this.asyncExecutorExplicitlySet;
}
/**
* Return the Map of {@link Method} to {@link MessagingGatewaySupport}
* generated by this factory bean.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
@@ -32,11 +33,15 @@ import org.springframework.core.task.TaskExecutor;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.gateway.MethodArgsHolder;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
@@ -102,13 +107,26 @@ public class GatewayDslTests {
}
@Autowired
private Function<Object, Message<?>> functionGateay;
private Function<Object, Message<?>> functionGateway;
@Autowired
@Qualifier("&functionGateway.gateway")
private GatewayProxyFactoryBean functionGatewayFactoryBean;
@Test
void testHeadersFromFunctionGateway() {
Message<?> message = this.functionGateay.apply("testPayload");
Message<?> message = this.functionGateway.apply("testPayload");
assertThat(message.getPayload()).isEqualTo("testPayload");
assertThat(message.getHeaders()).containsKeys("gatewayMethod", "gatewayArgs");
Map<Method, MessagingGatewaySupport> gateways = this.functionGatewayFactoryBean.getGateways();
MessagingGatewaySupport methodGateway = gateways.values().iterator().next();
MessagingTemplate messagingTemplate =
TestUtils.getPropertyValue(methodGateway, "messagingTemplate", MessagingTemplate.class);
assertThat(messagingTemplate.getReceiveTimeout()).isEqualTo(10);
assertThat(messagingTemplate.getSendTimeout()).isEqualTo(20);
}
@Autowired
@@ -164,7 +182,9 @@ public class GatewayDslTests {
return IntegrationFlows.from(MessageFunction.class,
(gateway) -> gateway
.header("gatewayMethod", MethodArgsHolder::getMethod)
.header("gatewayArgs", MethodArgsHolder::getArgs))
.header("gatewayArgs", MethodArgsHolder::getArgs)
.replyTimeout(10)
.requestTimeout(20))
.bridge()
.get();
}