Skip to content

Commit 7e79477

Browse files
committed
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
1 parent e6140a6 commit 7e79477

File tree

3 files changed

+81
-38
lines changed

3 files changed

+81
-38
lines changed

spring-integration-core/src/main/java/org/springframework/integration/config/IntegrationComponentScanRegistrar.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
2525
import org.springframework.beans.factory.config.BeanDefinition;
2626
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
27+
import org.springframework.context.EnvironmentAware;
2728
import org.springframework.context.ResourceLoaderAware;
2829
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
2930
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
31+
import org.springframework.core.env.Environment;
3032
import org.springframework.core.io.ResourceLoader;
3133
import org.springframework.core.type.AnnotationMetadata;
3234
import org.springframework.core.type.filter.AnnotationTypeFilter;
@@ -40,15 +42,19 @@
4042
*
4143
* @author Artem Bilan
4244
* @author Gary Russell
45+
*
4346
* @since 4.0
4447
*/
4548
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar,
46-
ResourceLoaderAware {
49+
ResourceLoaderAware, EnvironmentAware {
4750

48-
private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars = new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();
51+
private final Map<TypeFilter, ImportBeanDefinitionRegistrar> componentRegistrars =
52+
new HashMap<TypeFilter, ImportBeanDefinitionRegistrar>();
4953

5054
private ResourceLoader resourceLoader;
5155

56+
private Environment environment;
57+
5258
public IntegrationComponentScanRegistrar() {
5359
this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true), new MessagingGatewayRegistrar());
5460
}
@@ -58,6 +64,11 @@ public void setResourceLoader(ResourceLoader resourceLoader) {
5864
this.resourceLoader = resourceLoader;
5965
}
6066

67+
@Override
68+
public void setEnvironment(Environment environment) {
69+
this.environment = environment;
70+
}
71+
6172
@Override
6273
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
6374
Map<String, Object> componentScan = importingClassMetadata
@@ -82,14 +93,16 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B
8293
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
8394
}
8495

85-
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false) {
96+
ClassPathScanningCandidateComponentProvider scanner =
97+
new ClassPathScanningCandidateComponentProvider(false, this.environment) {
8698

87-
@Override
88-
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
89-
return beanDefinition.getMetadata().isIndependent()
90-
&& !beanDefinition.getMetadata().isAnnotation();
91-
}
92-
};
99+
@Override
100+
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
101+
return beanDefinition.getMetadata().isIndependent()
102+
&& !beanDefinition.getMetadata().isAnnotation();
103+
}
104+
105+
};
93106

