Skip to content

Commit 6991cd9

Browse files
committed
Allow @configuration classes to self-@componentscan
Prior to this change, a @configuration classes that @componentscan themselves would result in a ConflictingBeanDefinitionException. For example: package com.foo.config; @configuration @componentscan("com.foo"); public class AppConfig { // ... } This resulted in a ConflictingBeanDefinitionException that users have typically worked around in the following fashion: package com.foo.config; @configuration @componentscan(basePackages="com.foo", excludeFilters=@filter(value=ANNOTATION_TYPE, type=Configuration.class); public class AppConfig { // ... } This is obviously more verbose and cumbersome than would be desirable, and furthermore potentially too constraining as it prohibits the ability to include other legitimate @configuration classes via scanning. The exception was being thrown because of a logic problem in ClassPathBeanDefinitionScanner. The bean definition for AppConfig gets registered once by the user (e.g. when constructing an AnnotationConfigApplicationContext), then again when performing the component scan for 'com.foo'. Prior to this change, ClassPathBeanDefinitionScanner's #isCompatible returned false if the new bean definition was anything other than an AnnotatedBeanDefinition. The intention of this check is really to see whether the new bean definition is a *scanned* bean definition, i.e. the result of a component-scanning operation. If so, then it becomes safe to assume that the original bean definition is the one that should be kept, as it is the one explicitly registered by the user. Therefore, the fix is as simple as narrowing the instanceof check from AnnotatedBeanDefinition to its ScannedGenericBeanDefinition subtype. Note that this commit partially reverts changes introduced in SPR-8307 that explicitly caught ConflictingBeanDefinitionExceptions when processing recursive @componentscan definitions, and rethrew as a "CircularComponentScanException. With the changes in this commit, such CBDEs will no longer occur, obviating the need for this check and for this custom exception type altogether. Issue: SPR-8808, SPR-8307
1 parent 450c753 commit 6991cd9

File tree

5 files changed

+56
-44
lines changed

5 files changed

+56
-44
lines changed

org.springframework.context/src/main/java/org/springframework/context/annotation/CircularComponentScanException.java

Lines changed: 0 additions & 32 deletions
This file was deleted.

org.springframework.context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition)
326326
* new definition to be skipped in favor of the existing definition
327327
*/
328328
protected boolean isCompatible(BeanDefinition newDefinition, BeanDefinition existingDefinition) {
329-
return (!(existingDefinition instanceof AnnotatedBeanDefinition) || // explicitly registered overriding bean
329+
return (!(existingDefinition instanceof ScannedGenericBeanDefinition) || // explicitly registered overriding bean
330330
newDefinition.getSource().equals(existingDefinition.getSource()) || // scanned same file twice
331331
newDefinition.equals(existingDefinition)); // scanned equivalent class twice
332332
}

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,7 @@ protected void doProcessConfigurationClass(ConfigurationClass configClass, Annot
197197
// check the set of scanned definitions for any further config classes and parse recursively if necessary
198198
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
199199
if (ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), metadataReaderFactory)) {
200-
try {
201-
this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
202-
} catch (ConflictingBeanDefinitionException ex) {
203-
throw new CircularComponentScanException(
204-
"A conflicting bean definition was detected while processing @ComponentScan annotations. " +
205-
"This usually indicates a circle between scanned packages.", ex);
206-
}
200+
this.parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
207201
}
208202
}
209203
}

org.springframework.context/src/test/java/org/springframework/context/annotation/ComponentScanAnnotationRecursionTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.springframework.context.annotation.componentscan.level3.Level3Component;
2727

2828
/**
29-
* Tests ensuring that configuration clasess marked with @ComponentScan
29+
* Tests ensuring that configuration classes marked with @ComponentScan
3030
* may be processed recursively
3131
*
3232
* @author Chris Beams
@@ -50,11 +50,12 @@ public void recursion() {
5050
assertThat(ctx.getBean("level2Bean"), sameInstance(ctx.getBean("level2Bean")));
5151
}
5252

53-
@Test(expected=CircularComponentScanException.class)
54-
public void cycleDetection() {
53+
public void evenCircularScansAreSupported() {
5554
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
56-
ctx.register(LeftConfig.class);
55+
ctx.register(LeftConfig.class); // left scans right, and right scans left
5756
ctx.refresh();
57+
ctx.getBean("leftConfig"); // but this is handled gracefully
58+
ctx.getBean("rightConfig"); // and beans from both packages are available
5859
}
5960

6061
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2002-2011 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.spr8808;
18+
19+
import org.junit.Test;
20+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
21+
import org.springframework.context.annotation.ComponentScan;
22+
import org.springframework.context.annotation.Configuration;
23+
24+
/**
25+
* Tests cornering the bug in which @Configuration classes that @ComponentScan themselves
26+
* would result in a ConflictingBeanDefinitionException.
27+
*
28+
* @author Chris Beams
29+
* @since 3.1
30+
*/
31+
public class Spr8808Tests {
32+
33+
/**
34+
* This test failed with ConflictingBeanDefinitionException prior to fixes for
35+
* SPR-8808.
36+
*/
37+
@Test
38+
public void repro() {
39+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
40+
ctx.register(Config.class);
41+
ctx.refresh();
42+
}
43+
44+
}
45+
46+
@Configuration
47+
@ComponentScan(basePackageClasses=Spr8808Tests.class) // scan *this* package
48+
class Config {
49+
}

0 commit comments

Comments
 (0)