Skip to content

Commit 2a79b07

Browse files
artembilangaryrussell
authored andcommitted
GH-3266: Don't override props in AnnGWProxyFB
Fixes #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**
1 parent 000632e commit 2a79b07

File tree

3 files changed

+136
-38
lines changed

3 files changed

+136
-38
lines changed

spring-integration-core/src/main/java/org/springframework/integration/gateway/AnnotationGatewayProxyFactoryBean.java

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2019 the original author or authors.
2+
* Copyright 2017-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -72,23 +72,38 @@ public AnnotationGatewayProxyFactoryBean(Class<?> serviceInterface) {
7272
protected void onInit() {
7373
ConfigurableListableBeanFactory beanFactory = (ConfigurableListableBeanFactory) getBeanFactory();
7474

75-
populateGatewayMethodMetadata();
75+
if (getGlobalMethodMetadata() == null) {
76+
populateGatewayMethodMetadata();
77+
}
78+
79+
String defaultRequestTimeout = resolveAttribute("defaultRequestTimeout");
80+
String defaultReplyTimeout = resolveAttribute("defaultReplyTimeout");
7681

7782
JavaUtils.INSTANCE
78-
.acceptIfHasText(resolveAttribute("defaultRequestChannel"), this::setDefaultRequestChannelName)
79-
.acceptIfHasText(resolveAttribute("defaultReplyChannel"), this::setDefaultReplyChannelName)
80-
.acceptIfHasText(resolveAttribute("errorChannel"), this::setErrorChannelName)
81-
.acceptIfHasText(resolveAttribute("defaultRequestTimeout"),
83+
.acceptIfCondition(getDefaultRequestChannel() == null && getDefaultRequestChannelName() == null,
84+
resolveAttribute("defaultRequestChannel"),
85+
this::setDefaultRequestChannelName)
86+
.acceptIfCondition(getDefaultReplyChannel() == null && getDefaultReplyChannelName() == null,
87+
resolveAttribute("defaultReplyChannel"),
88+
this::setDefaultReplyChannelName)
89+
.acceptIfCondition(getErrorChannel() == null && getErrorChannelName() == null,
90+
resolveAttribute("errorChannel"),
91+
this::setErrorChannelName)
92+
.acceptIfCondition(getDefaultRequestTimeout() == null && StringUtils.hasText(defaultRequestTimeout),
93+
defaultRequestTimeout,
8294
value -> setDefaultRequestTimeout(Long.parseLong(value)))
83-
.acceptIfHasText(resolveAttribute("defaultReplyTimeout"),
95+
.acceptIfCondition(getDefaultReplyTimeout() == null && StringUtils.hasText(defaultReplyTimeout),
96+
defaultReplyTimeout,
8497
value -> setDefaultReplyTimeout(Long.parseLong(value)));
8598

86-
String asyncExecutor = beanFactory.resolveEmbeddedValue(this.gatewayAttributes.getString("asyncExecutor"));
87-
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
88-
setAsyncExecutor(null);
89-
}
90-
else if (StringUtils.hasText(asyncExecutor)) {
91-
setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
99+
if (!isAsyncExecutorExplicitlySet()) {
100+
String asyncExecutor = resolveAttribute("asyncExecutor");
101+
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
102+
setAsyncExecutor(null);
103+
}
104+
else if (StringUtils.hasText(asyncExecutor)) {
105+
setAsyncExecutor(beanFactory.getBean(asyncExecutor, Executor.class));
106+
}
92107
}
93108

94109
super.onInit();
@@ -114,7 +129,7 @@ private void populateGatewayMethodMetadata() {
114129
"'defaultHeaders' are not allowed when a 'mapper' is provided");
115130

116131
JavaUtils.INSTANCE
117-
.acceptIfHasText(mapper,
132+
.acceptIfCondition(hasMapper && getMapper() == null, mapper,
118133
value -> setMapper(beanFactory.getBean(value, MethodArgsMessageMapper.class)));
119134

120135
if (hasDefaultHeaders || hasDefaultPayloadExpression) {
@@ -124,23 +139,26 @@ private void populateGatewayMethodMetadata() {
124139
gatewayMethodMetadata.setPayloadExpression(EXPRESSION_PARSER.parseExpression(defaultPayloadExpression));
125140
}
126141

127-
Map<String, Expression> headerExpressions = Arrays.stream(defaultHeaders)
128-
.collect(Collectors.toMap(
129-
header -> beanFactory.resolveEmbeddedValue((String) header.get("name")),
130-
header -> {
131-
String headerValue = beanFactory.resolveEmbeddedValue((String) header.get("value"));
132-
boolean hasValue = StringUtils.hasText(headerValue);
133-
134-
String headerExpression =
135-
beanFactory.resolveEmbeddedValue((String) header.get("expression"));
136-
137-
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
138-
"exactly one of 'value' or 'expression' is required on a gateway's header.");
139-
140-
return hasValue ?
141-
new LiteralExpression(headerValue) :
142-
EXPRESSION_PARSER.parseExpression(headerExpression);
143-
}));
142+
Map<String, Expression> headerExpressions =
143+
Arrays.stream(defaultHeaders)
144+
.collect(Collectors.toMap(
145+
header -> beanFactory.resolveEmbeddedValue((String) header.get("name")),
146+
header -> {
147+
String headerValue =
148+
beanFactory.resolveEmbeddedValue((String) header.get("value"));
149+
boolean hasValue = StringUtils.hasText(headerValue);
150+
151+
String headerExpression =
152+
beanFactory.resolveEmbeddedValue((String) header.get("expression"));
153+
154+
Assert.state(!(hasValue == StringUtils.hasText(headerExpression)),
155+
"exactly one of 'value' or 'expression' is required on a gateway's " +
156+
"header.");
157+
158+
return hasValue ?
159+
new LiteralExpression(headerValue) :
160+
EXPRESSION_PARSER.parseExpression(headerExpression);
161+
}));
144162

145163
gatewayMethodMetadata.setHeaderExpressions(headerExpressions);
146164

spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
137137

138138
private AsyncTaskExecutor asyncExecutor = new SimpleAsyncTaskExecutor();
139139

140+
private boolean asyncExecutorExplicitlySet;
141+
140142
private Class<?> asyncSubmitType;
141143

142144
private Class<?> asyncSubmitListenableType;
@@ -198,6 +200,16 @@ public void setDefaultRequestChannelName(String defaultRequestChannelName) {
198200
this.defaultRequestChannelName = defaultRequestChannelName;
199201
}
200202

203+
@Nullable
204+
protected MessageChannel getDefaultRequestChannel() {
205+
return this.defaultRequestChannel;
206+
}
207+
208+
@Nullable
209+
protected String getDefaultRequestChannelName() {
210+
return this.defaultRequestChannelName;
211+
}
212+
201213
/**
202214
* Set the default reply channel. If no default reply channel is provided,
203215
* and no reply channel is configured with annotations, an anonymous,
@@ -221,6 +233,16 @@ public void setDefaultReplyChannelName(String defaultReplyChannelName) {
221233
this.defaultReplyChannelName = defaultReplyChannelName;
222234
}
223235

236+
@Nullable
237+
protected MessageChannel getDefaultReplyChannel() {
238+
return this.defaultReplyChannel;
239+
}
240+
241+
@Nullable
242+
protected String getDefaultReplyChannelName() {
243+
return this.defaultReplyChannelName;
244+
}
245+
224246
/**
225247
* Set the error channel. If no error channel is provided, this gateway will
226248
* propagate Exceptions to the caller. To completely suppress Exceptions, provide
@@ -242,6 +264,16 @@ public void setErrorChannelName(String errorChannelName) {
242264
this.errorChannelName = errorChannelName;
243265
}
244266

267+
@Nullable
268+
protected MessageChannel getErrorChannel() {
269+
return this.errorChannel;
270+
}
271+
272+
@Nullable
273+
protected String getErrorChannelName() {
274+
return this.errorChannelName;
275+
}
276+
245277
/**
246278
* Set the default timeout value for sending request messages. If not explicitly
247279
* configured with an annotation, or on a method element, this value will be used.
@@ -275,6 +307,11 @@ public void setDefaultRequestTimeoutExpressionString(String defaultRequestTimeou
275307
}
276308
}
277309

310+
@Nullable
311+
protected Expression getDefaultRequestTimeout() {
312+
return this.defaultRequestTimeout;
313+
}
314+
278315
/**
279316
* Set the default timeout value for receiving reply messages. If not explicitly
280317
* configured with an annotation, or on a method element, this value will be used.
@@ -308,6 +345,11 @@ public void setDefaultReplyTimeoutExpressionString(String defaultReplyTimeout) {
308345
}
309346
}
310347

348+
@Nullable
349+
protected Expression getDefaultReplyTimeout() {
350+
return this.defaultReplyTimeout;
351+
}
352+
311353
@Override
312354
public void setShouldTrack(boolean shouldTrack) {
313355
this.shouldTrack = shouldTrack;
@@ -326,12 +368,15 @@ public void setShouldTrack(boolean shouldTrack) {
326368
* @param executor The executor.
327369
*/
328370
public void setAsyncExecutor(@Nullable Executor executor) {
329-
if (executor == null && logger.isInfoEnabled()) {
371+
if (executor == null) {
330372
logger.info("A null executor disables the async gateway; " +
331373
"methods returning Future<?> will run on the calling thread");
332374
}
333-
this.asyncExecutor = (executor instanceof AsyncTaskExecutor || executor == null) ? (AsyncTaskExecutor) executor
334-
: new TaskExecutorAdapter(executor);
375+
this.asyncExecutor =
376+
(executor instanceof AsyncTaskExecutor || executor == null)
377+
? (AsyncTaskExecutor) executor
378+
: new TaskExecutorAdapter(executor);
379+
this.asyncExecutorExplicitlySet = true;
335380
}
336381

337382
public void setTypeConverter(TypeConverter typeConverter) {
@@ -347,6 +392,11 @@ public void setGlobalMethodMetadata(GatewayMethodMetadata globalMethodMetadata)
347392
this.globalMethodMetadata = globalMethodMetadata;
348393
}
349394

395+
@Nullable
396+
protected GatewayMethodMetadata getGlobalMethodMetadata() {
397+
return this.globalMethodMetadata;
398+
}
399+
350400
@Override
351401
public void setBeanClassLoader(ClassLoader beanClassLoader) {
352402
this.beanClassLoader = beanClassLoader;
@@ -361,10 +411,20 @@ public final void setMapper(MethodArgsMessageMapper mapper) {
361411
this.argsMapper = mapper;
362412
}
363413

414+
@Nullable
415+
protected MethodArgsMessageMapper getMapper() {
416+
return this.argsMapper;
417+
}
418+
419+
@Nullable
364420
protected AsyncTaskExecutor getAsyncExecutor() {
365421
return this.asyncExecutor;
366422
}
367423

424+
protected boolean isAsyncExecutorExplicitlySet() {
425+
return this.asyncExecutorExplicitlySet;
426+
}
427+
368428
/**
369429
* Return the Map of {@link Method} to {@link MessagingGatewaySupport}
370430
* generated by this factory bean.

spring-integration-core/src/test/java/org/springframework/integration/dsl/gateway/GatewayDslTests.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 the original author or authors.
2+
* Copyright 2019-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2121

2222
import java.lang.reflect.Method;
23+
import java.util.Map;
2324
import java.util.function.Function;
2425

2526
import org.junit.jupiter.api.Test;
@@ -32,11 +33,15 @@
3233
import org.springframework.integration.MessageRejectedException;
3334
import org.springframework.integration.channel.QueueChannel;
3435
import org.springframework.integration.config.EnableIntegration;
36+
import org.springframework.integration.core.MessagingTemplate;
3537
import org.springframework.integration.dsl.IntegrationFlow;
3638
import org.springframework.integration.dsl.IntegrationFlows;
3739
import org.springframework.integration.dsl.MessageChannels;
40+
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
41+
import org.springframework.integration.gateway.MessagingGatewaySupport;
3842
import org.springframework.integration.gateway.MethodArgsHolder;
3943
import org.springframework.integration.support.MessageBuilder;
44+
import org.springframework.integration.test.util.TestUtils;
4045
import org.springframework.messaging.Message;
4146
import org.springframework.messaging.MessageChannel;
4247
import org.springframework.messaging.PollableChannel;
@@ -102,13 +107,26 @@ void testNestedGatewayErrorPropagation() {
102107
}
103108

104109
@Autowired
105-
private Function<Object, Message<?>> functionGateay;
110+
private Function<Object, Message<?>> functionGateway;
111+
112+
@Autowired
113+
@Qualifier("&functionGateway.gateway")
114+
private GatewayProxyFactoryBean functionGatewayFactoryBean;
106115

107116
@Test
108117
void testHeadersFromFunctionGateway() {
109-
Message<?> message = this.functionGateay.apply("testPayload");
118+
Message<?> message = this.functionGateway.apply("testPayload");
110119
assertThat(message.getPayload()).isEqualTo("testPayload");
111120
assertThat(message.getHeaders()).containsKeys("gatewayMethod", "gatewayArgs");
121+
122+
Map<Method, MessagingGatewaySupport> gateways = this.functionGatewayFactoryBean.getGateways();
123+
124+
MessagingGatewaySupport methodGateway = gateways.values().iterator().next();
125+
MessagingTemplate messagingTemplate =
126+
TestUtils.getPropertyValue(methodGateway, "messagingTemplate", MessagingTemplate.class);
127+
128+
assertThat(messagingTemplate.getReceiveTimeout()).isEqualTo(10);
129+
assertThat(messagingTemplate.getSendTimeout()).isEqualTo(20);
112130
}
113131

114132
@Autowired
@@ -164,7 +182,9 @@ public IntegrationFlow functionGateway() {
164182
return IntegrationFlows.from(MessageFunction.class,
165183
(gateway) -> gateway
166184
.header("gatewayMethod", MethodArgsHolder::getMethod)
167-
.header("gatewayArgs", MethodArgsHolder::getArgs))
185+
.header("gatewayArgs", MethodArgsHolder::getArgs)
186+
.replyTimeout(10)
187+
.requestTimeout(20))
168188
.bridge()
169189
.get();
170190
}

0 commit comments

Comments
 (0)