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
This commit is contained in:
Artem Bilan
2018-12-06 15:41:40 -05:00
committed by Gary Russell
parent 2cde493fbf
commit 536b6b1786
3 changed files with 97 additions and 71 deletions

View File

@@ -61,12 +61,14 @@ import org.springframework.util.StringUtils;
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 4.0
*/
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar,
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;
@@ -98,14 +100,16 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
basePackages = Collections.singleton(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();
}
};
if ((boolean) componentScan.get("useDefaultFilters")) { // NOSONAR - never null
for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {

View File

@@ -23,6 +23,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;
@@ -59,6 +60,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;
@@ -77,6 +79,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.lang.Nullable;
@@ -93,6 +96,7 @@ import org.springframework.messaging.support.MessageHeaderAccessor;
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;
@@ -107,6 +111,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";
@@ -153,20 +158,23 @@ 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();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
assertThat(message.getHeaders().get("name"), equalTo("foo"));
assertThat(message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway." +
"GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat(message.getHeaders().get("object"), equalTo(fooMethod));
assertThat(message.getPayload(), equalTo("hello"));
assertThat(new MessageHeaderAccessor(message).getErrorChannel(), equalTo("errorChannel"));
called.set(true);
};
@@ -181,18 +189,18 @@ 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();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("foo"));
assertThat(message.getHeaders().get("name"), equalTo("foo"));
assertThat(message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway." +
"GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat(message.getHeaders().get("object"), equalTo(fooMethod));
assertThat(message.getPayload(), equalTo("foo"));
called.set(true);
};
channel.subscribe(handler);
@@ -204,8 +212,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);
@@ -217,15 +225,16 @@ 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();
MessageHandler handler = message -> {
assertThat(message.getHeaders().get("name"), equalTo("overrideGlobal"));
assertThat(message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.baz(java.lang.String)"));
equalTo("public abstract void org.springframework.integration.gateway." +
"GatewayInterfaceTests$Foo.baz(java.lang.String)"));
assertThat(message.getHeaders().get("object"), equalTo(bazMethod));
assertThat(message.getPayload(), equalTo("hello"));
called.set(true);
@@ -239,15 +248,16 @@ 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();
MessageHandler handler = message -> {
assertThat(message.getHeaders().get("name"), equalTo("arg1"));
assertThat(message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Bar.qux(java.lang.String,java.lang.String)"));
equalTo("public abstract void org.springframework.integration.gateway." +
"GatewayInterfaceTests$Bar.qux(java.lang.String,java.lang.String)"));
assertThat(message.getHeaders().get("object"), equalTo(quxMethod));
assertThat(message.getPayload(), equalTo("hello"));
called.set(true);
@@ -261,8 +271,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);
@@ -274,8 +284,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);
@@ -287,8 +297,8 @@ public class GatewayInterfaceTests {
@Test
public void testWithServiceHashcode() {
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);
@@ -300,8 +310,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);
@@ -313,13 +323,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);
@@ -327,15 +337,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);
@@ -352,12 +362,12 @@ 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 = message -> {
assertThat((String) message.getPayload(), equalTo("fizbuz"));
assertThat(message.getPayload(), equalTo("fizbuz"));
called.set(true);
};
channel.subscribe(handler);
@@ -369,8 +379,13 @@ public class GatewayInterfaceTests {
@Test
public void testLateReply() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml",
this.getClass());
ConfigurableApplicationContext ac =
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
DelayHandler delayHandler = ac.getBean(DelayHandler.class);
delayHandler.setMaxAttempts(2);
delayHandler.setRetryDelay(10);
Bar baz = ac.getBean(Bar.class);
String reply = baz.lateReply("hello", 1000, 0);
assertNull(reply);
@@ -448,14 +463,15 @@ public class GatewayInterfaceTests {
@SuppressWarnings("rawtypes")
public void testAnnotationGatewayProxyFactoryBean() {
assertNotNull(this.gatewayByAnnotationGPFB);
assertNull(this.notActivatedByProfileGateway);
assertSame(this.exec, this.annotationGatewayProxyFactoryBean.getAsyncExecutor());
assertEquals(1111L, TestUtils
.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultRequestTimeout", Expression.class)
.getValue());
assertEquals(222L, TestUtils
.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultReplyTimeout", Expression.class)
.getValue());
assertEquals(1111L,
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean,
"defaultRequestTimeout", Expression.class).getValue());
assertEquals(222L,
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean,
"defaultReplyTimeout", Expression.class).getValue());
Collection<MessagingGatewaySupport> messagingGateways =
this.annotationGatewayProxyFactoryBean.getGateways().values();
@@ -563,7 +579,7 @@ public class GatewayInterfaceTests {
public static class BazMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder object, @Nullable Map<String, Object> headers) throws Exception {
public Message<?> toMessage(MethodArgsHolder object, @Nullable Map<String, Object> headers) {
return MessageBuilder.withPayload("fizbuz")
.copyHeadersIfAbsent(headers)
.build();
@@ -652,6 +668,7 @@ public class GatewayInterfaceTests {
@MessagingGateway
@TestMessagingGateway
@Profile("gatewayTest")
public interface Int2634Gateway {
@Gateway(requestChannel = "gatewayChannel", payloadExpression = "#args[0]")
@@ -665,6 +682,15 @@ public class GatewayInterfaceTests {
}
@MessagingGateway(defaultRequestChannel = "errorChannel")
@TestMessagingGateway
@Profile("notActiveProfile")
public interface NotActivatedByProfileGateway {
void send(String payload);
}
@MessagingGateway(asyncExecutor = "exec")
@TestMessagingGateway
public interface ExecGateway {

View File

@@ -188,7 +188,7 @@ Consider the following example:
----
public String send1(Object thing1, Map thing2);
public String send2(Map thing1, Map thing2);=
public String send2(Map thing1, Map thing2);
----
====
@@ -313,8 +313,9 @@ The standard `@ComponentScan` infrastructure does not deal with interfaces.
Consequently, we introduced the custom `@IntegrationComponentScan` logic to fine the `@MessagingGateway` annotation on the interfaces and register `GatewayProxyFactoryBean` instances 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]]
@@ -460,11 +461,9 @@ Similarly, if the error flow on the `error-channel` returns an `ErrorMessage`, i
The same applies to any message with a `Throwable` payload.
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.
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.
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.
[[gateway-timeouts]]
==== Gateway Timeouts
@@ -642,8 +641,7 @@ In this scenario, the downstream flow is expected to return a `CompletableFuture
====== 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).
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]
@@ -657,8 +655,7 @@ CompletableFuture<Invoice> order(Order order);
----
====
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.
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.
====
@@ -674,8 +671,7 @@ CompletableFuture<Invoice> order(Order order);
----
====
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.
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.
If `DEBUG` logging is enabled, a log entry is emitted, indicating that the async executor cannot be used for this scenario.