INT-4565: Fix IntComponentScan for profiles (#2652)

* INT-4565: Fix IntComponentScan for profiles

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

* Propagate an `Environment` to the internal `ClassPathScanningCandidateComponentProvider`
in the `IntegrationComponentScanRegistrar` for proper profiles activation
* Ensure the logic works in the `GatewayInterfaceTests`
* Some polishing and performance improvement for the `GatewayInterfaceTests`
* Add a note about `@Profile` in the `gateway.adoc`
* Polishing for the `gateway.adoc`

**Cherry-pick to 5.0.x & 4.3.x**

* * Add not activated by profile gateway interface into the `GatewayInterfaceTests`
* More `GatewayInterfaceTests` polishing
* Fix typo in the `gateway.adoc`

* Fix Checkstyle violation

# Conflicts:
#	spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java
#	src/reference/asciidoc/gateway.adoc

# Conflicts:
#	spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java
This commit is contained in:
Artem Bilan
2018-12-06 15:41:40 -05:00
parent e6140a66fd
commit 7e79477b48
3 changed files with 81 additions and 38 deletions

View File

@@ -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<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars = new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();
private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars =
new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();
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<String, Object> 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);

View File

@@ -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 {

View File

@@ -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 <<annotations>>
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 <<configuration-enable-integration>> 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 <<agg-and-group-to>>)
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 <<agg-and-group-to>>) 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<Invoice>`, which is completed when the downstream flow replies to the gateway (with an `Invoice` object).
====
[source, java]
----
@@ -526,8 +527,8 @@ CompletableFuture<Invoice> order(Order order);
<int:gateway service-interface="foo.Service" default-request-channel="orders" />
----
In this scenario, the caller thread returns immediately with a `CompletableFuture<Invoice>` 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<Invoice>` 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<Invoice> order(Order order);
async-executor="" />
----
In this scenario, the caller thread will return with a CompletableFuture<Invoice> 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<Invoice>` 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]