From ecf9554fd0dc64ccd4f70d8ef8dd13eda612ef76 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 18 Jul 2016 19:43:37 -0400 Subject: [PATCH] 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** --- .../config/MessagingGatewayRegistrar.java | 6 +- .../gateway/GatewayProxyFactoryBean.java | 75 ++++++++++++++++--- .../integration/config/ChainParserTests.java | 6 +- .../gateway/GatewayInterfaceTests.java | 43 ++++++++++- 4 files changed, 111 insertions(+), 19 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java index dd39596c25..52e68113a3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/MessagingGatewayRegistrar.java @@ -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); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java index f16278ba26..f37a286b7b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/gateway/GatewayProxyFactoryBean.java @@ -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 headerExpressions = new HashMap(); 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); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java index fbc10f24fe..89f8d0facf 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/ChainParserTests.java @@ -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)); diff --git a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java index 0608879798..94a825fcb8 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java @@ -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); + + } + }