Support inferred base package for @ComponentScan
Prior to this change, @ComponentScan required the declaration of exactly one of the #value, #basePackage or #basePackageClasses attributes in order to determine which package(s) to scan. This commit introduces support for base package inference, relaxing the above requirement and falling back to scanning the package in which the @ComponentScan-annotated class is declared. Issue: SPR-9586
This commit is contained in:
committed by
Chris Beams
parent
512ffbb273
commit
73832f8c6e
@@ -30,7 +30,9 @@ import org.springframework.core.type.filter.TypeFilter;
|
||||
* Provides support parallel with Spring XML's {@code <context:component-scan>} element.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>Note that the {@code <context:component-scan>} element has an
|
||||
* {@code annotation-config} attribute, however this annotation does not. This is because
|
||||
|
||||
@@ -65,7 +65,7 @@ class ComponentScanAnnotationParser {
|
||||
}
|
||||
|
||||
|
||||
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan) {
|
||||
public Set<BeanDefinitionHolder> 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[]{}));
|
||||
|
||||
@@ -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<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan);
|
||||
Set<BeanDefinitionHolder> 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) {
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 <code>java.lang.String</code> 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) : "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user