Skip to content

Commit 388bd87

Browse files
committed
ComponentScan annotation is repeatable now
Issue: SPR-13151
1 parent aecb8b6 commit 388bd87

File tree

4 files changed

+111
-39
lines changed

4 files changed

+111
-39
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Documented;
2020
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Repeatable;
2122
import java.lang.annotation.Retention;
2223
import java.lang.annotation.RetentionPolicy;
2324
import java.lang.annotation.Target;
@@ -54,6 +55,7 @@
5455
@Retention(RetentionPolicy.RUNTIME)
5556
@Target(ElementType.TYPE)
5657
@Documented
58+
@Repeatable(ComponentScans.class)
5759
public @interface ComponentScan {
5860

5961
/**
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2015 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 org.springframework.context.annotation;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Container annotation that aggregates several {@link ComponentScan} annotations.
27+
*
28+
* <p>Can be used natively, declaring several nested {@link ComponentScan} annotations.
29+
* Can also be used in conjunction with Java 8's support for repeatable annotations,
30+
* where {@link ComponentScan} can simply be declared several times on the same method,
31+
* implicitly generating this container annotation.
32+
*
33+
* @author Juergen Hoeller
34+
* @since 4.3
35+
* @see ComponentScan
36+
*/
37+
@Retention(RetentionPolicy.RUNTIME)
38+
@Target(ElementType.TYPE)
39+
@Documented
40+
public @interface ComponentScans {
41+
42+
ComponentScan[] value();
43+
44+
}

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -260,15 +260,18 @@ protected final SourceClass doProcessConfigurationClass(ConfigurationClass confi
260260
}
261261

262262
// Process any @ComponentScan annotations
263-
AnnotationAttributes componentScan = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ComponentScan.class);
264-
if (componentScan != null && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
265-
// The config class is annotated with @ComponentScan -> perform the scan immediately
266-
Set<BeanDefinitionHolder> scannedBeanDefinitions =
267-
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
268-
// Check the set of scanned definitions for any further config classes and parse recursively if necessary
269-
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
270-
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
271-
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
263+
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
264+
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
265+
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
266+
for (AnnotationAttributes componentScan : componentScans) {
267+
// The config class is annotated with @ComponentScan -> perform the scan immediately
268+
Set<BeanDefinitionHolder> scannedBeanDefinitions =
269+
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
270+
// Check the set of scanned definitions for any further config classes and parse recursively if necessary
271+
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
272+
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), this.metadataReaderFactory)) {
273+
parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
274+
}
272275
}
273276
}
274277
}

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

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ public void viaBeanRegistration() {
147147
@Test
148148
public void withCustomBeanNameGenerator() {
149149
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
150-
ctx.register(ComponentScanWithBeanNameGenenerator.class);
150+
ctx.register(ComponentScanWithBeanNameGenerator.class);
151151
ctx.refresh();
152152
assertThat(ctx.containsBean("custom_fooServiceImpl"), is(true));
153153
assertThat(ctx.containsBean("fooServiceImpl"), is(false));
@@ -159,6 +159,14 @@ public void withScopeResolver() {
159159
// custom scope annotation makes the bean prototype scoped. subsequent calls
160160
// to getBean should return distinct instances.
161161
assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
162+
assertThat(ctx.containsBean("scannedComponent"), is(false));
163+
}
164+
165+
@Test
166+
public void multiComponentScan() {
167+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MultiComponentScan.class);
168+
assertThat(ctx.getBean(CustomScopeAnnotationBean.class), not(sameInstance(ctx.getBean(CustomScopeAnnotationBean.class))));
169+
assertThat(ctx.containsBean("scannedComponent"), is(true));
162170
}
163171

164172
@Test
@@ -241,7 +249,8 @@ public void withBasePackagesAndValueAlias() {
241249
@ComponentScan
242250
@Retention(RetentionPolicy.RUNTIME)
243251
@Target(ElementType.TYPE)
244-
public static @interface ComposedConfiguration {
252+
public @interface ComposedConfiguration {
253+
245254
String[] basePackages() default {};
246255
}
247256

@@ -253,8 +262,9 @@ public static class ComposedAnnotationConfig {
253262

254263

255264
@Configuration
256-
@ComponentScan(basePackageClasses=example.scannable._package.class)
265+
@ComponentScan(basePackageClasses = example.scannable._package.class)
257266
class ComponentScanAnnotatedConfig {
267+
258268
@Bean
259269
public TestBean testBean() {
260270
return new TestBean();
@@ -264,6 +274,7 @@ public TestBean testBean() {
264274
@Configuration
265275
@ComponentScan("example.scannable")
266276
class ComponentScanAnnotatedConfig_WithValueAttribute {
277+
267278
@Bean
268279
public TestBean testBean() {
269280
return new TestBean();
@@ -272,38 +283,50 @@ public TestBean testBean() {
272283

273284
@Configuration
274285
@ComponentScan
275-
class ComponentScanWithNoPackagesConfig {}
286+
class ComponentScanWithNoPackagesConfig {
287+
}
276288

277289
@Configuration
278-
@ComponentScan(basePackages="example.scannable", nameGenerator=MyBeanNameGenerator.class)
279-
class ComponentScanWithBeanNameGenenerator {}
290+
@ComponentScan(basePackages = "example.scannable", nameGenerator = MyBeanNameGenerator.class)
291+
class ComponentScanWithBeanNameGenerator {
292+
}
280293

281294
class MyBeanNameGenerator extends AnnotationBeanNameGenerator {
295+
282296
@Override
283297
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
284298
return "custom_" + super.generateBeanName(definition, registry);
285299
}
286300
}
287301

288302
@Configuration
289-
@ComponentScan(basePackages="example.scannable_scoped", scopeResolver=MyScopeMetadataResolver.class)
290-
class ComponentScanWithScopeResolver {}
303+
@ComponentScan(basePackages = "example.scannable_scoped", scopeResolver = MyScopeMetadataResolver.class)
304+
class ComponentScanWithScopeResolver {
305+
}
306+
307+
@Configuration
308+
@ComponentScan(basePackages = "example.scannable_scoped", scopeResolver = MyScopeMetadataResolver.class)
309+
@ComponentScan(basePackages = "example.scannable_implicitbasepackage")
310+
class MultiComponentScan {
311+
}
291312

292313
class MyScopeMetadataResolver extends AnnotationScopeMetadataResolver {
314+
293315
MyScopeMetadataResolver() {
294316
this.scopeAnnotationType = MyScope.class;
295317
}
296318
}
297319

298320
@Configuration
299321
@ComponentScan(
300-
basePackages="org.springframework.context.annotation",
301-
useDefaultFilters=false,
322+
basePackages = "org.springframework.context.annotation",
323+
useDefaultFilters = false,
302324
includeFilters = @Filter(type = FilterType.CUSTOM, classes = ComponentScanParserTests.CustomTypeFilter.class),
303325
// exclude this class from scanning since it's in the scanned package
304326
excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ComponentScanWithCustomTypeFilter.class),
305327
lazyInit = true)
306328
class ComponentScanWithCustomTypeFilter {
329+
307330
@Bean
308331
@SuppressWarnings({ "rawtypes", "serial", "unchecked" })
309332
public static CustomAutowireConfigurer customAutowireConfigurer() {
@@ -318,46 +341,46 @@ public ComponentScanParserTests.KustomAnnotationAutowiredBean testBean() {
318341
}
319342

320343
@Configuration
321-
@ComponentScan(basePackages="example.scannable",
322-
scopedProxy=ScopedProxyMode.INTERFACES,
323-
useDefaultFilters=false,
344+
@ComponentScan(basePackages = "example.scannable",
345+
scopedProxy = ScopedProxyMode.INTERFACES,
346+
useDefaultFilters = false,
324347
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = ScopedProxyTestBean.class))
325348
class ComponentScanWithScopedProxy {}
326349

327350
@Configuration
328-
@ComponentScan(basePackages="example.scannable",
329-
scopedProxy=ScopedProxyMode.INTERFACES,
330-
useDefaultFilters=false,
331-
includeFilters=@Filter(type=FilterType.REGEX, pattern ="((?:[a-z.]+))ScopedProxyTestBean"))
351+
@ComponentScan(basePackages = "example.scannable",
352+
scopedProxy = ScopedProxyMode.INTERFACES,
353+
useDefaultFilters = false,
354+
includeFilters = @Filter(type=FilterType.REGEX, pattern = "((?:[a-z.]+))ScopedProxyTestBean"))
332355
class ComponentScanWithScopedProxyThroughRegex {}
333356

334357
@Configuration
335-
@ComponentScan(basePackages="example.scannable",
336-
scopedProxy=ScopedProxyMode.INTERFACES,
337-
useDefaultFilters=false,
338-
includeFilters=@Filter(type=FilterType.ASPECTJ, pattern ="*..ScopedProxyTestBean"))
358+
@ComponentScan(basePackages = "example.scannable",
359+
scopedProxy = ScopedProxyMode.INTERFACES,
360+
useDefaultFilters = false,
361+
includeFilters = @Filter(type=FilterType.ASPECTJ, pattern = "*..ScopedProxyTestBean"))
339362
class ComponentScanWithScopedProxyThroughAspectJPattern {}
340363

341364
@Configuration
342-
@ComponentScan(basePackages="example.scannable",
343-
useDefaultFilters=false,
344-
includeFilters={
365+
@ComponentScan(basePackages = "example.scannable",
366+
useDefaultFilters = false,
367+
includeFilters = {
345368
@Filter(CustomStereotype.class),
346369
@Filter(CustomComponent.class)
347370
}
348371
)
349372
class ComponentScanWithMultipleAnnotationIncludeFilters1 {}
350373

351374
@Configuration
352-
@ComponentScan(basePackages="example.scannable",
353-
useDefaultFilters=false,
354-
includeFilters=@Filter({CustomStereotype.class, CustomComponent.class})
375+
@ComponentScan(basePackages = "example.scannable",
376+
useDefaultFilters = false,
377+
includeFilters = @Filter({CustomStereotype.class, CustomComponent.class})
355378
)
356379
class ComponentScanWithMultipleAnnotationIncludeFilters2 {}
357380

358381
@Configuration
359382
@ComponentScan(
360-
value="example.scannable",
361-
basePackages="example.scannable",
362-
basePackageClasses=example.scannable._package.class)
383+
value = "example.scannable",
384+
basePackages = "example.scannable",
385+
basePackageClasses = example.scannable._package.class)
363386
class ComponentScanWithBasePackagesAndValueAlias {}

0 commit comments

Comments
 (0)