Support Ant-style package name with component index

This commit improves the component index so that it supports ant-style
package name (i.e. com.example.**.foo).

Issue: SPR-16152
This commit is contained in:
Stephane Nicoll 2017-11-05 13:14:45 +02:00
parent 9649b0cb25
commit 1838ddb95d
4 changed files with 96 additions and 14 deletions

View File

@ -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<String, String> index;
private final static AntPathMatcher pathMatcher = new AntPathMatcher(".");
private final MultiValueMap<String, Entry> index;
CandidateComponentsIndex(List<Properties> content) {
@ -62,26 +66,47 @@ public class CandidateComponentsIndex {
* or an empty set if none has been found for the specified {@code basePackage}
*/
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
List<String> candidates = this.index.get(stereotype);
List<Entry> 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<String, String> parseIndex(List<Properties> content) {
MultiValueMap<String, String> index = new LinkedMultiValueMap<>();
private static MultiValueMap<String, Entry> parseIndex(List<Properties> content) {
MultiValueMap<String, Entry> 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);
}
}
}
}

View File

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

View File

@ -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<? extends BeanDefinition> expectedBeanDefinitionType) {
Set<BeanDefinition> 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<BeanDefinition> 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<BeanDefinition> 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<BeanDefinition> 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<BeanDefinition> 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));
}

View File

@ -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
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
example.scannable.sub.BarComponent=org.springframework.stereotype.Component