diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
index eaab654ec7..fe2b540442 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java
@@ -42,9 +42,10 @@ import org.springframework.core.type.filter.TypeFilter;
* always registered, meaning that any attempt to disable them at the
* {@code @ComponentScan} level would be ignored.
*
- *
See @{@link Configuration} Javadoc for usage examples.
+ *
See @{@link Configuration}'s javadoc for usage examples.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see Configuration
*/
@@ -139,31 +140,33 @@ public @interface ComponentScan {
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
+
/**
* The type of filter to use.
- *
Note that the filter types available are limited to those that may
- * be expressed as a {@code Class} in the {@link #value()} attribute. This is
- * in contrast to {@code }, which allows for
- * expression-based (i.e., string-based) filters such as AspectJ pointcuts.
- * These filter types are intentionally not supported here, and not available
- * in the {@link FilterType} enum.
- * @see FilterType
*/
FilterType type() default FilterType.ANNOTATION;
/**
* The class or classes to use as the filter. In the case of
- * {@link FilterType#ANNOTATION}, the class will be the annotation itself. In the
- * case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the type that
- * detected components should be assignable to. And in the case of
- * {@link FilterType#CUSTOM}, the class will be an implementation of
+ * {@link FilterType#ANNOTATION}, the class will be the annotation itself.
+ * In the case of {@link FilterType#ASSIGNABLE_TYPE}, the class will be the
+ * type that detected components should be assignable to. And in the case
+ * of {@link FilterType#CUSTOM}, the class will be an implementation of
* {@link TypeFilter}.
*
When multiple classes are specified, OR logic is applied, e.g. "include
* types annotated with {@code @Foo} OR {@code @Bar}".
*
Specifying zero classes is permitted but will have no effect on component
* scanning.
*/
- Class>[] value();
+ Class>[] value() default {};
+
+ /**
+ * The String pattern (or patterns) to use for the filter, as an alternative to
+ * specifying a Class {@link #value()}. In the case of {@link FilterType#ASPECTJ},
+ * this is an AspectJ type pattern expression; in case of {@link FilterType#REGEX},
+ * a regex pattern for the fully-qualified class names to match.
+ */
+ String[] pattern() default {};
}
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
index 7519495de0..559645800a 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScanAnnotationParser.java
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.regex.Pattern;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
@@ -30,7 +31,9 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
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.util.Assert;
import org.springframework.util.ClassUtils;
@@ -40,6 +43,7 @@ import org.springframework.util.StringUtils;
* Parser for the @{@link ComponentScan} annotation.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see ClassPathBeanDefinitionScanner#scan(String...)
* @see ComponentScanBeanDefinitionParser
@@ -138,25 +142,37 @@ class ComponentScanAnnotationParser {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,
- "An error occured when processing a @ComponentScan " +
- "ANNOTATION type filter: ");
+ "An error occured while processing a @ComponentScan ANNOTATION type filter: ");
@SuppressWarnings("unchecked")
- Class annoClass = (Class)filterClass;
- typeFilters.add(new AnnotationTypeFilter(annoClass));
+ 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 occured when processing a @ComponentScan " +
- "CUSTOM type filter: ");
+ "An error occured while processing a @ComponentScan CUSTOM type filter: ");
typeFilters.add(BeanUtils.instantiateClass(filterClass, TypeFilter.class));
break;
default:
- throw new IllegalArgumentException("unknown filter type " + filterType);
+ throw new IllegalArgumentException("Filter type not supported with Class value: " + filterType);
}
}
+
+ for (String expression : filterAttributes.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;
}
diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
index 26bd302d8e..f9c4b610e6 100644
--- a/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
+++ b/spring-context/src/main/java/org/springframework/context/annotation/FilterType.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
package org.springframework.context.annotation;
-import org.springframework.core.type.filter.AssignableTypeFilter;
-
-
/**
* Enumeration of the type filters that may be used in conjunction with
* {@link ComponentScan @ComponentScan}.
@@ -42,12 +39,24 @@ public enum FilterType {
/**
* Filter candidates assignable to a given type.
- * @see AssignableTypeFilter
+ * @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
+ /**
+ * Filter candidates matching a given AspectJ type pattern expression.
+ * @see org.springframework.core.type.filter.AspectJTypeFilter
+ */
+ ASPECTJ,
+
+ /**
+ * Filter candidates matching a given regex pattern.
+ * @see org.springframework.core.type.filter.RegexPatternTypeFilter
+ */
+ REGEX,
+
/** Filter candidates using a given custom
- * {@link org.springframework.core.type.filter.TypeFilter} implementation
+ * {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
index 88a1828e39..2eacd300fa 100644
--- a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
+++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.1.xsd
@@ -8,8 +8,8 @@
elementFormDefault="qualified"
attributeFormDefault="unqualified">
-
-
+
+
+ type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
@@ -161,13 +161,13 @@
+ type="org.springframework.beans.factory.config.PropertyOverrideConfigurer"/>
-
+
@@ -275,9 +275,8 @@
]]>
-
-
+
+
@@ -290,9 +289,8 @@
]]>
-
-
+
+
@@ -306,9 +304,9 @@
-
-
-
+
+
+
@@ -344,8 +342,7 @@
]]>
-
+
@@ -357,9 +354,8 @@
]]>
-
-
+
+
@@ -407,7 +403,7 @@
]]>
-
+
@@ -423,8 +419,7 @@
]]>
-
+
@@ -457,9 +452,9 @@
-
-
-
+
+
+
@@ -478,7 +473,7 @@
]]>
-
+
@@ -505,8 +500,8 @@
"annotation" indicates an annotation to be present at the type level in target components;
"assignable" indicates a class (or interface) that the target components are assignable to (extend/implement);
- "aspectj" indicates an AspectJ type expression to be matched by the target components;
- "regex" indicates a regex expression to be matched by the target components' class names;
+ "aspectj" indicates an AspectJ type pattern expression to be matched by the target components;
+ "regex" indicates a regex pattern to be matched by the target components' class names;
"custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface.
Note: This attribute will not be inherited by child bean definitions.
@@ -515,11 +510,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd
index 8fd8ad5857..5867682437 100644
--- a/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd
+++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-3.2.xsd
@@ -8,8 +8,8 @@
elementFormDefault="qualified"
attributeFormDefault="unqualified">
-
-
+
+
+ type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
@@ -161,13 +161,13 @@
+ type="org.springframework.beans.factory.config.PropertyOverrideConfigurer"/>
-
+
@@ -275,9 +275,8 @@
]]>
-
-
+
+
@@ -290,9 +289,8 @@
]]>
-
-
+
+
@@ -306,9 +304,9 @@
-
-
-
+
+
+
@@ -344,8 +342,7 @@
]]>
-
+
@@ -357,9 +354,8 @@
]]>
-
-
+
+
@@ -407,7 +403,7 @@
]]>
-
+
@@ -423,8 +419,7 @@
]]>
-
+
@@ -457,9 +452,9 @@
-
-
-
+
+
+
@@ -478,7 +473,7 @@
]]>
-
+
@@ -505,8 +500,8 @@
"annotation" indicates an annotation to be present at the type level in target components;
"assignable" indicates a class (or interface) that the target components are assignable to (extend/implement);
- "aspectj" indicates an AspectJ type expression to be matched by the target components;
- "regex" indicates a regex expression to be matched by the target components' class names;
+ "aspectj" indicates an AspectJ type pattern expression to be matched by the target components;
+ "regex" indicates a regex pattern to be matched by the target components' class names;
"custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface.
Note: This attribute will not be inherited by child bean definitions.
@@ -515,11 +510,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.0.xsd b/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.0.xsd
index 6c83b96143..041fb7bbba 100644
--- a/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.0.xsd
+++ b/spring-context/src/main/resources/org/springframework/context/config/spring-context-4.0.xsd
@@ -8,8 +8,8 @@
elementFormDefault="qualified"
attributeFormDefault="unqualified">
-
-
+
+
+ type="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"/>
@@ -161,13 +161,13 @@
+ type="org.springframework.beans.factory.config.PropertyOverrideConfigurer"/>
-
+
@@ -275,9 +275,8 @@
]]>
-
-
+
+
@@ -290,9 +289,8 @@
]]>
-
-
+
+
@@ -306,9 +304,9 @@
-
-
-
+
+
+
@@ -342,8 +340,7 @@
]]>
-
+
@@ -355,9 +352,8 @@
]]>
-
-
+
+
@@ -405,7 +401,7 @@
]]>
-
+
@@ -421,8 +417,7 @@
]]>
-
+
@@ -455,9 +450,9 @@
-
-
-
+
+
+
@@ -476,7 +471,7 @@
]]>
-
+
@@ -503,8 +498,8 @@
"annotation" indicates an annotation to be present at the type level in target components;
"assignable" indicates a class (or interface) that the target components are assignable to (extend/implement);
- "aspectj" indicates an AspectJ type expression to be matched by the target components;
- "regex" indicates a regex expression to be matched by the target components' class names;
+ "aspectj" indicates an AspectJ type pattern expression to be matched by the target components;
+ "regex" indicates a regex pattern to be matched by the target components' class names;
"custom" indicates a custom implementation of the org.springframework.core.type.TypeFilter interface.
Note: This attribute will not be inherited by child bean definitions.
@@ -513,11 +508,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
index f6d90975c2..9feceea8e9 100644
--- a/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
+++ b/spring-context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationIntegrationTests.java
@@ -16,18 +16,20 @@
package org.springframework.context.annotation;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.sameInstance;
-import static org.junit.Assert.assertThat;
-import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
-
import java.io.IOException;
import java.util.HashSet;
+import example.scannable.CustomComponent;
+import example.scannable.CustomStereotype;
+import example.scannable.DefaultNamedComponent;
+import example.scannable.FooService;
+import example.scannable.MessageBean;
+import example.scannable.ScopedProxyTestBean;
+import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImplicitBasePackage;
+import example.scannable_scoped.CustomScopeAnnotationBean;
+import example.scannable_scoped.MyScope;
import org.junit.Test;
+
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.CustomAutowireConfigurer;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -39,24 +41,19 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.tests.context.SimpleMapScope;
import org.springframework.util.SerializationTestUtils;
-import example.scannable.CustomComponent;
-import example.scannable.CustomStereotype;
-import example.scannable.DefaultNamedComponent;
-import example.scannable.FooService;
-import example.scannable.MessageBean;
-import example.scannable.ScopedProxyTestBean;
-import example.scannable_implicitbasepackage.ComponentScanAnnotatedConfigWithImplicitBasePackage;
-import example.scannable_scoped.CustomScopeAnnotationBean;
-import example.scannable_scoped.MyScope;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
/**
- * Integration tests for processing ComponentScan-annotated Configuration
- * classes.
+ * Integration tests for processing ComponentScan-annotated Configuration classes.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
public class ComponentScanAnnotationIntegrationTests {
+
@Test
public void controlScan() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -162,6 +159,30 @@ public class ComponentScanAnnotationIntegrationTests {
assertThat(deserialized.foo(1), equalTo("bar"));
}
+ @Test
+ public void withScopedProxyThroughRegex() throws IOException, ClassNotFoundException {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ComponentScanWithScopedProxyThroughRegex.class);
+ ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
+ ctx.refresh();
+ // should cast to the interface
+ FooService bean = (FooService) ctx.getBean("scopedProxyTestBean");
+ // should be dynamic proxy
+ assertThat(AopUtils.isJdkDynamicProxy(bean), is(true));
+ }
+
+ @Test
+ public void withScopedProxyThroughAspectJPattern() throws IOException, ClassNotFoundException {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(ComponentScanWithScopedProxyThroughAspectJPattern.class);
+ ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
+ ctx.refresh();
+ // should cast to the interface
+ FooService bean = (FooService) ctx.getBean("scopedProxyTestBean");
+ // should be dynamic proxy
+ assertThat(AopUtils.isJdkDynamicProxy(bean), is(true));
+ }
+
@Test
public void withMultipleAnnotationIncludeFilters1() throws IOException, ClassNotFoundException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -211,11 +232,11 @@ class ComponentScanAnnotatedConfig_WithValueAttribute {
@Configuration
@ComponentScan
-class ComponentScanWithNoPackagesConfig { }
+class ComponentScanWithNoPackagesConfig {}
@Configuration
@ComponentScan(basePackages="example.scannable", nameGenerator=MyBeanNameGenerator.class)
-class ComponentScanWithBeanNameGenenerator { }
+class ComponentScanWithBeanNameGenenerator {}
class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
@Override
@@ -226,7 +247,7 @@ class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
@Configuration
@ComponentScan(basePackages="example.scannable_scoped", scopeResolver=MyScopeMetadataResolver.class)
-class ComponentScanWithScopeResolver { }
+class ComponentScanWithScopeResolver {}
class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
MyScopeMetadataResolver() {
@@ -259,7 +280,21 @@ class ComponentScanWithCustomTypeFilter {
scopedProxy=ScopedProxyMode.INTERFACES,
useDefaultFilters=false,
includeFilters=@Filter(type=FilterType.ASSIGNABLE_TYPE, value=ScopedProxyTestBean.class))
-class ComponentScanWithScopedProxy { }
+class ComponentScanWithScopedProxy {}
+
+@Configuration
+@ComponentScan(basePackages="example.scannable",
+ scopedProxy=ScopedProxyMode.INTERFACES,
+ useDefaultFilters=false,
+ includeFilters=@Filter(type=FilterType.REGEX, pattern ="((?:[a-z.]+))ScopedProxyTestBean"))
+class ComponentScanWithScopedProxyThroughRegex {}
+
+@Configuration
+@ComponentScan(basePackages="example.scannable",
+ scopedProxy=ScopedProxyMode.INTERFACES,
+ useDefaultFilters=false,
+ includeFilters=@Filter(type=FilterType.ASPECTJ, pattern ="*..ScopedProxyTestBean"))
+class ComponentScanWithScopedProxyThroughAspectJPattern {}
@Configuration
@ComponentScan(basePackages="example.scannable",
@@ -269,18 +304,18 @@ class ComponentScanWithScopedProxy { }
@Filter(CustomComponent.class)
}
)
-class ComponentScanWithMultipleAnnotationIncludeFilters1 { }
+class ComponentScanWithMultipleAnnotationIncludeFilters1 {}
@Configuration
@ComponentScan(basePackages="example.scannable",
useDefaultFilters=false,
includeFilters=@Filter({CustomStereotype.class, CustomComponent.class})
)
-class ComponentScanWithMultipleAnnotationIncludeFilters2 { }
+class ComponentScanWithMultipleAnnotationIncludeFilters2 {}
@Configuration
@ComponentScan(
value="example.scannable",
basePackages="example.scannable",
basePackageClasses=example.scannable._package.class)
-class ComponentScanWithBasePackagesAndValueAlias { }
+class ComponentScanWithBasePackagesAndValueAlias {}