INT-4074 Late channel resolution for GatewayProxy

JIRA: https://jira.spring.io/browse/INT-4074

If the `@ServiceActivator` component is processed first the application will start.
However, if another component that relies on the channel creation goes first it'll fail as the bean for the channel does not exist.

* Add `name` setters for all channels in the `GatewayProxyFactoryBean` to allow late channel resolution in the target `MethodInvocationGateway`
* Change `MessagingGatewayRegistrar` to populate channel properties as names not bean references

**Cherry-pick to 4.2.x**
This commit is contained in:
Artem Bilan
2016-07-18 19:43:37 -04:00
committed by Gary Russell
parent 35a5a4a4ab
commit ecf9554fd0
4 changed files with 111 additions and 19 deletions

View File

@@ -142,13 +142,13 @@ public class MessagingGatewayRegistrar implements ImportBeanDefinitionRegistrar,
if (StringUtils.hasText(defaultRequestChannel)) {
gatewayProxyBuilder.addPropertyReference("defaultRequestChannel", defaultRequestChannel);
gatewayProxyBuilder.addPropertyValue("defaultRequestChannelName", defaultRequestChannel);
}
if (StringUtils.hasText(defaultReplyChannel)) {
gatewayProxyBuilder.addPropertyReference("defaultReplyChannel", defaultReplyChannel);
gatewayProxyBuilder.addPropertyValue("defaultReplyChannelName", defaultReplyChannel);
}
if (StringUtils.hasText(errorChannel)) {
gatewayProxyBuilder.addPropertyReference("errorChannel", errorChannel);
gatewayProxyBuilder.addPropertyValue("errorChannelName", errorChannel);
}
if (asyncExecutor == null || AnnotationConstants.NULL.equals(asyncExecutor)) {
gatewayProxyBuilder.addPropertyValue("asyncExecutor", null);

View File

@@ -93,10 +93,16 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
private volatile MessageChannel defaultRequestChannel;
private volatile String defaultRequestChannelName;
private volatile MessageChannel defaultReplyChannel;
private volatile String defaultReplyChannelName;
private volatile MessageChannel errorChannel;
private volatile String errorChannelName;
private volatile Long defaultRequestTimeout;
private volatile Long defaultReplyTimeout;
@@ -159,7 +165,6 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
/**
* Set the default request channel.
*
* @param defaultRequestChannel the channel to which request messages will
* be sent if no request channel has been configured with an annotation.
*/
@@ -167,11 +172,20 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.defaultRequestChannel = defaultRequestChannel;
}
/**
* Set the default request channel bean name.
* @param defaultRequestChannelName the channel name to which request messages will
* be sent if no request channel has been configured with an annotation.
* @since 4.2.9
*/
public void setDefaultRequestChannelName(String defaultRequestChannelName) {
this.defaultRequestChannelName = defaultRequestChannelName;
}
/**
* Set the default reply channel. If no default reply channel is provided,
* and no reply channel is configured with annotations, an anonymous,
* temporary channel will be used for handling replies.
*
* @param defaultReplyChannel the channel from which reply messages will be
* received if no reply channel has been configured with an annotation
*/
@@ -179,17 +193,39 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
this.defaultReplyChannel = defaultReplyChannel;
}
/**
* Set the default reply channel bean name. If no default reply channel is provided,
* and no reply channel is configured with annotations, an anonymous,
* temporary channel will be used for handling replies.
* @param defaultReplyChannelName the channel name from which reply messages will be
* received if no reply channel has been configured with an annotation
* @since 4.2.9
*/
public void setDefaultReplyChannelName(String defaultReplyChannelName) {
this.defaultReplyChannelName = defaultReplyChannelName;
}
/**
* Set the error channel. If no error channel is provided, this gateway will
* propagate Exceptions to the caller. To completely suppress Exceptions, provide
* a reference to the "nullChannel" here.
*
* @param errorChannel The error channel.
*/
public void setErrorChannel(MessageChannel errorChannel) {
this.errorChannel = errorChannel;
}
/**
* Set the error channel name. If no error channel is provided, this gateway will
* propagate Exceptions to the caller. To completely suppress Exceptions, provide
* a reference to the "nullChannel" here.
* @param errorChannelName The error channel bean name.
* @since 4.2.9
*/
public void setErrorChannelName(String errorChannelName) {
this.errorChannelName = errorChannelName;
}
/**
* Set the default timeout value for sending request messages. If not
* explicitly configured with an annotation, this value will be used.
@@ -456,13 +492,12 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
private MethodInvocationGateway createGatewayForMethod(Method method) {
Gateway gatewayAnnotation = method.getAnnotation(Gateway.class);
MessageChannel requestChannel = this.defaultRequestChannel;
String requestChannelName = null;
MessageChannel replyChannel = this.defaultReplyChannel;
String replyChannelName = null;
Long requestTimeout = this.defaultRequestTimeout;
Long replyTimeout = this.defaultReplyTimeout;
String payloadExpression = this.globalMethodMetadata != null ? this.globalMethodMetadata.getPayloadExpression()
String payloadExpression = this.globalMethodMetadata != null
? this.globalMethodMetadata.getPayloadExpression()
: null;
Map<String, Expression> headerExpressions = new HashMap<String, Expression>();
if (gatewayAnnotation != null) {
@@ -492,7 +527,7 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
String name = gatewayHeader.name();
boolean hasValue = StringUtils.hasText(value);
if (!(hasValue ^ StringUtils.hasText(expression))) {
if (hasValue == StringUtils.hasText(expression)) {
throw new BeanDefinitionStoreException("exactly one of 'value' or 'expression' " +
"is required on a gateway's header.");
}
@@ -533,23 +568,39 @@ public class GatewayProxyFactoryBean extends AbstractEndpoint
}
messageMapper.setBeanFactory(this.getBeanFactory());
MethodInvocationGateway gateway = new MethodInvocationGateway(messageMapper);
gateway.setErrorChannel(this.errorChannel);
if (this.errorChannel != null) {
gateway.setErrorChannel(this.errorChannel);
}
else if (StringUtils.hasText(this.errorChannelName)) {
gateway.setErrorChannelName(this.errorChannelName);
}
if (this.getTaskScheduler() != null) {
gateway.setTaskScheduler(this.getTaskScheduler());
}
gateway.setBeanName(this.getComponentName());
if (StringUtils.hasText(requestChannelName)) {
gateway.setRequestChannelName(requestChannelName);
}
else {
gateway.setRequestChannel(requestChannel);
else if (StringUtils.hasText(this.defaultRequestChannelName)) {
gateway.setRequestChannelName(this.defaultRequestChannelName);
}
else {
gateway.setRequestChannel(this.defaultRequestChannel);
}
if (StringUtils.hasText(replyChannelName)) {
gateway.setReplyChannelName(replyChannelName);
}
else {
gateway.setReplyChannel(replyChannel);
else if (StringUtils.hasText(this.defaultReplyChannelName)) {
gateway.setReplyChannelName(this.defaultReplyChannelName);
}
else {
gateway.setReplyChannel(this.defaultReplyChannel);
}
if (requestTimeout == null) {
gateway.setRequestTimeout(-1);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@@ -394,8 +394,8 @@ public class ChainParserTests {
//INT-3117
GatewayProxyFactoryBean gatewayProxyFactoryBean = this.beanFactory.getBean("&subComponentsIdSupport1$child.gatewayWithinChain.handler",
GatewayProxyFactoryBean.class);
assertSame(this.strings, TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultRequestChannel", MessageChannel.class));
assertSame(this.numbers, TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultReplyChannel", MessageChannel.class));
assertEquals("strings", TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultRequestChannelName"));
assertEquals("numbers", TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultReplyChannelName"));
assertEquals(new Long(1000), TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultRequestTimeout", Long.class));
assertEquals(new Long(100), TestUtils.getPropertyValue(gatewayProxyFactoryBean, "defaultReplyTimeout", Long.class));

View File

@@ -49,7 +49,9 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@@ -58,8 +60,10 @@ import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
@@ -71,6 +75,7 @@ import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -81,6 +86,7 @@ import org.springframework.util.concurrent.ListenableFutureCallback;
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
*/
@ContextConfiguration(classes = GatewayInterfaceTests.TestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@@ -107,6 +113,9 @@ public class GatewayInterfaceTests {
@Autowired
private SimpleAsyncTaskExecutor exec;
@Autowired
private AutoCreateChannelService autoCreateChannelService;
@Test
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
@@ -394,6 +403,10 @@ public class GatewayInterfaceTests {
assertThat(result2.get().getName(), startsWith("exec-"));
}
@Test
public void testAutoCreateChannelGateway() {
assertEquals("foo", this.autoCreateChannelService.service("foo"));
}
public interface Foo {
@@ -431,9 +444,23 @@ public class GatewayInterfaceTests {
}
@Component
public static class AutoCreateChannelService {
@Autowired
private AutoCreateChannelGateway gateway;
public String service(String request) {
return this.gateway.foo(request);
}
}
@Configuration
@EnableIntegration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = AutoCreateChannelService.class))
@IntegrationComponentScan
@EnableIntegration
public static class TestConfig {
@Bean
@@ -474,6 +501,12 @@ public class GatewayInterfaceTests {
return simpleAsyncTaskExecutor;
}
@Bean
@ServiceActivator(inputChannel = "autoCreateChannel")
public MessageHandler autoCreateServiceActivator() {
return new BridgeHandler();
}
}
@MessagingGateway
@@ -509,4 +542,12 @@ public class GatewayInterfaceTests {
}
@MessagingGateway(defaultRequestChannel = "autoCreateChannel")
public interface AutoCreateChannelGateway {
String foo(String payload);
}
}