Skip to content

Commit 1838ddb

Browse files
committed
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
1 parent 9649b0c commit 1838ddb

File tree

4 files changed

+96
-14
lines changed

4 files changed

+96
-14
lines changed

spring-context/src/main/java/org/springframework/context/index/CandidateComponentsIndex.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.util.Set;
2323
import java.util.stream.Collectors;
2424

25+
import org.springframework.util.AntPathMatcher;
26+
import org.springframework.util.ClassUtils;
2527
import org.springframework.util.LinkedMultiValueMap;
2628
import org.springframework.util.MultiValueMap;
2729

@@ -46,7 +48,9 @@
4648
*/
4749
public class CandidateComponentsIndex {
4850

49-
private final MultiValueMap<String, String> index;
51+
private final static AntPathMatcher pathMatcher = new AntPathMatcher(".");
52+
53+
private final MultiValueMap<String, Entry> index;
5054

5155

5256
CandidateComponentsIndex(List<Properties> content) {
@@ -62,26 +66,47 @@ public class CandidateComponentsIndex {
6266
* or an empty set if none has been found for the specified {@code basePackage}
6367
*/
6468
public Set<String> getCandidateTypes(String basePackage, String stereotype) {
65-
List<String> candidates = this.index.get(stereotype);
69+
List<Entry> candidates = this.index.get(stereotype);
6670
if (candidates != null) {
6771
return candidates.parallelStream()
68-
.filter(t -> t.startsWith(basePackage))
72+
.filter(t -> t.match(basePackage))
73+
.map(t -> t.type)
6974
.collect(Collectors.toSet());
7075
}
7176
return Collections.emptySet();
7277
}
7378

74-
private static MultiValueMap<String, String> parseIndex(List<Properties> content) {
75-
MultiValueMap<String, String> index = new LinkedMultiValueMap<>();
79+
private static MultiValueMap<String, Entry> parseIndex(List<Properties> content) {
80+
MultiValueMap<String, Entry> index = new LinkedMultiValueMap<>();
7681
for (Properties entry : content) {
7782
entry.forEach((type, values) -> {
7883
String[] stereotypes = ((String) values).split(",");
7984
for (String stereotype : stereotypes) {
80-
index.add(stereotype, (String) type);
85+
index.add(stereotype, new Entry((String) type));
8186
}
8287
});
8388
}
8489
return index;
8590
}
8691

92+
private static class Entry {
93+
private final String type;
94+
private final String packageName;
95+
96+
Entry(String type) {
97+
this.type = type;
98+
this.packageName = ClassUtils.getPackageName(type);
99+
}
100+
101+
public boolean match(String basePackage) {
102+
if (pathMatcher.isPattern(basePackage)) {
103+
return pathMatcher.match(basePackage, this.packageName);
104+
}
105+
else {
106+
return this.type.startsWith(basePackage);
107+
}
108+
}
109+
110+
}
111+
87112
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package example.scannable.sub;
18+
19+
import org.springframework.stereotype.Component;
20+
21+
/**
22+
* @author Stephane Nicoll
23+
*/
24+
@Component
25+
public class BarComponent {
26+
}

spring-context/src/test/java/org/springframework/context/annotation/ClassPathScanningCandidateComponentProviderTests.java

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
3636
import example.scannable.ScopedProxyTestBean;
3737
import example.scannable.ServiceInvocationCounter;
3838
import example.scannable.StubFooDao;
39+
import example.scannable.sub.BarComponent;
3940
import org.aspectj.lang.annotation.Aspect;
4041
import org.junit.Test;
4142

@@ -98,7 +99,31 @@ private void testDefault(ClassPathScanningCandidateComponentProvider provider,
9899
assertTrue(containsBeanClass(candidates, StubFooDao.class));
99100
assertTrue(containsBeanClass(candidates, NamedStubDao.class));
100101
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
101-
assertEquals(6, candidates.size());
102+
assertTrue(containsBeanClass(candidates, BarComponent.class));
103+
assertEquals(7, candidates.size());
104+
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
105+
}
106+
107+
@Test
108+
public void antStylePackageWithScan() {
109+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
110+
provider.setResourceLoader(new DefaultResourceLoader(
111+
CandidateComponentsTestClassLoader.disableIndex(getClass().getClassLoader())));
112+
testAntStyle(provider, ScannedGenericBeanDefinition.class);
113+
}
114+
115+
@Test
116+
public void antStylePackageWithIndex() {
117+
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(true);
118+
provider.setResourceLoader(new DefaultResourceLoader(TEST_BASE_CLASSLOADER));
119+
testAntStyle(provider, AnnotatedGenericBeanDefinition.class);
120+
}
121+
122+
private void testAntStyle(ClassPathScanningCandidateComponentProvider provider,
123+
Class<? extends BeanDefinition> expectedBeanDefinitionType) {
124+
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE + ".**.sub");
125+
assertTrue(containsBeanClass(candidates, BarComponent.class));
126+
assertEquals(1, candidates.size());
102127
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
103128
}
104129

@@ -200,7 +225,8 @@ private void testCustomSupportedIncludeAndExcludeFilter(ClassPathScanningCandida
200225
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
201226
assertTrue(containsBeanClass(candidates, NamedComponent.class));
202227
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
203-
assertEquals(2, candidates.size());
228+
assertTrue(containsBeanClass(candidates, BarComponent.class));
229+
assertEquals(3, candidates.size());
204230
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
205231
}
206232

@@ -251,7 +277,8 @@ private void testExclude(ClassPathScanningCandidateComponentProvider provider,
251277
assertTrue(containsBeanClass(candidates, FooServiceImpl.class));
252278
assertTrue(containsBeanClass(candidates, StubFooDao.class));
253279
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
254-
assertEquals(3, candidates.size());
280+
assertTrue(containsBeanClass(candidates, BarComponent.class));
281+
assertEquals(4, candidates.size());
255282
assertBeanDefinitionType(candidates, expectedBeanDefinitionType);
256283
}
257284

@@ -270,9 +297,10 @@ public void testWithComponentAnnotationOnly() {
270297
provider.addExcludeFilter(new AnnotationTypeFilter(Service.class));
271298
provider.addExcludeFilter(new AnnotationTypeFilter(Controller.class));
272299
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
273-
assertEquals(2, candidates.size());
300+
assertEquals(3, candidates.size());
274301
assertTrue(containsBeanClass(candidates, NamedComponent.class));
275302
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
303+
assertTrue(containsBeanClass(candidates, BarComponent.class));
276304
assertFalse(containsBeanClass(candidates, FooServiceImpl.class));
277305
assertFalse(containsBeanClass(candidates, StubFooDao.class));
278306
assertFalse(containsBeanClass(candidates, NamedStubDao.class));
@@ -311,10 +339,11 @@ public void testWithMultipleMatchingFilters() {
311339
provider.addIncludeFilter(new AnnotationTypeFilter(Component.class));
312340
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
313341
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
314-
assertEquals(6, candidates.size());
342+
assertEquals(7, candidates.size());
315343
assertTrue(containsBeanClass(candidates, NamedComponent.class));
316344
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
317345
assertTrue(containsBeanClass(candidates, FooServiceImpl.class));
346+
assertTrue(containsBeanClass(candidates, BarComponent.class));
318347
}
319348

320349
@Test
@@ -324,9 +353,10 @@ public void testExcludeTakesPrecedence() {
324353
provider.addIncludeFilter(new AssignableTypeFilter(FooServiceImpl.class));
325354
provider.addExcludeFilter(new AssignableTypeFilter(FooService.class));
326355
Set<BeanDefinition> candidates = provider.findCandidateComponents(TEST_BASE_PACKAGE);
327-
assertEquals(5, candidates.size());
356+
assertEquals(6, candidates.size());
328357
assertTrue(containsBeanClass(candidates, NamedComponent.class));
329358
assertTrue(containsBeanClass(candidates, ServiceInvocationCounter.class));
359+
assertTrue(containsBeanClass(candidates, BarComponent.class));
330360
assertFalse(containsBeanClass(candidates, FooServiceImpl.class));
331361
}
332362

spring-context/src/test/resources/example/scannable/spring.components

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ example.scannable.FooServiceImpl=org.springframework.stereotype.Component,exampl
66
example.scannable.ScopedProxyTestBean=example.scannable.FooService
77
example.scannable.StubFooDao=org.springframework.stereotype.Component
88
example.scannable.NamedStubDao=org.springframework.stereotype.Component
9-
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
9+
example.scannable.ServiceInvocationCounter=org.springframework.stereotype.Component
10+
example.scannable.sub.BarComponent=org.springframework.stereotype.Component

0 commit comments

Comments
 (0)