diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java
index b8c66204e2..0f18cb4b63 100644
--- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java
+++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/IntegrationComponentScan.java
@@ -22,7 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import org.springframework.integration.config.IntegrationComponentScanRegistrar;
@@ -36,7 +36,7 @@ import org.springframework.integration.config.IntegrationComponentScanRegistrar;
* @author Artem Bilan
* @since 4.0
*
- * @see ComponentScan
+ * @see org.springframework.context.annotation.ComponentScan
* @see MessagingGateway
*/
@Retention(RetentionPolicy.RUNTIME)
@@ -53,24 +53,53 @@ public @interface IntegrationComponentScan {
* @return the array of 'basePackages'.
*/
@AliasFor("basePackages")
- String[] value() default {};
+ String[] value() default { };
/**
- * Base packages to scan for annotated components.
- * The {@link #value()} is an alias for (and mutually exclusive with) this attribute.
- * Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
+ * Base packages to scan for annotated components. The {@link #value()} is an alias
+ * for (and mutually exclusive with) this attribute. Use {@link #basePackageClasses()}
+ * for a type-safe alternative to String-based package names.
* @return the array of 'basePackages'.
*/
@AliasFor("value")
- String[] basePackages() default {};
+ String[] basePackages() default { };
/**
- * Type-safe alternative to {@link #basePackages()} for specifying the packages
- * to scan for annotated components. The package of each class specified will be scanned.
- * Consider creating a special no-op marker class or interface in each package
- * that serves no purpose other than being referenced by this attribute.
+ * Type-safe alternative to {@link #basePackages()} for specifying the packages to
+ * scan for annotated components. The package of each class specified will be scanned.
+ * Consider creating a special no-op marker class or interface in each package that
+ * serves no purpose other than being referenced by this attribute.
* @return the array of 'basePackageClasses'.
*/
- Class>[] basePackageClasses() default {};
+ Class>[] basePackageClasses() default { };
+
+
+ /**
+ * Indicates whether automatic detection of classes annotated with
+ * {@code @MessagingGateway} should be enabled.
+ * @since 5.0
+ */
+ boolean useDefaultFilters() default true;
+
+ /**
+ * Specifies which types are eligible for component scanning.
+ *
Further narrows the set of candidate components from everything in
+ * {@link #basePackages} to everything in the base packages that matches the given
+ * filter or filters.
+ *
Note that these filters will be applied in addition to the default filters, if
+ * specified. Any type under the specified base packages which matches a given filter
+ * will be included, even if it does not match the default filters (i.e. is not
+ * annotated with {@code @MessagingGateway}).
+ * @since 5.0
+ * @see #excludeFilters()
+ */
+ Filter[] includeFilters() default { };
+
+ /**
+ * Specifies which types are not eligible for component scanning.
+ * @since 5.0
+ * @see #includeFilters()
+ */
+ Filter[] excludeFilters() default { };
}
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..6657938c3a 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
@@ -16,22 +16,40 @@
package org.springframework.integration.config;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.Aware;
+import org.springframework.beans.factory.BeanClassLoaderAware;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
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.FilterType;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.AnnotationAttributes;
+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;
+import org.springframework.core.type.filter.AspectJTypeFilter;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.core.type.filter.RegexPatternTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
@@ -43,14 +61,17 @@ import org.springframework.util.StringUtils;
* @since 4.0
*/
public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRegistrar,
- ResourceLoaderAware {
+ ResourceLoaderAware, EnvironmentAware {
private final Map componentRegistrars = new HashMap();
private ResourceLoader resourceLoader;
+ private Environment environment;
+
public IntegrationComponentScanRegistrar() {
- this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true), new MessagingGatewayRegistrar());
+ this.componentRegistrars.put(new AnnotationTypeFilter(MessagingGateway.class, true),
+ new MessagingGatewayRegistrar());
}
@Override
@@ -58,6 +79,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
@@ -91,18 +117,32 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
}
};
- for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {
- scanner.addIncludeFilter(typeFilter);
+ if ((boolean) componentScan.get("useDefaultFilters")) {
+ for (TypeFilter typeFilter : this.componentRegistrars.keySet()) {
+ scanner.addIncludeFilter(typeFilter);
+ }
}
+ for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("includeFilters")) {
+ for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
+ scanner.addIncludeFilter(typeFilter);
+ }
+ }
+ for (AnnotationAttributes filter : (AnnotationAttributes[]) componentScan.get("excludeFilters")) {
+ for (TypeFilter typeFilter : typeFiltersFor(filter, registry)) {
+ scanner.addExcludeFilter(typeFilter);
+ }
+ }
+
+
scanner.setResourceLoader(this.resourceLoader);
for (String basePackage : basePackages) {
Set candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
- for (ImportBeanDefinitionRegistrar importBeanDefinitionRegistrar : this.componentRegistrars.values()) {
- importBeanDefinitionRegistrar.registerBeanDefinitions(((AnnotatedBeanDefinition) candidateComponent).getMetadata(),
+ for (ImportBeanDefinitionRegistrar registrar : this.componentRegistrars.values()) {
+ registrar.registerBeanDefinitions(((AnnotatedBeanDefinition) candidateComponent).getMetadata(),
registry);
}
}
@@ -110,4 +150,68 @@ public class IntegrationComponentScanRegistrar implements ImportBeanDefinitionRe
}
}
+ private List typeFiltersFor(AnnotationAttributes filter, BeanDefinitionRegistry registry) {
+ List typeFilters = new ArrayList<>();
+ FilterType filterType = filter.getEnum("type");
+
+ for (Class> filterClass : filter.getClassArray("classes")) {
+ switch (filterType) {
+ case ANNOTATION:
+ Assert.isAssignable(Annotation.class, filterClass,
+ "An error occurred while processing a @IntegrationComponentScan ANNOTATION type filter: ");
+ @SuppressWarnings("unchecked")
+ Class annotationType = (Class) filterClass;
+ typeFilters.add(new AnnotationTypeFilter(annotationType));
+ break;
+ case ASSIGNABLE_TYPE:
+ typeFilters.add(new AssignableTypeFilter(filterClass));
+ break;
+ case CUSTOM:
+ Assert.isAssignable(TypeFilter.class, filterClass,
+ "An error occurred while processing a @IntegrationComponentScan CUSTOM type filter: ");
+ TypeFilter typeFilter = BeanUtils.instantiateClass(filterClass, TypeFilter.class);
+ invokeAwareMethods(filter, this.environment, this.resourceLoader, registry);
+ typeFilters.add(typeFilter);
+ break;
+ default:
+ throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
+ }
+ }
+
+ for (String expression : filter.getStringArray("pattern")) {
+ switch (filterType) {
+ case ASPECTJ:
+ typeFilters.add(new AspectJTypeFilter(expression, this.resourceLoader.getClassLoader()));
+ break;
+ case REGEX:
+ typeFilters.add(new RegexPatternTypeFilter(Pattern.compile(expression)));
+ break;
+ default:
+ throw new IllegalArgumentException("Filter type not supported with String pattern: " + filterType);
+ }
+ }
+
+ return typeFilters;
+ }
+
+ private static void invokeAwareMethods(Object parserStrategyBean, Environment environment,
+ ResourceLoader resourceLoader, BeanDefinitionRegistry registry) {
+ if (parserStrategyBean instanceof Aware) {
+ if (parserStrategyBean instanceof BeanClassLoaderAware) {
+ ClassLoader classLoader = (registry instanceof ConfigurableBeanFactory ?
+ ((ConfigurableBeanFactory) registry).getBeanClassLoader() : resourceLoader.getClassLoader());
+ ((BeanClassLoaderAware) parserStrategyBean).setBeanClassLoader(classLoader);
+ }
+ if (parserStrategyBean instanceof BeanFactoryAware && registry instanceof BeanFactory) {
+ ((BeanFactoryAware) parserStrategyBean).setBeanFactory((BeanFactory) registry);
+ }
+ if (parserStrategyBean instanceof EnvironmentAware) {
+ ((EnvironmentAware) parserStrategyBean).setEnvironment(environment);
+ }
+ if (parserStrategyBean instanceof ResourceLoaderAware) {
+ ((ResourceLoaderAware) parserStrategyBean).setResourceLoader(resourceLoader);
+ }
+ }
+ }
+
}
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 67603539fa..b4a7c2aa52 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
@@ -30,6 +30,10 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
@@ -110,13 +114,15 @@ public class GatewayInterfaceTests {
@Autowired
@Qualifier("&gatewayInterfaceTests$NoExecGateway")
private GatewayProxyFactoryBean noExecGatewayFB;
-
@Autowired
private SimpleAsyncTaskExecutor exec;
@Autowired
private AutoCreateChannelService autoCreateChannelService;
+ @Autowired(required = false)
+ private NotAGatewayByScanFilter notAGatewayByScanFilter;
+
@Test
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
@@ -383,6 +389,13 @@ public class GatewayInterfaceTests {
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertThat(result2.get().getName(), startsWith("exec-"));
+
+ /*
+ @IntegrationComponentScan(useDefaultFilters = false,
+ includeFilters = @ComponentScan.Filter(TestMessagingGateway.class))
+ excludes this a candidate
+ */
+ assertNull(this.notAGatewayByScanFilter);
}
@Test
@@ -402,6 +415,7 @@ public class GatewayInterfaceTests {
}
public interface Bar extends Foo {
+
@Gateway(requestChannel = "requestChannelBar")
void bar(String payload);
@@ -409,7 +423,9 @@ public class GatewayInterfaceTests {
}
public static class NotAnInterface {
- public void fail(String payload) { }
+
+ public void fail(String payload) {
+ }
}
public interface Baz {
@@ -441,7 +457,8 @@ public class GatewayInterfaceTests {
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = AutoCreateChannelService.class))
- @IntegrationComponentScan
+ @IntegrationComponentScan(useDefaultFilters = false,
+ includeFilters = @ComponentScan.Filter(TestMessagingGateway.class))
@EnableIntegration
public static class TestConfig {
@@ -492,6 +509,7 @@ public class GatewayInterfaceTests {
}
@MessagingGateway
+ @TestMessagingGateway
public interface Int2634Gateway {
@Gateway(requestChannel = "gatewayChannel", payloadExpression = "#args[0]")
@@ -506,6 +524,7 @@ public class GatewayInterfaceTests {
}
@MessagingGateway(asyncExecutor = "exec")
+ @TestMessagingGateway
public interface ExecGateway {
@Gateway(requestChannel = "gatewayThreadChannel")
@@ -517,6 +536,7 @@ public class GatewayInterfaceTests {
}
@MessagingGateway(asyncExecutor = AnnotationConstants.NULL)
+ @TestMessagingGateway
public interface NoExecGateway {
@Gateway(requestChannel = "gatewayThreadChannel")
@@ -526,10 +546,24 @@ public class GatewayInterfaceTests {
@MessagingGateway(defaultRequestChannel = "autoCreateChannel")
+ @TestMessagingGateway
public interface AutoCreateChannelGateway {
String foo(String payload);
}
+ @MessagingGateway
+ public interface NotAGatewayByScanFilter {
+
+ String foo(String payload);
+
+ }
+
+ @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface TestMessagingGateway {
+
+ }
+
}