INT-3502: filters for @IntegrationComponentScan
JIRA: https://jira.spring.io/browse/INT-3502 For some use-cases it is really useful to filter `@MessagingGateway` components as any other `@ComponentScan` capable. * Add `useDefaultFilters()`, `includeFilters()` and `excludeFilters()` to the `@IntegrationComponentScan` * Modify `GatewayInterfaceTests` to reflect changes and be sure that logic is applied properly
This commit is contained in:
committed by
Gary Russell
parent
49638fdea2
commit
16be9fc47d
@@ -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.
|
||||
* <p>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.
|
||||
* <p>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 { };
|
||||
|
||||
}
|
||||
|
||||
@@ -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<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());
|
||||
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<String, Object> 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<BeanDefinition> 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<TypeFilter> typeFiltersFor(AnnotationAttributes filter, BeanDefinitionRegistry registry) {
|
||||
List<TypeFilter> 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<Annotation> annotationType = (Class<Annotation>) 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user