diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java index b73ed7d28b..7db1a40ba3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java @@ -24,9 +24,11 @@ import java.util.Set; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; @@ -40,15 +42,19 @@ import org.springframework.util.StringUtils; * * @author Artem Bilan * @author Gary Russell + * * @since 4.0 */ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar, - ResourceLoaderAware { + ResourceLoaderAware, EnvironmentAware { - private final Map componentRegistrars = new HashMap(); + private final Map componentRegistrars = + new HashMap(); private ResourceLoader resourceLoader; + private Environment environment; + public IntegrationComponentScanRegistrar() { this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true), new MessagingGatewayRegistrar()); } @@ -58,6 +64,11 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe this.resourceLoader = resourceLoader; } + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map componentScan = importingClassMetadata @@ -82,14 +93,16 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName())); } - ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) { + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false, this.environment) { - @Override - protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { - return beanDefinition.getMetadata().isIndependent() - && !beanDefinition.getMetadata().isAnnotation(); - } - }; + @Override + protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { + return beanDefinition.getMetadata().isIndependent() + && !beanDefinition.getMetadata().isAnnotation(); + } + + }; for (TypeFilter typeFilter : this.componentRegistrars.keySet()) { scanner.addIncludeFilter(typeFilter); 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 44ca327bdb..00d16ca2cf 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 @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; @@ -54,6 +55,7 @@ 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.annotation.Profile; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.SimpleAsyncTaskExecutor; @@ -70,6 +72,7 @@ import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.context.IntegrationProperties; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.BridgeHandler; +import org.springframework.integration.handler.DelayHandler; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; @@ -84,6 +87,7 @@ 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.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.concurrent.ListenableFuture; @@ -98,6 +102,7 @@ import org.springframework.util.concurrent.ListenableFutureCallback; @ContextConfiguration(classes = GatewayInterfaceTests.TestConfig.class) @RunWith(SpringJUnit4ClassRunner.class) @DirtiesContext +@ActiveProfiles("gatewayTest") public class GatewayInterfaceTests { private static final String IGNORE_HEADER = "ignoreHeader"; @@ -131,9 +136,13 @@ public class GatewayInterfaceTests { @Autowired private IgnoredHeaderGateway ignoredHeaderGateway; + @Autowired(required = false) + private NotActivatedByProfileGateway notActivatedByProfileGateway; + @Test public void testWithServiceSuperclassAnnotatedMethod() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); final Method fooMethod = Foo.class.getMethod("foo", String.class); final AtomicBoolean called = new AtomicBoolean(); @@ -161,7 +170,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceSuperclassAnnotatedMethodOverridePE() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); final Method fooMethod = Foo.class.getMethod("foo", String.class); final AtomicBoolean called = new AtomicBoolean(); @@ -187,7 +197,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceAnnotatedMethod() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBar", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -199,7 +210,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceSuperclassUnAnnotatedMethod() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); final Method bazMethod = Foo.class.getMethod("baz", String.class); final AtomicBoolean called = new AtomicBoolean(); @@ -225,7 +237,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceUnAnnotatedMethodGlobalHeaderDoesntOverride() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); final Method quxMethod = Bar.class.getMethod("qux", String.class, String.class); final AtomicBoolean called = new AtomicBoolean(); @@ -251,7 +264,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceCastAsSuperclassAnnotatedMethod() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -263,7 +277,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceCastAsSuperclassUnAnnotatedMethod() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -274,8 +289,9 @@ public class GatewayInterfaceTests { } @Test - public void testWithServiceHashcode() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + public void testWithServiceHashcode() { + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -287,7 +303,8 @@ public class GatewayInterfaceTests { @Test public void testWithServiceToString() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -299,12 +316,13 @@ public class GatewayInterfaceTests { @Test public void testWithServiceEquals() throws Exception { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); Bar bar = ac.getBean(Bar.class); - assertTrue(bar.equals(ac.getBean(Bar.class))); + assertSame(bar, ac.getBean(Bar.class)); GatewayProxyFactoryBean fb = new GatewayProxyFactoryBean(Bar.class); DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.registerSingleton("requestChannelBar", channel); @@ -312,14 +330,15 @@ public class GatewayInterfaceTests { bf.registerSingleton("requestChannelFoo", channel); fb.setBeanFactory(bf); fb.afterPropertiesSet(); - assertFalse(bar.equals(fb.getObject())); + assertNotSame(bar, fb.getObject()); verify(handler, times(0)).handleMessage(Mockito.any(Message.class)); ac.close(); } @Test public void testWithServiceGetClass() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); MessageHandler handler = mock(MessageHandler.class); channel.subscribe(handler); @@ -336,7 +355,8 @@ public class GatewayInterfaceTests { @Test public void testWithCustomMapper() { - ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass()); + ConfigurableApplicationContext ac = + new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass()); DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class); final AtomicBoolean called = new AtomicBoolean(); MessageHandler handler = new MessageHandler() { @@ -577,6 +597,7 @@ public class GatewayInterfaceTests { } @MessagingGateway + @Profile("gatewayTest") public interface Int2634Gateway { @Gateway(requestChannel = "gatewayChannel", payloadExpression = "#args[0]") @@ -590,6 +611,15 @@ public class GatewayInterfaceTests { } + @MessagingGateway(defaultRequestChannel = "errorChannel") + @TestMessagingGateway + @Profile("notActiveProfile") + public interface NotActivatedByProfileGateway { + + void send(String payload); + + } + @MessagingGateway(asyncExecutor = "exec") public interface ExecGateway { diff --git a/src/reference/asciidoc/gateway.adoc b/src/reference/asciidoc/gateway.adoc index b93ba2e7e8..fb5a106d03 100644 --- a/src/reference/asciidoc/gateway.adoc +++ b/src/reference/asciidoc/gateway.adoc @@ -283,8 +283,9 @@ The standard `@ComponentScan` infrastructure doesn't deal with interfaces, there to determine `@MessagingGateway` annotation on the interfaces and register `GatewayProxyFactoryBean` s for them. See also <> -NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration` -class. +Along with the `@MessagingGateway` annotation you can mark a service interface with the `@Profile` annotation to avoid the bean creation, if such a profile is not active. + +NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration` class. See <> for more information. [[gateway-calling-no-argument-methods]] @@ -374,14 +375,11 @@ error: if there is an `error-channel` configured, it will be sent there, to the thrown to the caller of gateway. Similarly, if the error flow on the `error-channel` returns an `ErrorMessage` its payload is thrown to the caller. The same applies to any message with a `Throwable` payload. -This can be useful in async situations when when there is a need propagate an `Exception` directly to the caller. -To achieve this you can either return an `Exception` as the `reply` from some service, or simply throw it. -Generally, even with an async flow, the framework will take care of propagating an exception thrown by the -downstream flow back to the gateway. -The https://github.com/spring-projects/spring-integration-samples/tree/master/intermediate/tcp-client-server-multiplex[TCP Client-Server Multiplex] -sample demonstrates both techniques to return the exception to the caller. -It emulates a Socket IO error to the waiting thread using an `aggregator` with `group-timeout` (see <>) -and `MessagingTimeoutException` reply on the discard flow. +This can be useful in asynchronous situations when when you need to propagate an `Exception` directly to the caller. +To do so, you can either return an `Exception` (as the `reply` from some service) or throw it. +Generally, even with an asynchronous flow, the framework takes care of propagating an exception thrown by the downstream flow back to the gateway. +The https://github.com/spring-projects/spring-integration-samples/tree/master/intermediate/tcp-client-server-multiplex[TCP Client-Server Multiplex] sample demonstrates both techniques to return the exception to the caller. +It emulates a socket IO error to the waiting thread by using an `aggregator` with `group-timeout` (see <>) and a `MessagingTimeoutException` reply on the discard flow. [[async-gateway]] @@ -514,6 +512,9 @@ In this scenario, it is expected that the downstream flow will return a `Complet *Usage Scenarios* +In the following scenario, the caller thread returns immediately with a `CompletableFuture`, which is completed when the downstream flow replies to the gateway (with an `Invoice` object). + +==== [source, java] ---- @@ -526,8 +527,8 @@ CompletableFuture order(Order order); ---- -In this scenario, the caller thread returns immediately with a `CompletableFuture` which will be completed -when the downstream flow replies to the gateway (with an `Invoice` object). +In the following scenario, the caller thread returns with a `CompletableFuture` when the downstream flow provides it as the payload of the reply to the gateway. +Some other process must complete the future when the invoice is ready. [source, java] ---- @@ -542,8 +543,7 @@ CompletableFuture order(Order order); async-executor="" /> ---- -In this scenario, the caller thread will return with a CompletableFuture when the downstream flow provides -it as the payload of the reply to the gateway. +In the following scenario, the caller thread returns with a `CompletableFuture` when the downstream flow provides it as the payload of the reply to the gateway. Some other process must complete the future when the invoice is ready. [source, java]