94107
for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {
95108
scanner.addIncludeFilter(typeFilter);

spring-integration-core/src/test/java/org/springframework/integration/gateway/GatewayInterfaceTests.java

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.Assert.assertFalse;
2323
import static org.junit.Assert.assertNotEquals;
2424
import static org.junit.Assert.assertNotNull;
25+
import static org.junit.Assert.assertNotSame;
2526
import static org.junit.Assert.assertNull;
2627
import static org.junit.Assert.assertSame;
2728
import static org.junit.Assert.assertThat;
@@ -54,6 +55,7 @@
5455
import org.springframework.context.annotation.ComponentScan;
5556
import org.springframework.context.annotation.Configuration;
5657
import org.springframework.context.annotation.FilterType;
58+
import org.springframework.context.annotation.Profile;
5759
import org.springframework.context.support.ClassPathXmlApplicationContext;
5860
import org.springframework.core.task.AsyncTaskExecutor;
5961
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@@ -70,6 +72,7 @@
7072
import org.springframework.integration.context.IntegrationProperties;
7173
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
7274
import org.springframework.integration.handler.BridgeHandler;
75+
import org.springframework.integration.handler.DelayHandler;
7376
import org.springframework.integration.support.MessageBuilder;
7477
import org.springframework.integration.test.util.TestUtils;
7578
import org.springframework.messaging.Message;
@@ -84,6 +87,7 @@
8487
import org.springframework.scheduling.annotation.AsyncResult;
8588
import org.springframework.stereotype.Component;
8689
import org.springframework.test.annotation.DirtiesContext;
90+
import org.springframework.test.context.ActiveProfiles;
8791
import org.springframework.test.context.ContextConfiguration;
8892
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
8993
import org.springframework.util.concurrent.ListenableFuture;
@@ -98,6 +102,7 @@
98102
@ContextConfiguration(classes = GatewayInterfaceTests.TestConfig.class)
99103
@RunWith(SpringJUnit4ClassRunner.class)
100104
@DirtiesContext
105+
@ActiveProfiles("gatewayTest")
101106
public class GatewayInterfaceTests {
102107

103108
private static final String IGNORE_HEADER = "ignoreHeader";
@@ -131,9 +136,13 @@ public class GatewayInterfaceTests {
131136
@Autowired
132137
private IgnoredHeaderGateway ignoredHeaderGateway;
133138

139+
@Autowired(required = false)
140+
private NotActivatedByProfileGateway notActivatedByProfileGateway;
141+
134142
@Test
135143
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
136-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
144+
ConfigurableApplicationContext ac =
145+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
137146
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
138147
final Method fooMethod = Foo.class.getMethod("foo", String.class);
139148
final AtomicBoolean called = new AtomicBoolean();
@@ -161,7 +170,8 @@ public void handleMessage(Message<?> message) throws MessagingException {
161170

162171
@Test
163172
public void testWithServiceSuperclassAnnotatedMethodOverridePE() throws Exception {
164-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", this.getClass());
173+
ConfigurableApplicationContext ac =
174+
new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", getClass());
165175
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
166176
final Method fooMethod = Foo.class.getMethod("foo", String.class);
167177
final AtomicBoolean called = new AtomicBoolean();
@@ -187,7 +197,8 @@ public void handleMessage(Message<?> message) throws MessagingException {
187197

188198
@Test
189199
public void testWithServiceAnnotatedMethod() {
190-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
200+
ConfigurableApplicationContext ac =
201+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
191202
DirectChannel channel = ac.getBean("requestChannelBar", DirectChannel.class);
192203
MessageHandler handler = mock(MessageHandler.class);
193204
channel.subscribe(handler);
@@ -199,7 +210,8 @@ public void testWithServiceAnnotatedMethod() {
199210

200211
@Test
201212
public void testWithServiceSuperclassUnAnnotatedMethod() throws Exception {
202-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
213+
ConfigurableApplicationContext ac =
214+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
203215
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
204216
final Method bazMethod = Foo.class.getMethod("baz", String.class);
205217
final AtomicBoolean called = new AtomicBoolean();
@@ -225,7 +237,8 @@ public void handleMessage(Message<?> message) throws MessagingException {
225237

226238
@Test
227239
public void testWithServiceUnAnnotatedMethodGlobalHeaderDoesntOverride() throws Exception {
228-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
240+
ConfigurableApplicationContext ac =
241+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
229242
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
230243
final Method quxMethod = Bar.class.getMethod("qux", String.class, String.class);
231244
final AtomicBoolean called = new AtomicBoolean();
@@ -251,7 +264,8 @@ public void handleMessage(Message<?> message) throws MessagingException {
251264

252265
@Test
253266
public void testWithServiceCastAsSuperclassAnnotatedMethod() {
254-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
267+
ConfigurableApplicationContext ac =
268+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
255269
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
256270
MessageHandler handler = mock(MessageHandler.class);
257271
channel.subscribe(handler);
@@ -263,7 +277,8 @@ public void testWithServiceCastAsSuperclassAnnotatedMethod() {
263277

264278
@Test
265279
public void testWithServiceCastAsSuperclassUnAnnotatedMethod() {
266-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
280+
ConfigurableApplicationContext ac =
281+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
267282
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
268283
MessageHandler handler = mock(MessageHandler.class);
269284
channel.subscribe(handler);
@@ -274,8 +289,9 @@ public void testWithServiceCastAsSuperclassUnAnnotatedMethod() {
274289
}
275290

276291
@Test
277-
public void testWithServiceHashcode() throws Exception {
278-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
292+
public void testWithServiceHashcode() {
293+
ConfigurableApplicationContext ac =
294+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
279295
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
280296
MessageHandler handler = mock(MessageHandler.class);
281297
channel.subscribe(handler);
@@ -287,7 +303,8 @@ public void testWithServiceHashcode() throws Exception {
287303

288304
@Test
289305
public void testWithServiceToString() {
290-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
306+
ConfigurableApplicationContext ac =
307+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
291308
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
292309
MessageHandler handler = mock(MessageHandler.class);
293310
channel.subscribe(handler);
@@ -299,27 +316,29 @@ public void testWithServiceToString() {
299316

300317
@Test
301318
public void testWithServiceEquals() throws Exception {
302-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
319+
ConfigurableApplicationContext ac =
320+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
303321
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
304322
MessageHandler handler = mock(MessageHandler.class);
305323
channel.subscribe(handler);
306324
Bar bar = ac.getBean(Bar.class);
307-
assertTrue(bar.equals(ac.getBean(Bar.class)));
325+
assertSame(bar, ac.getBean(Bar.class));
308326
GatewayProxyFactoryBean fb = new GatewayProxyFactoryBean(Bar.class);
309327
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
310328
bf.registerSingleton("requestChannelBar", channel);
311329
bf.registerSingleton("requestChannelBaz", channel);
312330
bf.registerSingleton("requestChannelFoo", channel);
313331
fb.setBeanFactory(bf);
314332
fb.afterPropertiesSet();
315-
assertFalse(bar.equals(fb.getObject()));
333+
assertNotSame(bar, fb.getObject());
316334
verify(handler, times(0)).handleMessage(Mockito.any(Message.class));
317335
ac.close();
318336
}
319337

320338
@Test
321339
public void testWithServiceGetClass() {
322-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
340+
ConfigurableApplicationContext ac =
341+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
323342
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
324343
MessageHandler handler = mock(MessageHandler.class);
325344
channel.subscribe(handler);
@@ -336,7 +355,8 @@ public void testWithServiceAsNotAnInterface() {
336355

337356
@Test
338357
public void testWithCustomMapper() {
339-
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
358+
ConfigurableApplicationContext ac =
359+
new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", getClass());
340360
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
341361
final AtomicBoolean called = new AtomicBoolean();
342362
MessageHandler handler = new MessageHandler() {
@@ -577,6 +597,7 @@ public MessageHandler autoCreateServiceActivator() {
577597
}
578598

579599
@MessagingGateway
600+
@Profile("gatewayTest")
580601
public interface Int2634Gateway {
581602

582603
@Gateway(requestChannel = "gatewayChannel", payloadExpression = "#args[0]")
@@ -590,6 +611,15 @@ public interface Int2634Gateway {
590611

591612
}
592613

614+
@MessagingGateway(defaultRequestChannel = "errorChannel")
615+
@TestMessagingGateway
616+
@Profile("notActiveProfile")
617+
public interface NotActivatedByProfileGateway {
618+
619+
void send(String payload);
620+
621+
}
622+
593623
@MessagingGateway(asyncExecutor = "exec")
594624
public interface ExecGateway {
595625

src/reference/asciidoc/gateway.adoc

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,9 @@ The standard `@ComponentScan` infrastructure doesn't deal with interfaces, there
283283
to determine `@MessagingGateway` annotation on the interfaces and register `GatewayProxyFactoryBean` s for them.
284284
See also <<annotations>>
285285

286-
NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration`
287-
class.
286+
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.
287+
288+
NOTE: If you have no XML configuration, the `@EnableIntegration` annotation is required on at least one `@Configuration` class.
288289
See <<configuration-enable-integration>> for more information.
289290

290291
[[gateway-calling-no-argument-methods]]
@@ -374,14 +375,11 @@ error: if there is an `error-channel` configured, it will be sent there, to the
374375
thrown to the caller of gateway.
375376
Similarly, if the error flow on the `error-channel` returns an `ErrorMessage` its payload is thrown to the caller.
376377
The same applies to any message with a `Throwable` payload.
377-
This can be useful in async situations when when there is a need propagate an `Exception` directly to the caller.
378-
To achieve this you can either return an `Exception` as the `reply` from some service, or simply throw it.
379-
Generally, even with an async flow, the framework will take care of propagating an exception thrown by the
380-
downstream flow back to the gateway.
381-
The https://github.com/spring-projects/spring-integration-samples/tree/master/intermediate/tcp-client-server-multiplex[TCP Client-Server Multiplex]
382-
sample demonstrates both techniques to return the exception to the caller.
383-
It emulates a Socket IO error to the waiting thread using an `aggregator` with `group-timeout` (see <<agg-and-group-to>>)
384-
and `MessagingTimeoutException` reply on the discard flow.
378+
This can be useful in asynchronous situations when when you need to propagate an `Exception` directly to the caller.
379+
To do so, you can either return an `Exception` (as the `reply` from some service) or throw it.
380+
Generally, even with an asynchronous flow, the framework takes care of propagating an exception thrown by the downstream flow back to the gateway.
381+
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.
382+
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.
385383

386384

387385
[[async-gateway]]
@@ -514,6 +512,9 @@ In this scenario, it is expected that the downstream flow will return a `Complet
514512

515513
*Usage Scenarios*
516514

515+
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).
516+
517+
====
517518
[source, java]
518519
----
519520
@@ -526,8 +527,8 @@ CompletableFuture<Invoice> order(Order order);
526527
<int:gateway service-interface="foo.Service" default-request-channel="orders" />
527528
----
528529
529-
In this scenario, the caller thread returns immediately with a `CompletableFuture<Invoice>` which will be completed
530-
when the downstream flow replies to the gateway (with an `Invoice` object).
530+
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.
531+
Some other process must complete the future when the invoice is ready.
531532
532533
[source, java]
533534
----
@@ -542,8 +543,7 @@ CompletableFuture<Invoice> order(Order order);
542543
async-executor="" />
543544
----
544545
545-
In this scenario, the caller thread will return with a CompletableFuture<Invoice> when the downstream flow provides
546-
it as the payload of the reply to the gateway.
546+
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.
547547
Some other process must complete the future when the invoice is ready.
548548
549549
[source, java]

0 commit comments

Comments
 (0)