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 26a3a6cec9..ffd202737b 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 @@ -30,7 +30,9 @@ import org.springframework.core.type.filter.TypeFilter; * Provides support parallel with Spring XML's {@code } element. * *

One of {@link #basePackageClasses()}, {@link #basePackages()} or its alias - * {@link #value()} must be specified. + * {@link #value()} may be specified to define specific packages to scan. If specific + * packages are not defined scanning will occur from the package of the + * class with this annotation. * *

Note that the {@code } element has an * {@code annotation-config} attribute, however this annotation does not. This is because 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 a549b8d0af..4993306cc7 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 @@ -65,7 +65,7 @@ class ComponentScanAnnotationParser { } - public Set parse(AnnotationAttributes componentScan) { + public Set parse(AnnotationAttributes componentScan, String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, componentScan.getBoolean("useDefaultFilters")); @@ -118,7 +118,7 @@ class ComponentScanAnnotationParser { } if (basePackages.isEmpty()) { - throw new IllegalStateException("At least one base package must be specified"); + basePackages.add(ClassUtils.getPackageName(declaringClass)); } return scanner.doScan(basePackages.toArray(new String[]{})); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 3f92029066..cf6e12f296 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -210,7 +210,8 @@ class ConfigurationClassParser { AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); if (componentScan != null) { // the config class is annotated with @ComponentScan -> perform the scan immediately - Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan); + Set scannedBeanDefinitions = + this.componentScanParser.parse(componentScan, metadata.getClassName()); // check the set of scanned definitions for any further config classes and parse recursively if necessary for (BeanDefinitionHolder holder : scannedBeanDefinitions) { diff --git a/spring-context/src/test/java/example/scannable_implicitbasepackage/ComponentScanAnnotatedConfigWithImplicitBasePackage.java b/spring-context/src/test/java/example/scannable_implicitbasepackage/ComponentScanAnnotatedConfigWithImplicitBasePackage.java new file mode 100644 index 0000000000..0df041daac --- /dev/null +++ b/spring-context/src/test/java/example/scannable_implicitbasepackage/ComponentScanAnnotatedConfigWithImplicitBasePackage.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.scannable_implicitbasepackage; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author Phillip Webb + */ +@Configuration +@ComponentScan +public class ComponentScanAnnotatedConfigWithImplicitBasePackage { +} diff --git a/spring-context/src/test/java/example/scannable_implicitbasepackage/ScannedComponent.java b/spring-context/src/test/java/example/scannable_implicitbasepackage/ScannedComponent.java new file mode 100644 index 0000000000..f47e5d3a5d --- /dev/null +++ b/spring-context/src/test/java/example/scannable_implicitbasepackage/ScannedComponent.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package example.scannable_implicitbasepackage; + +import org.springframework.stereotype.Component; + +/** + * @author Phillip Webb + */ +@Component +public class ScannedComponent { + +} 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 9ed43a3bbf..7b461584a0 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -21,9 +21,7 @@ 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.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; import java.io.IOException; @@ -47,6 +45,7 @@ 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; @@ -93,6 +92,18 @@ public class ComponentScanAnnotationIntegrationTests { ctx.containsBean("fooServiceImpl"), is(true)); } + @Test + public void viaContextRegistration_FromPackageOfConfigClass() { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(ComponentScanAnnotatedConfigWithImplicitBasePackage.class); + ctx.refresh(); + ctx.getBean(ComponentScanAnnotatedConfigWithImplicitBasePackage.class); + assertThat("config class bean not found", ctx.containsBeanDefinition("componentScanAnnotatedConfigWithImplicitBasePackage"), is(true)); + assertThat("@ComponentScan annotated @Configuration class registered directly against " + + "AnnotationConfigApplicationContext did not trigger component scanning as expected", + ctx.containsBean("scannedComponent"), is(true)); + } + @Test public void viaBeanRegistration() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -110,18 +121,6 @@ public class ComponentScanAnnotationIntegrationTests { ctx.containsBean("fooServiceImpl"), is(true)); } - @Test - public void invalidComponentScanDeclaration_noPackagesSpecified() { - AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); - ctx.register(ComponentScanWithNoPackagesConfig.class); - try { - ctx.refresh(); - fail("Expected exception when parsing @ComponentScan definition that declares no packages"); - } catch (IllegalStateException ex) { - assertThat(ex.getMessage(), containsString("At least one base package must be specified")); - } - } - @Test public void withCustomBeanNameGenerator() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java index cbd6bae580..e321291620 100644 --- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java +++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -462,17 +462,28 @@ public abstract class ClassUtils { } /** - * Determine the name of the package of the given class: - * e.g. "java.lang" for the java.lang.String class. + * Determine the name of the package of the given class, + * e.g. "java.lang" for the {@code java.lang.String} class. * @param clazz the class * @return the package name, or the empty String if the class * is defined in the default package */ public static String getPackageName(Class clazz) { Assert.notNull(clazz, "Class must not be null"); - String className = clazz.getName(); - int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR); - return (lastDotIndex != -1 ? className.substring(0, lastDotIndex) : ""); + return getPackageName(clazz.getName()); + } + + /** + * Determine the name of the package of the given fully-qualified class name, + * e.g. "java.lang" for the {@code java.lang.String} class name. + * @param fqClassName the fully-qualified class name + * @return the package name, or the empty String if the class + * is defined in the default package + */ + public static String getPackageName(String fqClassName) { + Assert.notNull(fqClassName, "Class name must not be null"); + int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR); + return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : ""); } /**