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 {}