diff --git a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java index 4d316efea19..22fe77c1154 100644 --- a/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java +++ b/spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java @@ -22,6 +22,8 @@ import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.ClassUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -46,7 +48,9 @@ import org.springframework.util.MultiValueMap; */ public class CandidateComponentsIndex { - private final MultiValueMap index; + private final static AntPathMatcher pathMatcher = new AntPathMatcher("."); + + private final MultiValueMap index; CandidateComponentsIndex(List content) { @@ -62,26 +66,47 @@ public class CandidateComponentsIndex { * or an empty set if none has been found for the specified {@code basePackage} */ public Set getCandidateTypes(String basePackage, String stereotype) { - List candidates = this.index.get(stereotype); + List candidates = this.index.get(stereotype); if (candidates != null) { return candidates.parallelStream() - .filter(t -> t.startsWith(basePackage)) + .filter(t -> t.match(basePackage)) + .map(t -> t.type) .collect(Collectors.toSet()); } return Collections.emptySet(); } - private static MultiValueMap parseIndex(List content) { - MultiValueMap index = new LinkedMultiValueMap<>(); + private static MultiValueMap parseIndex(List content) { + MultiValueMap index = new LinkedMultiValueMap<>(); for (Properties entry : content) { entry.forEach((type, values) -> { String[] stereotypes = ((String) values).split(","); for (String stereotype : stereotypes) { - index.add(stereotype, (String) type); + index.add(stereotype, new Entry((String) type)); } }); } return index; } + private static class Entry { + private final String type; + private final String packageName; + + Entry(String type) { + this.type = type; + this.packageName = ClassUtils.getPackageName(type); + } + + public boolean match(String basePackage) { + if (pathMatcher.isPattern(basePackage)) { + return pathMatcher.match(basePackage, this.packageName); + } + else { + return this.type.startsWith(basePackage); + } + } + + } + } diff --git a/spring-context/src/test/java/example/scannable/sub/BarComponent.java b/spring-context/src/test/java/example/scannable/sub/BarComponent.java new file mode 100644 index 00000000000..53ceb376afb --- /dev/null +++ b/spring-context/src/test/java/example/scannable/sub/BarComponent.java @@ -0,0 +1,26 @@ +/* + * Copyright 2002-2017 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.sub; + +import org.springframework.stereotype.Component; + +/** + * @author Stephane Nicoll + */ +@Component +public class BarComponent { +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java index 6537777beb8..9efc75033c8 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -36,6 +36,7 @@ import example.scannable.NamedStubDao; import example.scannable.ScopedProxyTestBean; import example.scannable.ServiceInvocationCounter; import example.scannable.StubFooDao; +import example.scannable.sub.BarComponent; import org.aspectj.lang.annotation.Aspect; import org.junit.Test; @@ -98,7 +99,31 @@ public class ClassPathScanningCandidateComponentProviderTests { assertTrue(containsBeanClass(candidates, StubFooDao.class)); assertTrue(containsBeanClass(candidates, NamedStubDao.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); - assertEquals(6, candidates.size()); + assertTrue(containsBeanClass(candidates, BarComponent.class)); + assertEquals(7, candidates.size()); + assertBeanDefinitionType(candidates, expectedBeanDefinitionType); + } + + @Test + public void antStylePackageWithScan() { + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); + provider.setResourceLoader(new DefaultResourceLoader( + CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader()))); + testAntStyle(provider, ScannedGenericBeanDefinition.class); + } + + @Test + public void antStylePackageWithIndex() { + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true); + provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER)); + testAntStyle(provider, AnnotatedGenericBeanDefinition.class); + } + + private void testAntStyle(ClassPathScanningCandidateComponentProvider provider, + Class expectedBeanDefinitionType) { + Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub"); + assertTrue(containsBeanClass(candidates, BarComponent.class)); + assertEquals(1, candidates.size()); assertBeanDefinitionType(candidates, expectedBeanDefinitionType); } @@ -200,7 +225,8 @@ public class ClassPathScanningCandidateComponentProviderTests { Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); assertTrue(containsBeanClass(candidates, NamedComponent.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); - assertEquals(2, candidates.size()); + assertTrue(containsBeanClass(candidates, BarComponent.class)); + assertEquals(3, candidates.size()); assertBeanDefinitionType(candidates, expectedBeanDefinitionType); } @@ -251,7 +277,8 @@ public class ClassPathScanningCandidateComponentProviderTests { assertTrue(containsBeanClass(candidates, FooServiceImpl.class)); assertTrue(containsBeanClass(candidates, StubFooDao.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); - assertEquals(3, candidates.size()); + assertTrue(containsBeanClass(candidates, BarComponent.class)); + assertEquals(4, candidates.size()); assertBeanDefinitionType(candidates, expectedBeanDefinitionType); } @@ -270,9 +297,10 @@ public class ClassPathScanningCandidateComponentProviderTests { provider.addExcludeFilter(new AnnotationTypeFilter(Service.class)); provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertEquals(2, candidates.size()); + assertEquals(3, candidates.size()); assertTrue(containsBeanClass(candidates, NamedComponent.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); + assertTrue(containsBeanClass(candidates, BarComponent.class)); assertFalse(containsBeanClass(candidates, FooServiceImpl.class)); assertFalse(containsBeanClass(candidates, StubFooDao.class)); assertFalse(containsBeanClass(candidates, NamedStubDao.class)); @@ -311,10 +339,11 @@ public class ClassPathScanningCandidateComponentProviderTests { provider.addIncludeFilter(new AnnotationTypeFilter(Component.class)); provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertEquals(6, candidates.size()); + assertEquals(7, candidates.size()); assertTrue(containsBeanClass(candidates, NamedComponent.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); assertTrue(containsBeanClass(candidates, FooServiceImpl.class)); + assertTrue(containsBeanClass(candidates, BarComponent.class)); } @Test @@ -324,9 +353,10 @@ public class ClassPathScanningCandidateComponentProviderTests { provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class)); provider.addExcludeFilter(new AssignableTypeFilter(FooService.class)); Set candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE); - assertEquals(5, candidates.size()); + assertEquals(6, candidates.size()); assertTrue(containsBeanClass(candidates, NamedComponent.class)); assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class)); + assertTrue(containsBeanClass(candidates, BarComponent.class)); assertFalse(containsBeanClass(candidates, FooServiceImpl.class)); } diff --git a/spring-context/src/test/resources/example/scannable/spring.components b/spring-context/src/test/resources/example/scannable/spring.components index 62de4a95cdc..9e7303d2517 100644 --- a/spring-context/src/test/resources/example/scannable/spring.components +++ b/spring-context/src/test/resources/example/scannable/spring.components @@ -6,4 +6,5 @@ example.scannable.FooServiceImpl=org.springframework.stereotype.Component,exampl example.scannable.ScopedProxyTestBean=example.scannable.FooService example.scannable.StubFooDao=org.springframework.stereotype.Component example.scannable.NamedStubDao=org.springframework.stereotype.Component -example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component \ No newline at end of file +example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component +example.scannable.sub.BarComponent=org.springframework.stereotype.Component \ No newline at end of file